Отладка, стиль и финальная сборка
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Проект написан и вроде бы работает — самое время навести порядок: поймать оставшиеся баги, вынести повторы в функции, дать вещам понятные имена и сделать интерфейс пригодным для клавиатуры. Это та часть работы, которую видно не в коде, а в ощущении «сделано аккуратно».
Отладка — это не перечитывание кода в надежде, что ошибка сама бросится в глаза. Это короткий цикл из трёх шагов: что я ожидал увидеть, что увидел на самом деле и в какой момент эти две картины разошлись. Пока вы не назвали ожидаемое значение вслух, искать нечего.
NaN появился в конце, но родился раньше.Смотреть надо на каждый шаг цикла: печатать не голое значение, а с меткой, иначе в консоли получится столбик чисел без подписи.
Виновник виден сразу: первый элемент пропущен, а на третьем шаге цикл вышел за край массива. Лечится заменой на let i = 0 и i < prices.length. Когда логов становится много, переходите на вкладку Sources в инструментах разработчика: строка debugger в коде остановит выполнение, и все переменные можно будет рассмотреть без единой печати.
Почти все баги первого проекта — это три-четыре повторяющихся сюжета. Если узнавать их по симптому, отладка занимает минуту вместо вечера.
| Что видно | Обычная причина | Куда смотреть |
|---|---|---|
undefined вместо значения | функция ничего не вернула | есть ли return в каждой ветке if |
105 вместо 15 | число приехало строкой | Number() при чтении из поля ввода |
NaN в итоговой сумме | выход за границу массива или пустое поле объекта | печать индексов и length |
| ничего не происходит по клику | обработчик повешен на другой элемент или до его появления | порядок кода и селектор |
Скопированный кусок опасен не длиной, а тем, что правку в нём придётся вспомнить трижды. Найдите в проекте блоки, которые отличаются только данными, — и различия станут параметрами функции.
Есть простое правило трёх: два похожих куска — просто совпадение, три — уже система. Слишком ранняя общая функция часто обрастает флагами вроде isShort и withIcon, и внутри неё живут те же три варианта, только теперь спутанные в один клубок.
Хороший признак: если вы не можете придумать функции короткое честное имя, значит, она делает больше одного дела — и выносить рано.
Сравните две записи одного и того же: function f(a, b) { return a.filter((x) => x.p <= b); } — и версию ниже. Код одинаковый, но вторую можно прочитать вслух и сразу понять, что она делает.
| Начало имени | Что обещает | Пример |
|---|---|---|
is, has | внутри true или false | isEmpty, hasDiscount |
get, find | возвращает значение и ничего не ломает | getTotal, findAffordable |
render, update | меняет то, что видно на экране | renderCart |
handle, on | реакция на событие пользователя | handleSubmit |
Имя переменной — это комментарий, который невозможно забыть обновить.
Первое — подписи. У каждого поля должен быть свой <label for="..."> или, если подпись не помещается в макет, атрибут aria-label. Иконка-крестик без подписи для программы чтения с экрана — просто «кнопка», и пользователь не узнает, что она удаляет товар.
Второе — роли. Кликабельный <div> только выглядит как кнопка: Tab до него не доходит, ни Enter, ни пробел его не нажимают, программа чтения с экрана не называет его кнопкой. <button> даёт всё это сам; переход по адресу — <a>, действие — <button>. И роли, и подписи по пунктам разобраны в прологе — в статье про кнопки, ссылки и подписи.
Уберите руку с мыши и пройдите весь сценарий проекта клавишами: 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. Найдите причину — сначала печатью промежуточных значений, а не глазами — и исправьте.
Цикл стартует с i = 1, поэтому первый товар пропущен: в сумму попадает только 180. Индексы массива начинаются с нуля, значит и счётчик должен.
Чтобы такой баг не повторялся, перебирайте массив там, где границу задавать не нужно: for (const item of items) или items.reduce(...).
Уберите повтор и дайте понятные имена
Два одинаковых по смыслу блока и имена из одной буквы. Вынесите общее в функцию и переименуйте поля так, чтобы код читался вслух.
Различаются только данные, значит различие — это параметр. Поля получают полные имена, строка собирается шаблоном, а перебор берёт на себя forEach.
Теперь формат строки задан в одном месте: захотите добавить символ рубля — правка будет ровно одна.