
Нейминг. Любимое занятие разработчиков, которое занимает почетное место где-то между бесконечными совещаниями и рефакторингом кода. Как разработчики, мы знаем, что нейминг может быть как благословением, так и проклятием. Это важная часть нашей работы, но имена, которые мы придумываем, не всегда удачны.
В этой статье я расскажу, как эффективно составлять имена для переменных. Ведь даже если вы не испытываете проблем с придумыванием имен, используете ли вы весь их потенциал?
Спасительный паттерн нейминга
Если вы затрудняетесь назвать конкретную переменную, функцию или класс, воспользуйтесь шаблоном и задайте себе соответствующие вопросы. Шаблон имеет следующий вид:
[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]