Введение

В этой статье я хочу обратить внимание на такой аспект разработки как нейминг.

Мы рассмотрим, почему важно уделять внимание названиям переменных и функций, и как грамотный подход к неймингу может сократить время и усилия на поддержку и развитие проекта. Надеюсь, мои мысли окажутся полезными. Буду рада обратной связи в комментариях.

Описание проблемы

Чтобы писать хороший код, мы стремимся следовать различным принципам, таким как SOLID, DRY, KISS, осваиваем паттерны проектирования, анализируем различные архитектуры и подходы к организации кодовой базы. Но зачастую забываем о самой базовой, но критически важной вещи, без которой все эти усилия могут оказаться малоэффективными, – это хороший нейминг. 

Код с плохим неймингом – это плохо читаемый код. А плохо читаемый код – это бомба замедленного действия. Вам рано или поздно к этому коду придется вернуться, чтобы исправить ошибки или добавить новый функционал, и тут возможны три сценария:

  1. Вы решите всё отрефакторить. Это увеличит объем задачи и время ее выполнения, что потребует дополнительного тестирования и приведет к новым багам.

  2. Вы решите ничего не рефакторить, а аккуратно и точечно пригородить костыль. Что сделает этот фрагмент кода еще более запутанным и менее привлекательным для дальнейшего вмешательства, а это, в свою очередь, рано или поздно превратит его в легаси.

  3. Вам в целом будет лень глубоко копать, особенно если сроки поджимают, и вы добавите тот код, который посчитаете нужным, не учитывая какие-нибудь редко встречающиеся случаи, которые тестирование может и не покрывать.

А что насчет типизации? Возможно, подумали вы. Все верно, типизация это один из возможных способов улучшения читаемости кода. Однако даже типизация не гарантирует отсутствие ошибок, связанных с неправильным пониманием назначения функций и переменных. Например, когда две функции принимают и возвращают данные одинаковых типов, но одна из них создает новый массив, а другая – модифицирует существующий, это обязательно должно быть отражено в их названии.

Почему время, потраченное на нейминг, стоит того?

Потому что в долгосрочной перспективе время, потраченное на нейминг, сократит время, которое иначе придется потратить на:

  1. Понимание и чтение кода.

  2. Рефакторинг и исправление багов. Если код понятен и выполняет свою задачу, его не нужно рефакторить. Если код не рефакторить, то в нем не появляются новые баги. 

  3. Онбординг новых членов команды. Новым разработчикам и так довольно сложно, не стоит усложнять им жизнь еще и неочевидным неймингом.

  4. Коммуникацию в команде. Хороший нейминг облегчает обсуждение кода, так как все разработчики понимают, о чем идет речь, без дополнительных пояснений.

  5. Документацию кода. ?

Мотивация

Упрощение поиска

Например: Вы работаете с проектом, в котором множество функций и переменных имеют схожие или слишком общие названия, например, data, info, processRequest. Поиск нужной переменной превращается в настоящую проблему, поскольку такие названия встречаются в десятках мест.

Хороший нейминг: Если бы разработчики использовали конкретные и уникальные названия, такие как userData, orderInfo, processPayment, то поиск был бы гораздо легче.

Улучшение читаемости

Например: Попробуйте понять, что делает этот код.

function countDays(d1, d2) {
    let count = 0;
    let current = new Date(d1);
    while (current <= d2) {
        const day = current.getDay();
        if (day !== 0 && day !== 6) {
            count++;
        }
        current.setDate(current.getDate() + 1);
    }
    return count;
}
const days = countDays(new Date('2024-08-01'), new Date('2024-08-15'));

console.log(days);

Хороший нейминг: Если нейминг хороший, то становится понятно, что делает эта функция, просто прочитав код. Эта функция вычисляет, сколько рабочих дней прошло между двумя датами, исключая выходные.

const SUNDAY = 0;
const SATURDAY = 6;

function calculateBusinessDays(startDate, endDate) {
    let businessDayCount = 0;
    let currentDate = new Date(startDate);
    while (currentDate <= endDate) {
        const currentDayOfWeek = currentDate.getDay();
        if (currentDayOfWeek !== SUNDAY && currentDayOfWeek !== SATURDAY) {
            businessDayCount++;
        }
        currentDate.setDate(currentDate.getDate() + 1);
    }
    return businessDayCount;
}
const businessDays = calculateBusinessDays(new Date('2024-08-01'), new Date('2024-08-15'));

console.log(businessDays);

Предотвращение ошибок

Например: Имеем такой код.

function fetchData(url) {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    fetch(url)
      .then((response) => response.json())
      .then((data) => setData(data))
      .catch((error) => console.error(error));
  }, [url]);
  return data;
}

function MyComponent() {
  function handleButtonClick() {
    const data = fetchData("https://api.example.com/data"); 
    // Ошибка: вызов хука внутри функции
    console.log("Данные:", data);
  }
  return (
    <div>
      <button onClick={handleButtonClick}>Отобразить данные</button>
      <p>{JSON.stringify(data)}</p>
    </div>
  );
}

