Введение

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

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

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

Чтобы писать хороший код, мы стремимся следовать различным принципам, таким как 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. Теперь, когда вы начинаете вводить название функции, автозаполнение сразу подсказывает нужный вариант. 

Заключение

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