Отладка, стиль и финальная сборка

проект≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Проект написан и вроде бы работает — самое время навести порядок: поймать оставшиеся баги, вынести повторы в функции, дать вещам понятные имена и сделать интерфейс пригодным для клавиатуры. Это та часть работы, которую видно не в коде, а в ощущении «сделано аккуратно».

Разделы темы:·

Отладка — это не перечитывание кода в надежде, что ошибка сама бросится в глаза. Это короткий цикл из трёх шагов: что я ожидал увидеть, что увидел на самом деле и в какой момент эти две картины разошлись. Пока вы не назвали ожидаемое значение вслух, искать нечего.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
разбор вывода
NaN появился в конце, но родился раньше.

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Виновник виден сразу: первый элемент пропущен, а на третьем шаге цикл вышел за край массива. Лечится заменой на let i = 0 и i < prices.length. Когда логов становится много, переходите на вкладку Sources в инструментах разработчика: строка debugger в коде остановит выполнение, и все переменные можно будет рассмотреть без единой печати.

Почти все баги первого проекта — это три-четыре повторяющихся сюжета. Если узнавать их по симптому, отладка занимает минуту вместо вечера.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
Что видноОбычная причинаКуда смотреть
undefined вместо значенияфункция ничего не вернулаесть ли return в каждой ветке if
105 вместо 15число приехало строкойNumber() при чтении из поля ввода
NaN в итоговой суммевыход за границу массива или пустое поле объектапечать индексов и length
ничего не происходит по кликуобработчик повешен на другой элемент или до его появленияпорядок кода и селектор

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Есть простое правило трёх: два похожих куска — просто совпадение, три — уже система. Слишком ранняя общая функция часто обрастает флагами вроде isShort и withIcon, и внутри неё живут те же три варианта, только теперь спутанные в один клубок.

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

Сравните две записи одного и того же: function f(a, b) { return a.filter((x) => x.p <= b); } — и версию ниже. Код одинаковый, но вторую можно прочитать вслух и сразу понять, что она делает.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
Начало имениЧто обещаетПример
is, hasвнутри true или falseisEmpty, hasDiscount
get, findвозвращает значение и ничего не ломаетgetTotal, findAffordable
render, updateменяет то, что видно на экранеrenderCart
handle, onреакция на событие пользователяhandleSubmit

Имя переменной — это комментарий, который невозможно забыть обновить.

Первое — подписи. У каждого поля должен быть свой <label for="..."> или, если подпись не помещается в макет, атрибут aria-label. Иконка-крестик без подписи для программы чтения с экрана — просто «кнопка», и пользователь не узнает, что она удаляет товар.

Второе — роли. Кликабельный <div> только выглядит как кнопка: Tab до него не доходит, ни Enter, ни пробел его не нажимают, программа чтения с экрана не называет его кнопкой. <button> даёт всё это сам; переход по адресу — <a>, действие — <button>. И роли, и подписи по пунктам разобраны в прологе — в статье про кнопки, ссылки и подписи.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Уберите руку с мыши и пройдите весь сценарий проекта клавишами: Tab — вперёд, Shift + Tab — назад, Enter и пробел — нажатие. Если фокус куда-то пропал, застрял в модальном окне или прыгает не в том порядке, что и элементы на экране, — вы нашли проблему.

Второй быстрый тест: увеличьте страницу до 200 % и посмотрите, не наехали ли блоки друг на друга. Оба теста занимают минуту и ловят больше, чем кажется.

Перед тем как показать проект кому-то, пройдите короткий чек-лист: консоль пуста от ошибок, все данные приводятся к числу там, где считаются, повторов нет, имена читаются вслух, по проекту можно пройти клавиатурой, а в коде не осталось отладочных console.log.

Следующий шагЧто он даёт
fetch и async/awaitнастоящие данные с сервера вместо массива в коде
localStorageпроект помнит состояние после перезагрузки страницы
Модули import / exportкод разложен по файлам, а не в одном длинном скрипте
Git и сборщик (Vite)история изменений и публикация проекта в интернете
Фреймворк (React, Vue, Svelte)интерфейс описывается состоянием, а не ручными правками страницы

И главный совет: не бросайте этот проект. Добавьте к нему по одной вещи из таблицы — сначала сохранение в localStorage, потом загрузку данных с сервера. Одно живое приложение, выросшее за месяц, учит быстрее, чем пять начатых и брошенных заготовок.

  • Отладка — это цикл «что ожидал → что получил → где разошлось»; печать с меткой шага находит место расхождения быстрее, чем перечитывание кода.
  • Три постоянных источника странного вывода: забытый return, число, приехавшее строкой, и выход за границу массива.
  • Повтор выносится в функцию, где различия становятся параметрами; правило трёх защищает от слишком раннего обобщения с флагами.
  • Хорошее имя заменяет комментарий: findAffordable(products, budget) читается вслух, а f(a, b) — нет.
  • Минимум доступности: подпись у каждого поля, настоящая кнопка вместо кликабельного блока и полный проход по проекту клавишей Tab.

1/3Что выведет console.log(double(21)), если функция написана так: function double(n) { n * 2; }

2/3Значение из поля ввода пришло как "10". Что напечатает console.log("10" + 5)?

3/3Нужен элемент «Удалить», по которому кликают. Что выбрать?

Найдите и почините баг в подсчёте корзины

Функция должна вернуть сумму всех цен, но возвращает 180 вместо 430. Найдите причину — сначала печатью промежуточных значений, а не глазами — и исправьте.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Цикл стартует с i = 1, поэтому первый товар пропущен: в сумму попадает только 180. Индексы массива начинаются с нуля, значит и счётчик должен.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Чтобы такой баг не повторялся, перебирайте массив там, где границу задавать не нужно: for (const item of items) или items.reduce(...).

Уберите повтор и дайте понятные имена

Два одинаковых по смыслу блока и имена из одной буквы. Вынесите общее в функцию и переименуйте поля так, чтобы код читался вслух.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Различаются только данные, значит различие — это параметр. Поля получают полные имена, строка собирается шаблоном, а перебор берёт на себя forEach.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Теперь формат строки задан в одном месте: захотите добавить символ рубля — правка будет ровно одна.