Предыстория
Когда я был младшим разработчиком, мне повезло начать разработку в среде, где я мог получать отзывы о коде. Поначалу я даже не имел чёткого представления о критериях проверки. Я считал, что разработка завершена, если функциональность работает правильно, а замечания, высказанные во время проверок, поначалу казались незначительными.
Мне часто задавали вопросы об областях, которые, казалось, не были напрямую связаны с функциональностью, например о порядке инструкций импорта или именах переменных.
-
«Действительно ли это значение состояния необходимо?»
-
«Здесь используется watch, но не было бы правильнее обработать это через событие?»
-
«Не слишком ли много обязанностей у этого компонента?»
-
«Корректно ли обрабатываются null, undefined и пустые массивы?»
Поначалу вносить каждое исправление по отдельности порой было утомительно. Однако после того как я на собственном опыте столкнулся с командной разработкой и сервисами, работающими в production, моя точка зрения постепенно изменилась. Вместо того чтобы ограничиваться вопросом «Работает ли функциональность прямо сейчас?», я начал спрашивать себя: «Если кому-то позже придётся изменить этот код, сможет ли он легко в нём разобраться?»
В production-среде трудно придерживаться установки «Если позже возникнет проблема, другой разработчик её исправит». Даже при командной разработке кто-то другой может изменить написанный мной код, или мне самому может понадобиться снова взглянуть на него через несколько месяцев.
По мере того как я повторял этот опыт, я естественным образом начал хотя бы раз проверять в процессе разработки пункты, на которые раньше обращал внимание во время code review.
Разумеется, на практике по-прежнему трудно тщательно проверять каждую часть кода. Особенно это верно, когда сроки разработки сжаты. Поэтому под «самопроверкой кода» я подразумеваю минимальный процесс верификации, который скорее напоминает быструю повторную проверку легко упустить проблемы при стремительной разработке, чем подробный анализ.
1. Почему необходима самопроверка кода
При быстрой разработке легко перейти к следующей задаче после реализации функции и подтверждения того, что она работает правильно. Если учитывать только скорость разработки, такой подход может быть эффективным. Однако чем быстрее вы разрабатываете, тем меньше у вас возможностей взглянуть на код объективно. Поскольку при написании кода вы понимаете общий контекст, ненужный код или неоднозначные структуры легко не заметить.
2. Проверка не заканчивается только потому, что функциональность работает
Если экран отображается нормально, а нажатие кнопки даёт желаемый результат, может показаться, что разработка завершена. Однако реальные сервисы состоят не только из штатных ситуаций (Happy Path). Даже для одного API-запроса необходимо учитывать различные сценарии.
-
Успешный ответ → Отобразить данные
-
Сбой API → Обработать ошибку
-
Нет данных в ответе → Пустое состояние
-
undefined / null → Проверить наличие данных и безопасно их обработать
-
Повторные нажатия → Предотвратить дублирование запросов или несогласованность состояния
При самопроверке я не ограничиваюсь простым вопросом «Работает ли это правильно?». Я также проверяю: «Как это ведёт себя в нештатных ситуациях?»
3. Первая проверка: поиск ненужного кода
Первое, на что я смотрю, удивительно просто: «Действительно ли этот код необходим?»
① Неиспользуемые переменные и инструкции импорта
Проверьте, не остались ли console.log() для тестирования, не используются ли ещё какие-либо переменные и не нужны ли больше импортированные модули. Хорошей идеей будет автоматизировать эту проверку с помощью инструментов статического анализа, таких как ESLint.
② Дублирование кода
Если одинаковые вызовы API и обработка ошибок повторяются на нескольких экранах, проверьте, нужно ли сделать их общими. Однако бездумно объединять всё не всегда правильно. Принимайте решение, исходя из вопроса: «Действительно ли сопровождение станет проще, если сделать этот код общим?»
③ Избыточные значения состояния
// ❌ AS-IS
const dataList = ref([]);
const isEmpty = ref(false);
const hasData = ref(false);
// ⭕ TO-BE
const dataList = ref([]);
const isEmpty = computed(() => dataList.value.length === 0);한 상태값
Как показано выше, сначала можно проверить, действительно ли нужно хранить отдельно в состоянии (ref) такие значения, как isEmpty или hasData, которые можно вычислить только на основе dataList.
Если состояние управляется отдельно, значения isEmpty или hasData также необходимо вручную обновлять при каждом изменении данных. Если пропустить хотя бы один фрагмент логики обновления состояния, может возникнуть ошибка: фактические данные существуют, но на экране отображается «Нет данных». Использование computed, чтобы логика зависела от исходных данных, уменьшает необходимость вручную синхронизировать значения состояния и снижает вероятность того, что разработчик пропустит обновление состояния.
4. Вторая проверка: проверка обязанностей компонента
Даже компонент, который изначально был простым, по мере развития разработки может получить множество обязанностей, таких как поиск пользователей, аутентификация, валидация формы, модальные окна и пагинация.
Когда я вижу большой компонент, я разделяю его на функциональные области и проверяю, можно ли отделить какие-либо из них независимо. Однако бессистемное разделение кода только потому, что он длинный, лишь увеличивает затраты на навигацию по файлам. Важно не «сделать его меньше», а «сделать обязанности понятными».
Props и Emits: Проверьте, не управляет ли родитель напрямую слишком большим количеством состояния и можно ли понять поведение, просто взглянув на имя события.
5. Третья проверка: проверка состояния и исключительных ситуаций
Лично я считаю это самым важным в самопроверке. Экраны гораздо чаще ломаются в исключительных ситуациях, чем в обычных.
①Состояния загрузки и ошибки
-
Отображается ли пользователю подходящее состояние во время загрузки данных?
-
Если API завершается ошибкой, обрабатывается ли она таким образом, чтобы пользователь мог выполнить следующее действие?
Однажды во время code review проекта я обнаружил проблему: при возникновении определённой ошибки во время аутентификации исключение не обрабатывалось, из-за чего весь экран становился белым и последующий процесс не мог продолжиться. Поскольку в обычном сценарии аутентификации проблем не было, эту ошибку было легко не заметить, если проверять только штатный случай. С тех пор, просматривая код вызова API, я проверяю не только результат успешного выполнения, но и спрашиваю себя: «Если здесь произойдёт сбой, какой экран увидит пользователь?»
② Пустые значения и Null / Undefined
Ситуация, в которой данные представлены пустым массивом ([]) (Empty State), отличается от ситуации, когда произошёл сбой API. Кроме того, если не учитывать возможность отсутствия profile в такой ссылке, как user.profile.name, может возникнуть ошибка во время выполнения, из-за которой экран не сможет корректно отобразиться.
6. Четвёртая проверка: проверка асинхронных операций и вызовов API
Дублирующиеся вызовы: Проверьте, не вызывается ли один и тот же API без необходимости при входе на страницу, при срабатывании watch или при возникновении пользовательского события.
Порядок асинхронной обработки (Race Condition): Проверьте, может ли при быстром двукратном выполнении пользователем одного действия сначала прийти ответ на более поздний запрос, а затем ответ на более ранний запрос, который перезапишет последнее состояние.
7. Пятый обзор: повторный анализ с точки зрения сопровождения
Наконец, я обобщаю всё с точки зрения человека, который видит код впервые.
Именование и семантика HTML: Используйте имена, раскрывающие роль, вместо const temp, и проверяйте, используется ли <button>, а не <div>, для кнопок, которым требуются события нажатия.
Комментарии и неиспользуемый код: Лучше удалить неиспользуемый код, чем оставлять его закомментированным. С историей предыдущих изменений кода можно ознакомиться в Git. Если комментарии необходимы, оставляйте в них объяснения того, что сложно понять только по коду, или описывайте то, что требует особого внимания.
Согласованность структуры: Проверьте, значительно ли отличаются расположение и стиль import, props, state, методов и прочего от существующего кода проекта. Однако вместо слепого следования существующему подходу также рассмотрите, нет ли более подходящего подхода для текущего кода.
Если в проекте уже существует общий подход к работе с API, вспомогательные средства или UI-компоненты, сначала проверьте существующие реализации, прежде чем создавать новый подход.
Возможно, существующий подход можно использовать без изменений, но в зависимости от ситуации вы также можете выбрать новый подход. Важно сначала выяснить, почему используется существующий подход, а затем определить, какой метод больше подходит для текущего кода.
Например, если вызовы API и обработка ошибок уже стандартизированы, сначала проверьте, можно ли использовать существующий подход. И наоборот, если существующий подход работает только в определённых ситуациях или нуждается в улучшении, рассмотрите возможность применения нового подхода.
8. Самопроверка на основе фактически внесённых изменений
Проверка всего этого в процессе написания кода может фактически замедлить разработку. Я предпочитаю сначала завершить реализацию функциональности, а затем повторно проверить её на экране, где виден только фактически изменённый код, например в Git diff или в изменениях PR.
Во время написания я сосредоточен на вопросе: «Как это следует реализовать?» Во время проверки я меняю точку зрения на следующую: «Если бы я увидел этот код впервые, смог бы я его понять?»
[Минимальный контрольный список для самопроверки]
-Функциональность
-
Работает ли это так, как задумано, при корректных входных данных?
-
Обрабатываются ли состояния загрузки, ошибки и отсутствия данных?
-
Проверили ли вы возможные значения null / undefined?
-
Не влияет ли это на существующую функциональность?
-Код
-
Есть ли неиспользуемые переменные / импорты / операторы console.log?
-
Создали ли вы какие-либо ненужные значения состояния?
-
Проверили ли вы наличие дублирующейся логики?
-
Не слишком ли велика зона ответственности компонента?
-API / Асинхронные операции
-
Не вызывается ли один и тот же API без необходимости повторно?
-
Согласована ли обработка ошибок API?
-
Существует ли вероятность того, что последовательные запросы приведут к несогласованному состоянию?
-Сопровождаемость
-
Можно ли понять роли переменных и функций, просто взглянув на их имена?
-
Есть ли неиспользуемый код или устаревшие комментарии?
-
Если вы использовали подход, отличающийся от существующего шаблона, есть ли для этого причина?
-
Сможет ли разработчик, впервые увидевший этот код, разобраться в нём?
9. Практический опыт проверки кода и разумные компромиссы
То, чему я научился, получая замечания во время проверок кода в качестве младшего разработчика, продолжало помогать мне, когда позднее я сам начал проводить такие проверки. Вместо того чтобы просто искать проблемы в коде, я также начал учитывать, как они могут повлиять на последующие изменения и сопровождение. На практике изучение кода с этой точки зрения иногда помогало мне обнаружить и исправить ошибки, которые легко не заметить во время разработки.
Однако невозможно применять идеальные стандарты к каждому проекту. Я столкнулся с этим, поддерживая проект с жёсткими сроками. Поскольку я также отвечал за разработку функциональности, у меня не было реального времени, чтобы привести в порядок весь существующий код. В такой ситуации я расставил приоритеты и сначала улучшил необходимые области.
-
Критические проблемы, влияющие на функциональность
-
Области, напрямую препятствующие сопровождаемости
-
Области, в которых стандартизация принесла бы очевидную пользу
-
Простая очистка стиля кода (удаление операторов console, упорядочивание комментариев и тому подобное с сохранением минимального объёма изменений)
Игнорирование всех проблем из-за жёстких сроков в конечном итоге обернётся техническим долгом, но попытка добиться совершенства сразу и срывать сроки — тоже проблема. Поэтому я расставляю приоритеты в зависимости от ситуации и стараюсь сначала улучшить необходимые области.
Заключение
Самопроверка кода не должна превращаться в подробный архитектурный обзор. Особенно это верно, когда требуется быстрая разработка. Для меня самопроверка кода в конечном счёте означает «ещё раз критически взглянуть на код после реализации функциональности».
В качестве младшего разработчика я учился, поочерёдно устраняя замечания, полученные во время проверок кода. Даже сейчас, сталкиваясь с похожими ситуациями, я стараюсь вспоминать вопросы, которые мне задавали во время тех проверок.
Когда нужно быстро разработать функциональность, вместо попытки идеально проверить каждую часть можно начать с того, чтобы потратить хотя бы пять минут и ещё раз взглянуть на код, проверив, не упустили ли вы что-нибудь.
Code_Latte