
Нейминг. Любимое занятие разработчиков, которое занимает почетное место где-то между бесконечными совещаниями и рефакторингом кода. Как разработчики, мы знаем, что нейминг может быть как благословением, так и проклятием. Это важная часть нашей работы, но имена, которые мы придумываем, не всегда удачны.
В этой статье я расскажу, как эффективно составлять имена для переменных. Ведь даже если вы не испытываете проблем с придумыванием имен, используете ли вы весь их потенциал?
Спасительный паттерн нейминга
Если вы затрудняетесь назвать конкретную переменную, функцию или класс, воспользуйтесь шаблоном и задайте себе соответствующие вопросы. Шаблон имеет следующий вид:
[scope][typePrefix][baseName][qualifier][typeSuffix].
1. [scope]:
- Какова видимость или доступность этой переменной? (Например, private, public, protected, internal, package-private).
- Является ли эта переменная специфичной для языка программирования или каких-либо соглашений? (Например, двойной символ подчеркивания в Python, знак доллара в jQuery,
@для переменных экземпляра в Ruby). - Является ли эта переменная частью фреймворка или библиотеки, требующей определенного шаблона именования? (Например, префикс
ngв Angular для директив, префиксuseв React для хуков).
2. [typePrefix]:
- Содержит ли эта переменная булево значение? Является ли функцией, возвращающей булево значение? (Примеры соответствующих префиксов — is, has, contains, are, integrates).
- Представляет ли переменная состояние, условие или действие, которое можно описать словами «is», «has», «contains», «integrates» («есть», «имеет», «содержит» или «интегрирует»)? (Примеры — isEnabled, hasAccess, containsItem).
- Является ли эта функция ответственной за получение, установку, выборку или обновление данных? (Примеры префиксов — get, set, fetch, update, retrieve, store, save).
3. [baseName]:
- Каково основное назначение этой переменной? (Например, user, distance, payment, errorMessage).
- Можно ли описать роль переменной в коде с помощью четкого, краткого слова или фразы? (Например, registrationStatus, totalPrice, elapsedTime).
4. [qualifier]:
- Есть ли дополнительные детали или контекст, которые помогут отличить эту переменную от других с аналогичным назначением? (Например, FirstName, LastName, PhoneNumber).
- Имеет ли значение переменной конкретные единицы измерения или свойства, которые должны быть включены в имя? (Например, distanceInMiles, timeInSeconds).
- Есть ли конкретное состояние или условие, которое представляет эта переменная? (например, isValidEmail, isRemovableItem).
5. [typeSuffix]:
- Каково основное назначение или структура этой переменной? (Например, Count, Index, Sum, Average, List, Map, Array, Set, Queue).
- Можно ли уточнить роль переменной, добавив суффикс структуры? (Например, itemCount, currentIndex, totalPriceSum, ratingAverage, userList, settingsMap).
Примеры нейминга
__isUserLoggedIn:
- scope: __ (приватная переменная в Python)
- typePrefix: is (булево значение)
- baseName: User
- qualifier: LoggedIn
fetchProductById:
- scope: «» (публичные методы в большинстве языков не имеют специфичного префикса)
- typePrefix: fetch (функция)
- baseName: Product
- qualifier: ById
updateEmailPreference:
- scope: «» (публичный)
- typePrefix: update (функция)
- baseName: Email
- qualifier: Preference
$distanceInMetersInput:
- scope: $ (переменная jQuery)
- typePrefix: «»
- baseName: distance
- qualifier: InMeters
- typeSuffix: Input
getUserByEmail:
- typePrefix: get (функция)
- baseName: User
- qualifier: ByEmail
Бывают случаи, когда необходимо поменять местами базовое имя и классификатор ([baseName] и [qualifier]), чтобы дать переменной более подходящее и осмысленное имя. Пример: getLoggedInUser вместо .getUserLoggedIn
От редакции Techrocks: о нейминге в CSS читайте в статье «Методология БЭМ: именование классов и идентификаторов».
Эффективный нейминг переменных
Итак, у вас есть надежный план именования переменных. Но это только половина успеха. Другая половина? Комментарии. Но не в том смысле, о котором вы думаете.
Возможно, вы слышали советы разбросать комментарии по всему коду, чтобы сделать его более понятным, особенно для самого себя в будущем. Но на самом деле это не всегда наилучший подход.
Подумайте вот о чем: если ваш код непонятен без комментариев, то проблема не в отсутствии комментариев. А если ваш код и так понятен, то комментарии вообще не нужны.
Итак, что я пытаюсь донести? Используйте переменные в качестве комментариев.
Когда вы храните все в переменных с осмысленными именами, вы часто можете понять весь код, просто прочитав эти имена и ключевые управляющие структуры, такие как операторы if. Позвольте мне показать вам пример.
На днях я наткнулся на кусок кода, на полное понимание которого у меня ушло около 10 минут.
if (!row.doc[otherField]) {
let val;
if(currentField == "left") {
val = row.doc[currentField].charAt(0) === "-" ? row.doc[currentField].slice(1) : row.doc[currentField];
}
if(currentField == "right") {
val = row.doc[currentField].charAt(0) === "-" ? row.doc[currentField] : `-${row.doc[currentField]}`;
}
row.doc[otherField] = val === "-0" ? "0" : val;
row.refreshField(otherField);
}
if (currentField === "left" && row.doc["left"] && row.doc["left"].charAt(0) !== "-") {
row.doc["left"] = row.doc["left"] === "0" ? row.doc["left"] : `-${row.doc["left"]}`;
row.refreshField("left");
}
if (currentField === "right" && row.doc["right"] && row.doc["right"].charAt(0) === "-") {
row.doc["right"] = row.doc["right"].slice(1);
row.refreshField("right");
}
Поэтому давайте очистим его, поместив все в переменные и дав им осмысленные имена:
const valueOfOtherField = row.doc[otherField];
const valueOfCurrentField = row.doc[currentField];
const valueOfLeftField = row.doc["left"];
const valueOfRightField = row.doc["right"];
const isCurrentFieldOnLeft = currentField === "left";
const isCurrentFieldOnRight = currentField === "right";
const startsWithMinusSign = (str) => str.charAt(0) === "-";
const removeMinusFromZero = (str) => str === "-0" ? "0" : str;
const ensureMinusAtStart = (str) => startsWithMinusSign(str) ? str : `-${str}`;
const removeMinusFromStart = (str) => str.replace(/^-/, "");
if (!valueOfOtherField) {
let val;
if (isCurrentFieldOnLeft) {
val = startsWithMinusSign(valueOfCurrentField) ?
removeMinusFromStart(valueOfCurrentField) :
valueOfCurrentField;
} else if (isCurrentFieldOnRight) {
val = startsWithMinusSign(valueOfCurrentField) ?
valueOfCurrentField :
ensureMinusAtStart(valueOfCurrentField);
}
row.doc[otherField] = removeMinusFromZero(val);
row.refreshField(otherField);
}
if (isCurrentFieldOnLeft && valueOfLeftField && !startsWithMinusSign(valueOfLeftField)) {
row.doc["left"] = removeMinusFromZero(ensureMinusAtStart(valueOfLeftField));
row.refreshField("left");
}
if (isCurrentFieldOnRight && valueOfRightField && startsWithMinusSign(valueOfRightField)) {
row.doc["right"] = removeMinusFromStart(valueOfRightField);
row.refreshField("right");
}
В результате внесенных изменений код читается почти как обычный английский. Однако при ближайшем рассмотрении можно обнаружить несколько ненужных логических шагов, которые можно удалить и упростить. Возьмем, к примеру, эту строку:
val = startsWithMinusSign(valueOfCurrentField) ?
valueOfCurrentField :
ensureMinusAtStart(valueOfCurrentField);
Логически это можно упростить еще больше. Если значение начинается со знака минус, мы сохраняем его. В противном случае добавляем новый знак минус. По сути, это означает, что мы можем просто добавить знак минус в начало. Таким образом, строка может быть упрощена до следующей:
val = ensureMinusAtStart(valueOfCurrentField);
Предполагая, что производительностью row.refreshField можно пренебречь, ту же логику можно применить к операторам if, удалив условия && startsWithMinusSign(valueOfRightField) и && !startsWithMinusSign(valueOfLeftField). Теперь весь код должен выглядеть следующим образом:
const valueOfOtherField = row.doc[otherField];
const valueOfCurrentField = row.doc[currentField];
const valueOfLeftField = row.doc["left"];
const valueOfRightField = row.doc["right"];
const isCurrentFieldOnLeft = currentField === "left";
const isCurrentFieldOnRight = currentField === "right";
const startsWithMinusSign = (str) => str.charAt(0) === "-";
const removeMinusFromZero = (str) => str === "-0" ? "0" : str;
const ensureMinusAtStart = (str) => removeMinusFromZero(
startsWithMinusSign(str) ? str : `-${str}`
);
const removeMinusFromStart = (str) => str.replace(/^-/, "");
if (!valueOfOtherField) {
// If the current input field is on the left, let's remove the minus from the start
// and if its on the right, let's add the minus at the start. And put it into the
// other field. (NOTE: In the original code there were exactly two fields, left and right.)
const newValue = isCurrentFieldOnLeft ?
removeMinusFromStart(valueOfCurrentField) :
ensureMinusAtStart(valueOfCurrentField);
row.doc[otherField] = newValue;
row.refreshField(otherField);
}
// If the current field is the left one and there is an inputted value then
// make sure to add the minus at the start
if (isCurrentFieldOnLeft && valueOfLeftField) {
row.doc["left"] = ensureMinusAtStart(valueOfLeftField);
row.refreshField("left");
}
// If the current field is the right one and there is an inputted value then
// make sure to remove the minus from the start
if (isCurrentFieldOnRight && valueOfRightField) {
row.doc["right"] = removeMinusFromStart(valueOfRightField);
row.refreshField("right");
}
На данном этапе мы можем спокойно читать код, не вырывая себе волосы. Код решает две основные задачи:
- Он гарантирует, что когда пользователь вводит значения в левое и правое поля, правое всегда будет положительным, а левое — отрицательным.
- Если одно из полей еще не заполнено, код копирует значение текущего поля ввода в другое поле.
Понять такое поведение исходного кода было бы довольно сложно. Однако, используя переменные более эффективно, мы можем абстрагироваться от сложного синтаксиса и сделать код более естественным.
Поделитесь своими мыслями насчет нейминга в комментариях. Возможно, у вас есть еще какие-то предложения. Успешного кодинга!
Перевод статьи «Naming: Every Developer’s Nightmare».
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]


