Нейминг: как давать осмысленные имена переменным

Нейминг. Любимое занятие разработчиков, которое занимает почетное место где-то между бесконечными совещаниями и рефакторингом кода. Как разработчики, мы знаем, что нейминг может быть как благословением, так и проклятием. Это важная часть нашей работы, но имена, которые мы придумываем, не всегда удачны.

В этой статье я расскажу, как эффективно составлять имена для переменных. Ведь даже если вы не испытываете проблем с придумыванием имен, используете ли вы весь их потенциал?

Спасительный паттерн нейминга

Если вы затрудняетесь назвать конкретную переменную, функцию или класс, воспользуйтесь шаблоном и задайте себе соответствующие вопросы. Шаблон имеет следующий вид:

[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).

Примеры нейминга

  1. __isUserLoggedIn:
  • scope: __ (приватная переменная в Python)
  • typePrefix: is (булево значение)
  • baseName: User
  • qualifier: LoggedIn
  1. fetchProductById:
  • scope: «» (публичные методы в большинстве языков не имеют специфичного префикса)
  • typePrefix: fetch (функция)
  • baseName: Product
  • qualifier: ById
  1. updateEmailPreference:
  • scope: «» (публичный)
  • typePrefix: update (функция)
  • baseName: Email
  • qualifier: Preference
  1. $distanceInMetersInput:
  • scope: $ (переменная jQuery)
  • typePrefix: «»
  • baseName: distance
  • qualifier: InMeters
  • typeSuffix: Input
  1. 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");
}

На данном этапе мы можем спокойно читать код, не вырывая себе волосы. Код решает две основные задачи:

  1. Он гарантирует, что когда пользователь вводит значения в левое и правое поля, правое всегда будет положительным, а левое – отрицательным.
  2. Если одно из полей еще не заполнено, код копирует значение текущего поля ввода в другое поле.

Понять такое поведение исходного кода было бы довольно сложно. Однако, используя переменные более эффективно, мы можем абстрагироваться от сложного синтаксиса и сделать код более естественным.

Поделитесь своими мыслями насчет нейминга в комментариях. Возможно, у вас есть еще какие-то предложения. Успешного кодинга!

Перевод статьи «Naming: Every Developer’s Nightmare».

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх