[Перевод] Греппабельность — важная метрика кода
При работе над поддержкой незнакомой мне кодовой базы я трачу кучу времени на поиск строк при помощи 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
, которая может встречаться в приложении во многих местах.