В этом примере fetchData используется внутри функции handleButtonClick, которая вызывается при нажатии кнопки. Поскольку fetchData на самом деле является хуком, его можно вызывать только на верхнем уровне в теле функционального компонента или другого хука. Вызов хука внутри функции-обработчика события (handleButtonClick) нарушает правила использования хуков, и React не сможет правильно обработать состояние. Из-за того что исходя из названия fetchData непонятно, что эта функция является хуком, повышается риск ее неправильного использования.

Хороший нейминг: Переименуем fetchData в useFetchData, после чего этот хук перехочется вызывать внутри функций уже на подсознательном уровне.

function useFetchData(url) {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    fetch(url)
      .then((response) => response.json())
      .then((data) => setData(data))
      .catch((error) => console.error(error));
  }, [url]);
  return data;
}
function MyComponent() {
  const data = useFetchData("https://api.example.com/data");
  // Правильное использование хука
  function handleButtonClick() {
    console.log("Данные:", data);
  }
  return (
    <div>
      <button onClick={handleButtonClick}>Отобразить данные</button>
      <p>{JSON.stringify(data)}</p>
    </div>
  );
}

Облегчение командной работы

Например: Имеем такой код.

// Компонент для отображения списка задач в дашборде
function TaskList({ tasks }) {
  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>{task.title}</li>
      ))}
    </ul>
  );
}

// Компонент для отображения списка задач в модальном окне
function TaskList({ tasks, onTaskClick }) {
  return (
    <div className="modal">
      <ul>
        {tasks.map((task) => (
          <li key={task.id} onClick={() => onTaskClick(task)}>
            {task.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

На Катю назначили баг с высоким приоритетом. Зная, что недавно в этом месте работал Валера, она сразу идет к нему за советом.

Катя: Слушай, у нас проблема с TaskList. Ты ведь недавно заливал обновление на тестовый стенд, правильно? Может, подскажешь, в чем дело? Мне пришел баг-репорт, что задачи не кликабельны, хотя должны быть. 

Валера ?: Нет, не должны. Олег зря создал тикет. Напиши ему, чтобы он обсудил это с нашим продактом Артуром и он убрал этот баг из спринта.

Катя связывается с Олегом, Олег – с Артуром, Артур отвечает Олегу, Олег – Кате. После целого дня переписки, Катя, вспоминая Валеру не самыми добрыми словами, все-таки идет исправлять баг, но не в дашборде, как предположил Валера, а в модальном окне, где задачи действительно должны быть кликабельными. Если бы название компонента отражало его назначение, изначальная беседа с Валерой была бы намного продуктивнее и сэкономила бы кучу времени..

Хороший нейминг: Переименуем TaskList в одном месте в TaskListDashboard, и в другом в TaskListModal.

// Компонент для отображения списка задач в дашборде
function TaskListDashboard({ tasks }) {
  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>{task.title}</li>
      ))}
    </ul>
  );
}

// Компонент для отображения списка задач в модальном окне
function TaskListModal({ tasks, onTaskClick }) {
  return (
    <div className="modal">
      <ul>
        {tasks.map((task) => (
          <li key={task.id} onClick={() => onTaskClick(task)}>
            {task.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

Удобное автозаполнение

Например: Вы работаете над крупным проектом и часто используете функции для форматирования дат. В проекте есть несколько утилит, которые помогают с этим, но названы они недостаточно конкретно: formatDate, formatDates, dateFormatter, dateForm. Каждый раз, когда вы пытаетесь вызвать одну из этих функций, автозаполнение в редакторе выдает список из всех похожих названий, что сбивает вас с толку и замедляет работу. Вы постоянно путаетесь, какая именно функция нужна в конкретном случае.

Хороший нейминг: Переименуем эти функции более осмысленно: formatDateForReport, formatDateForDisplay, formatDateForExport. Теперь, когда вы начинаете вводить название функции, автозаполнение сразу подсказывает нужный вариант. 

Заключение

Качество нейминга напрямую влияет на качество кода. Чтобы писать качественный код, важно придерживаться принципов хорошего нейминга. Но для этого эти принципы должны быть четко и однозначно определены. В своей следующей статье я подробно рассмотрю эти принципы.

Комментарии (4)


  1. JordanCpp
    07.09.2024 10:34

    calculateBusinessDays

    И что здесь понятно? Может тогда именовать явно: diffDates? Первоначальное название, больше говорит о какой то бизнес логике связанную с датами и днями. Но ника о том, что это просто разница в днях.


    1. Rsa97
      07.09.2024 10:34

      Это не разница в днях, а количество рабочих дней (исключая субботы и воскресенья) между двумя датами. Его, конечно же, можно посчитать и проще, но оставим это на совести автора.


    1. anonymous
      07.09.2024 10:34

      НЛО прилетело и опубликовало эту надпись здесь


  1. aleksandy
    07.09.2024 10:34

    Переименуем fetchData в useFetchData, после чего этот хук перехочется вызывать внутри функций уже на подсознательном уровне.

    У React-ивистов какой-то фетиш на префикс use? На основании чего должно перехотеться? Тем более очевидно, что данная функция не является частью React-библиотеки, и, соответсвенно, внутри может быть что угодно.