Введение
В этой статье я хочу обратить внимание на такой аспект разработки как нейминг.
Мы рассмотрим, почему важно уделять внимание названиям переменных и функций, и как грамотный подход к неймингу может сократить время и усилия на поддержку и развитие проекта. Надеюсь, мои мысли окажутся полезными. Буду рада обратной связи в комментариях.
Описание проблемы
Чтобы писать хороший код, мы стремимся следовать различным принципам, таким как SOLID, DRY, KISS, осваиваем паттерны проектирования, анализируем различные архитектуры и подходы к организации кодовой базы. Но зачастую забываем о самой базовой, но критически важной вещи, без которой все эти усилия могут оказаться малоэффективными, – это хороший нейминг.
Код с плохим неймингом – это плохо читаемый код. А плохо читаемый код – это бомба замедленного действия. Вам рано или поздно к этому коду придется вернуться, чтобы исправить ошибки или добавить новый функционал, и тут возможны три сценария:
Вы решите всё отрефакторить. Это увеличит объем задачи и время ее выполнения, что потребует дополнительного тестирования и приведет к новым багам.
Вы решите ничего не рефакторить, а аккуратно и точечно пригородить костыль. Что сделает этот фрагмент кода еще более запутанным и менее привлекательным для дальнейшего вмешательства, а это, в свою очередь, рано или поздно превратит его в легаси.
Вам в целом будет лень глубоко копать, особенно если сроки поджимают, и вы добавите тот код, который посчитаете нужным, не учитывая какие-нибудь редко встречающиеся случаи, которые тестирование может и не покрывать.
А что насчет типизации? Возможно, подумали вы. Все верно, типизация это один из возможных способов улучшения читаемости кода. Однако даже типизация не гарантирует отсутствие ошибок, связанных с неправильным пониманием назначения функций и переменных. Например, когда две функции принимают и возвращают данные одинаковых типов, но одна из них создает новый массив, а другая – модифицирует существующий, это обязательно должно быть отражено в их названии.
Почему время, потраченное на нейминг, стоит того?
Потому что в долгосрочной перспективе время, потраченное на нейминг, сократит время, которое иначе придется потратить на:
Понимание и чтение кода.
Рефакторинг и исправление багов. Если код понятен и выполняет свою задачу, его не нужно рефакторить. Если код не рефакторить, то в нем не появляются новые баги.
Онбординг новых членов команды. Новым разработчикам и так довольно сложно, не стоит усложнять им жизнь еще и неочевидным неймингом.
Коммуникацию в команде. Хороший нейминг облегчает обсуждение кода, так как все разработчики понимают, о чем идет речь, без дополнительных пояснений.
Документацию кода. ?
Мотивация
Упрощение поиска
Например: Вы работаете с проектом, в котором множество функций и переменных имеют схожие или слишком общие названия, например, 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
. Теперь, когда вы начинаете вводить название функции, автозаполнение сразу подсказывает нужный вариант.
Заключение
Качество нейминга напрямую влияет на качество кода. Чтобы писать качественный код, важно придерживаться принципов хорошего нейминга. Но для этого эти принципы должны быть четко и однозначно определены. В своей следующей статье я подробно рассмотрю эти принципы.
Комментарии (21)
aleksandy
07.09.2024 10:34Переименуем fetchData в useFetchData, после чего этот хук перехочется вызывать внутри функций уже на подсознательном уровне.
У React-ивистов какой-то фетиш на префикс use? На основании чего должно перехотеться? Тем более очевидно, что данная функция не является частью React-библиотеки, и, соответсвенно, внутри может быть что угодно.
deamondz
07.09.2024 10:34+8потому что есть соглашение о наименовании специального типа функций (хуков) - оно должно начинаться с префикса use. Эти хуки нельзя вызывать в функциях (технически, конечно, вам никто не запретит и где-то это даже будет работать, но есть правила в линтерах это запрещающие)
подробнее тут - https://react.dev/learn/reusing-logic-with-custom-hooks#hook-names-always-start-with-use
Dadadam999
07.09.2024 10:34Имхо но в первом примере, в первом варианте код лучше выглядит. Во втором варианте есть явные проблемы.
Длинные названия переменных для читаемости тоже плохо. Необязательно каждую переменную в функции называть длинным названием, достаточно написать понятное название самой функции, т.к. всё что внутри неё инкапсулировано и имеет смысл в контексте этой функции.
Второй вариант нарушает принцип функционального программирования. Зачем выносить константы начала и конца недели в глобальную видимость, да ещё и нарушать нотацию js (вроде как) по неймингу, написав их капсом. По факту, второй вариант написан в процедурном стиле. Эти константы можно было перенести в аргументы функции, если их нужно менять или поместить их во внутрь функции.
Касаемо второго примера, то уже многие писали, что есть системные названия хуков. Если они режут глаз, можно их "обернуть" в функции с понятным названием.
Так же касаемо нейминга. useFetch это масло масленное. Есть негласно принятое правило нейминга функций/методов во всех ЯП. Первым в названии идёт глагол, вторым существительное, прилагательное и т.д. Use и fetch это два глагола. Тогда хотя бы надо было назвать функцию useFetching, а лучше назвать её по смыслу того, что делает fetch внутри. Например getUser, если fetch получает данные юзера с бэкенда.
uknowlina Автор
07.09.2024 10:34Насчет хуков хотела бы поспорить -- речь идет конкретно о хуках в React. Эти хуки должны быть легко отличимы от функций, потому что концептуально отличаются и используются по-разному. Поэтому вариант с названием хука без use в начале -- не вариант.
Однако я согласна с тем, что useFetchData звучит не очень, уместнее было бы при таком исполнении назвать его просто useData. В целом, в названии хуков редко после use идет глагол, просто такой пример. Я хотела придумать пример, который бы продемонстрировал, как вызов хука, ошибочно принятого за функцию, может привести к ошибке, и это лучшее, что пришло мне в голову ))
tertiumnon
07.09.2024 10:34+1Название метода подразумевает какой-то расчёт, но непонятно, что он возвращает или же не возвращает ничего!
Вот такое название метода будет говорить нам, что метода что-то делает, но ничего не возвращает:
function calculateBusinessDays(startDate, endDate)
Если мы хотим показать, что метод что-то возвращает, тогда используем:
function getBusinessDays(startDate, endDate)
Мы ведь всё равно понимаем, что мы отдали что-то в метод, метод что-то сделал и вернул нам обратно - не нужно лишних слов. Нужно только одно слово - get, чтобы показать, что метод возвращает.
Далее переменную следует переименовать:
let businessDayCount = 0;
В
let businessDaysCount = 0;
businessDays - это массив/список, businessDaysCount - это его "свойство".
!!!
Дополнительно отмечу, что использование TypeScript решит большую часть проблем, в том числе проблем с пониманием кода.
daressel
07.09.2024 10:34...
const [data, setData] = React.useState(null);
...
.then((data) => setData(data))для статьи, где рассказывают о важности нейминга, именуют данные просто словом data, так еще и в рамках одной функции две переменные с тем же именем...
Например: Вы работаете с проектом, в котором множество функций и переменных имеют схожие или слишком общие названия, например, data, info, processRequest
uknowlina Автор
07.09.2024 10:34Да, я еще думала, обратит ли кто-нибудь на это внимание. У меня был выбор, либо детально прорабатывать примеры, рассеивая фокус внимания, либо опускать некоторые детали, чтобы сфокусировать внимание на чем-то конкретном. Я решила пойти по второму пути, но многие комментаторы отписались, что примеры не очень, поэтому, видимо, они правда не очень. Буду стараться лучше!
JordanCpp
И что здесь понятно? Может тогда именовать явно: diffDates? Первоначальное название, больше говорит о какой то бизнес логике связанную с датами и днями. Но ника о том, что это просто разница в днях.
Rsa97
Это не разница в днях, а количество рабочих дней (исключая субботы и воскресенья) между двумя датами. Его, конечно же, можно посчитать и проще, но оставим это на совести автора.
orefkov
Это не количество рабочих дней, а просто количество дней без суббот и воскресений. Чтобы это был подсчет рабочих дней, надо хотя бы учитывать страну и производственный календарь с указанием праздничных дней.
uknowlina Автор
Я упростила для примера. Старалась сделать акцент на контрастность до/после, немного подзабив на детали, потому что статья рассчитана на начинающих. Но вы правы насчет того, что на них не стоило забивать, все-таки речь про нейминг ))
SamDark
businessDaysBetween(a, b)
?anonymous
НЛО прилетело и опубликовало эту надпись здесь
pqbd
Тут, ИМХО, вообще тонкий лёд. Что такое бизнес день? Рабочий день? Рабочий день - это вообще может быть часть часов, когда люди трудятся. А что с праздниками и переносами?
Звучит так, что я ожидаю что-то одно, а оно считает что-то другое. Или имя функции не отражает назначение, или в этой функции ошибки в расчете.
Надо было назвать getNumbeOfDaysBetweenTwoPointsOfTimeExcludingWeekend, ну или diffDatesExcludingWeekends
Ну и [SUNDAY, SATURDAY].includes(currentDayOfWeek) немного легче читать, чем предложенное условие
uknowlina Автор
Прям жестко, мне нравится :D
Но с вашим комментарием согласна, в следующий раз буду лучше примеры продумывать.
cstrike