[Перевод] Греппабельность — важная метрика кода

A surveyor searching for metal in a field

При работе над поддержкой незнакомой мне кодовой базы я трачу кучу времени на поиск строк при помощи grep. Даже в проектах, полностью написанных мной, мне нужно много искать: имена функций, сообщения об ошибках, имена классов и тому подобное. Если я не могу найти нужное, то я буду как минимум расстроен, а как максимум могу создать опасную ситуацию, если предположу, что какой-то элемент больше не нужен, ведь я не могу найти ссылок на него в кодовой базе. На основании этих ситуаций я выработал правила, которые позволяют повысить греппабельность кода.

Не разделяйте идентификаторы

Оказалось, что разбиение или динамическое создание идентификаторов — это плохая идея.

Допустим, у нас есть две таблицы базы данных shipping_addresses,  billing_addresses,. Может показаться абсолютно нормальным решением создавать имя таблицы динамически по порядковому типу.

const getTableName = (addressType: 'shipping' | 'billing') => {
    return `${addressType}_addresses`
}

Хотя кажется, что это красиво и соответствует DRY, для поддержки это не очень удобно: кто-то неизбежно будет искать в кодовой базе имя shipping_addresses и пропустит это вхождение.

Отрефакторим код для повышения греппабельности:

const getTableName = (addressType: 'shipping' | 'billing') => {
    if (addressType === 'shipping') {
        return 'shipping_addresses'
    }
    if (addressType === 'billing') {
        return 'billing_addresses'
    }
    throw new TypeError('addressType must be billing or shipping')
}

То же самое относится к именам столбцов, полям объектов и, упаси боже, именам методов/функций (в Javascript можно создавать имена классов динамически).

Используйте одни и те же имена для элементов во всём стеке

Не переименовывайте поля на границах приложений, чтобы соответствовать схемам наименований. Очевидный пример: мы импортируем идентификаторы в snake_case в стиле Postgres в код на Javascript, а затем преобразуем их в camelCase. Это усложняет поиск — теперь чтобы найти все вхождения, вам нужно использовать grep для двух строк вместо одной!

const getAddress = async (id: string) => {
    const address = await getAddressById(id)
    return {
        streetName: address.street_name,
        zipCode: address.zip_code,
    }
}

Лучше пойти более сложным путём и возвращать непосредственно объект:

const getAddress = async (id: string) => {
    return await getAddressById(id)
}

Плоская структура лучше вложенной

Я взял этот совет из Дзена Пайтона: при работе с пространствами имён уплощение структур папок/объектов обычно лучше, чем вложенность.

Например, если у вас есть два варианта настройки файлов перевода:

{
    "auth": {
        "login": {
            "title": "Login",
            "emailLabel": "Email",
            "passwordLabel": "Password",
        },
        "register":
            "title": "Register",
            "emailLabel": "Email",
            "passwordLabel": "Password",
        }
    }
}

и

{
    "auth.login.title": "Login",
    "auth.login.emailLabel": "Email",
    "auth.login.passwordLabel": "Password",
    "auth.register.title": "Login",
    "auth.register.emailLabel": "Email",
    "auth.register.passwordLabel": "Password",
}

то выберите второй! Тогда вы с лёгкостью можете находить ключи, ссылки на которые предположительно будут выглядеть так:  t('auth.login.title').

Или рассмотрим структуру компонентов React. Структура компонентов

./components/AttributeFilterCombobox.tsx
./components/AttributeFilterDialog.tsx
./components/AttributeFilterRating.tsx
./components/AttributeFilterSelect.tsx

предпочтительнее, чем

./components/attribute/filter/Combobox.tsx
./components/attribute/filter/Dialog.tsx
./components/attribute/filter/Rating.tsx
./components/attribute/filter/Select.tsx

с точки зрения греппабельности, потому что вы сможете выполнять поиск grep всего компонента AttributeFilterCombobox в пространстве имён просто по его использованию, а не по строке Dialog, которая может встречаться в приложении во многих местах.

© Habrahabr.ru