Привет, Хабр! Меня зовут Алексей Гмитрон, я наставник на курсе «Веб-разработчик» Яндекс Практикума, а также работаю фулстек-разработчиком.  Я начал программировать шесть лет назад, и обучение не сразу давалось легко. Одна из главных проблем — не умел выяснять, почему мой код не работает. Это долго тормозило развитие, но когда я начал понимать принцип, как думать при поиске ошибок — процесс сдвинулся с мертвой точки.  Сейчас я преподаю в Практикуме, и ко мне на индивидуальные консультации часто приходят студенты с той же проблемой. Мы дебажим их код вместе, и за десятки подобных сессий я заметил общие трудности новичков в процессе отладки собственного кода. В этой статье расскажу о привычках, которые нужны самостоятельному разработчику для дебага.  Эта статья предназначена для тех, кто только недавно научился писать свои первые программы на JavaScript и испытывает трудности при поиске багов. Статья не столько, не про конкретные инструменты и вкладки в DevTools, сколько про то, о чём думать и куда смотреть при отладке. Дебаггинг (от англ. debugging), или отладка, — это процесс выявления ошибок в коде. Этот навык — первый шаг к самостоятельности разработчика, и его обязательно нужно освоить. Если вы уже начали программировать, то наверняка знаете, что написать полностью рабочий код с первой попытки — скорее исключение, чем правило, и что-то обязательно «сломается»: Код вообще не работает и выдаёт ошибки.  Ошибок не будет в консоли, но при этом программа работает некорректно. Стратегия отладки в обоих случаях примерно одинаковая, но с небольшими отличиями. Первый случай немного проще, поскольку консоль браузера может подсказать, в чём проблема, — поэтому давайте с него и начнем. Чаще всего код не работает из-за недопонимания между компьютером и автором кода, поэтому первоначальный чек-лист выглядит так:  Вспомнить, что мы хотели донести до компьютера (последовательность шагов в программе или в её части), — «ожидание». Проверить, в каком виде наша мысль в итоге дошла до компьютера и как он её понял, — «реальность». Сделать это мы можем только из первых уст — из инструментов разработчика.  Найти несоответствия между «ожиданием» и «реальностью». Советую во время разработки держать инструменты разработчика всегда включёнными — это помогает вовремя найти разницу между «ожиданиями» и «реальностью». Обычно я никогда не закрываю вкладку DevTools и обращаю внимание на вкладку Console (консоль) при каждом сохранении файла. Так проще отследить, после какого изменения код перестал работать.  Совет #1. Отредактировав файл, сразу же проверьте консоль на наличие ошибок. Это должно войти в привычку.  Если в консоли появилась ошибка, то вам повезло — у вас есть всё, чтобы решить проблему:  Сообщение об ошибке, описывающее её суть. Указание на место в коде, где ошибка возникла. Действия, которые нужно предпринять:  Внимательно вчитываемся в ошибку и дословно переводим, что в ней говорится. Смотрим источник ошибки. Если присмотреться, сообщение об ошибке содержит три названия файлов и номера их строк. Опытному разработчику достаточно одного взгляда на него, чтобы понять, в чём проблема. Давайте поймём, как это делать. Первое, на что смотрит разработчик при появлении ошибки в консоли, — название файла и номер строки. Если ошибка появилась в том файле, который мы только что редактировали, значит, она связана с последними изменениями. Это может быть опечатка, или, возможно, мы просто забыли передать аргумент в функцию. Ниже так называемый StackTrace — это своего рода лог, который показывает, через какие места в коде ошибка успела пройти до того, как сломать приложение: В StackTrace мы видим, что сначала ошибка возникла в функции greetingMessageCreator в файле index.js на строке 10 и в столбце 12, а затем перешла на строку 14 того же файла. В сообщении об ошибке раскрывается весь её путь до того, как она сломала приложение.  Ошибки в JS — отдельная большая тема. Но стоит запомнить: если есть функция, вызывающая ещё одну функцию, вызывающая ещё одну и выдающая ошибку, то, если эту ошибку не «отловить» и не обработать, она сломает все «родительские» функции. Это напоминает принцип домино: Но обычно для понимания проблемы достаточно первых ссылок на строки кода, в нашем случае — index.js:10 и at greetingMessageCreator (index.js:10:12).  Задача первого шага — понять, связана ли ошибка с последними изменениями в коде. Если да, то отладка сильно упрощается: остается лишь внимательно перепроверить последние написанные строки кода. Чаще всего дело в невнимательности.  Представим, что мы только что отредактировали файл index.js, и перейдем к шагу №2. Очень важно внимательно вчитаться в смысл ошибки и дословно перевести её. Если не понимаете, переведите в переводчике — в этом нет ничего зазорного.  Неперехваченная ошибка ссылки: mesage не определена В этом примере ошибка связана с тем, что некая переменная mesage не определена. А почему она, собственно, должна быть определена? Самое время заглянуть в наш код! В самом начале мы мельком проверили, не находится ли ошибка в том же файле, который мы только что редактировали. Выяснилось, что находится.  Кроме названия файла, в котором произошла ошибка, там указан и номер строки. Мы можем либо открыть этот файл и найти эту строку прямо в редакторе, либо просто кликнуть по названию файла и номеру строки справа.  Кликаем — открывается панель с исходным кодом и подчеркивается место с ошибкой: Если навести на крестик, выводится и сама ошибка:  Написано, что переменная mesage не объявлена, хотя на строку выше мы её объявили… Но интерпретатор все равно почему-то не может её найти.  Смотрим внимательнее и обнаруживаем опечатку:  Фух, всего лишь опечатка. А вначале выглядело как конец света! На самом деле подобные ошибки встречаются у новичков часто, и это вполне нормально: в голове еще нет прошивки по порядку просматривать все пункты, не вошло в привычку и проверять на опечатки каждый набранный символ.  Но нужно довести до автоматизма проверку, как же на самом деле компьютер понял то, что вы хотели ему донести.  Совет #2. Старайтесь проверять не только правильность своей логики, но и то, как это в итоге понял интерпретатор и где он запнулся. Узнать это можно только из первых уст: из инструментов разработчика. Рассмотрим шесть самых распространенных типов ошибок на примерах и по порядку разберем, что с ними делать. Uncaught ReferenceError: <какая-то переменная или функция> is not defined Uncaught ReferenceError: Cannot access <какая-то переменная или функция> before initialization Uncaught TypeError: Cannot read property <какое-то свойство> of undefined Uncaught SyntaxError: Unexpected identifier Uncaught TypeError: <что-то> is not a function Uncaught RangeError: Invalid array length Часто эта ошибка возникает, если мы где-то опечатались. В этом случае переменную невозможно найти. Пример: Если появилась ReferenceError, ищите опечатку. Reference — это ссылка. В примере выше интерпретатор пытается ссылаться на переменную mesage, не обнаруживает её и сообщает, что переменная mesage не объявлена: Проверить опечатки не мешает при любом типе ошибки — это самая частая причина их появления в консоли. Эта ошибка появляется, когда пытаемся прочесть какое-то свойство из объекта, но ссылаемся не на объект или не на тот объект. Пример: у нас есть объект, и у него есть свойства a, b, c, где c — это число. Я хочу вывести в консоль a, b, c. Редактор кода подсказывает, что есть методы, связанные с числом. Но я хочу вызвать d и по какой-то причине считаю, что c — тоже объект.  В результате выводится сообщение про undefined. Потому что внутри c не лежит никакого d, ведь с — это число. А если из undefined постараться что-то достать, появится ошибка TypeError. Чтобы разобраться с этой ошибкой, смотрим, на какой строке она возникла: строка 19. Находим 19 строку и видим, где именно возникла проблема. Нужно удостовериться, что мы не пытаемся читать свойства из не-обьектов. Эта ошибка возникает в том случае, если мы пытаемся использовать переменную раньше, чем она объявлена. В отличие от ошибок с “... is not defined”, данная ошибка нам даёт понять, что переменная объявлена, просто после того места, где она используется. Если нажать на строку, о которой говорится в ошибке, то откроется вкладка Sources с исходным кодом и будет подчёркнута строка с ошибкой:  Здесь уже несложно заметить, в чём дело: переменная объявлена на строке 19, а используется она на строке 17.  Синтаксическая ошибка. Все синтаксические ошибки обычно подсвечивает редактор. Нужно очень внимательно присмотреться к строке, на которую указывает ошибка. В первую очередь обращайте внимание на открывающие/закрывающие скобки, запятые, кавычки, двоеточия в названиях методов.  Дословный перевод: «что-то – НЕ функция». Эта ошибка возникает, когда мы что-то пытаемся вызвать как функцию с круглыми скобками, но при этом это «что-то» не является функцией. В большинстве случаев возникает при вызове метода объекта. Шаги решения:  Выясняем, что такое «что-то» и где оно находится. Идем на строку, о которой говорится в ошибке, в примере со скриншотом выше — это строка 15. Выясняем тип того, что вызывается на самом деле. У нас есть объект person, внутри которого есть объект actions с функциями. Однако, присмотревшись, можно заметить, что свойство sleep – это вовсе не функция, а просто строка, поэтому мы не можем её вызвать как функцию. Будьте аккуратны в таких местах! Их легко пропустить, особенно когда писал код всю ночь.  Эта ошибка про длину массива, и она попадается реже всего. Представим, что у нас есть массив под названием arr. Нужно указать длину массива, содержащего три пустых элемента. Попытаемся создать массив на 999 в степени 999 элементов. Ошибка возникнет, потому что это число слишком большое и упирается в бесконечность. RangeError — это ошибка, связанная с длиной массива. Также массив не может быть отрицательной длины.  Есть проблема сложнее. Приложение не падает с ошибкой и даже работает, но лишь частично и неправильно. Как в таком случае определить, где ошибка, если интерпретатор нам явно этого не подсказывает?  Нужно найти ответы на несколько вопросов: Какой фрагмент кода или функция отвечает за то, чтобы необходимый функционал работал? Какие переменные используются в данном фрагменте кода? А доходит ли вообще интерпретатор до выполнения необходимой строчки кода? Что, если необходимый код в принципе игнорируется?  Давайте разберем реальный кейс:  Это приложение с карточками о разных уголках нашей планеты. Функционал:  редактирование профиля пользователя; возможность поставить лайк карточке; возможность удалить карточку; возможность добавить новую карточку. В последнем пункте есть небольшая недоработка: когда мы добавляем новую карточку и вдруг хотим добавить ещё одну, форма остается заполненной, а должна очищаться:  Чтобы найти проблему в такой ситуации, нужно воспроизвести баг и вспомнить, какие шаги мы сделали для добавления карточки:  Открыли страницу; Нажали на кнопку +; Ввели данные; Нажали кнопку «Создать»; Снова нажали на кнопку +;  Баг воспроизвелся: наша форма осталась заполненной, а не пустой. Шаги 1, 2, 3 и 4 сработали как нужно — в общем-то, проблема изначально была и не в них. А вот на пятом шаге мы уже увидели проблему. Вполне вероятно, проблема была уже на предыдущем шаге, просто увидеть мы её смогли только на этом, поэтому давайте также подозревать и шаг четыре. Искать проблему нужно там, но перед этим ещё раз опишем последовательность более низкоуровнево, ближе к тому, как это видит интерпретатор JavaScript:  4. Нажали кнопку «создать». 4.1. Браузер находит в HTML форму (то, что мы писали в .querySelector). 4.2. Браузер находит в JS обработчик события submit (то, что мы писали в .addEventListener). 4.3. Вызывается обработчик события submit, в котором написана логика закрытия popup. 4.4. Закрывается popup. 5. Снова нажимаем на кнопку +.  5.1. Браузер находит в HTML форму (то, что мы писали в .querySelector). 5.2. Браузер находит в JS обработчик события submit (то, что мы писали в .addEventListener). 5.3. Вызывается обработчик события submit, в котором написана логика закрытия popup. 5.4. ← наш баг, вероятно, где-то тут! Благодаря этому у нас есть больше полезной информации о том, где может прятаться потенциальный баг. Нам нужно найти код, который находит кнопки и заводит на них обработчики, а также найти код, который закрывает всплывающее окно. Нужно очень внимательно посмотреть на исходный код, а также воспользоваться DevTools, чтобы узнать селекторы для кнопок. Для этого нажмём по странице правой кнопкой мыши и выберем опцию ‘Inspect’:  В появившемся окне нажимаем на иконку курсора в левом верхнем углу:  Наводим курсор на нужный элемент и нажимаем на него:  Данный элемент отображается в панели Elements. Теперь мы знаем, что у него за классы, и нам есть за что уцепиться в поисках по исходному коду. Давайте скопируем класс и поищем его в исходном коде по всему проекту. Для Visual Studio Code это комбинация Ctrl + Shift + F (Command + Shift + F для тех, кто на MacOS):  Перед нами — список файлов, где упоминается данный селектор. Мы знаем, что баг явно не в HTML и CSS, а где-то в JS. Больше всего похоже на правду упоминание в index.js, поэтому давайте посмотрим на него:  Данный селектор используется в двух местах: в одном что-то про редактирование, в другом — про добавление. Наш баг связан именно с добавлением карточки. Переменная, которая напрямую связана с проблемой, — cardForm. Теперь нужно выяснить, где используется конкретно эта переменная. Для этого можно воспользоваться поиском по файлу:  Находим обработчик события submit — и это действительно выглядит как нечто похожее на правду. Давайте взглянем, что внутри обработчика:  Прекрасно! Мы нашли кое-какую зацепку, и у нас есть основания полагать, что ошибка может быть в этом месте. Какой фрагмент кода или функция отвечает за то, чтобы необходимый функционал работал? Какие переменные используются в данном фрагменте кода? А доходит ли вообще интерпретатор до выполнения необходимой строчки кода? Что, если необходимый код в принципе игнорируется? Или вдруг мы дебажим не тот код? Теперь нужно подтвердить то, что ошибка именно здесь. Для этого нужно добавить что-то очень заметное, что произойдёт при вызове данной функции, — тогда при попытке воспроизвести баг мы окончательно будем уверены, что работаем именно с тем кодом, который влияет на проблему. Это очень важное место: порой случается, что дебажишь целый день, а потом выясняется, что дебажил совсем не в том месте. Давайте сразу это исключим и добьёмся того, чтобы при воспроизведении бага выполнился тот код, который мы добавим для проверки.  Для проверки того, что мы дебажим нужное место, мы можем использовать console.log, однако часто бывает утомительно и не очень удобно искать среди множества логов именно то, что нам нужно. Есть инструмент круче — debugger. Давайте попробуем написать ключевое слово внутрь данной функции, чтобы убедиться, доходит ли вообще выполнение кода до неё. Теперь будем пытаться воспроизводить баг. Нам нужно, чтобы при открытии формы мы убедились. Очень важно: не забудьте открыть DevTools! Ключевое слово debugger остановит выполнение кода на нужной строчке и даст возможность изучить состояние программы в этот момент. Это довольно занимательно, обязательно попробуйте ставить debugger в процессе поисков багов! Только не забудьте удалять его перед тем, как публиковать код, иначе пользователи вашего приложения могут очень удивиться внезапной остановке сайта.  Итак, поскольку при воспроизведении бага код остановился, то теперь мы точно уверены, что проблема именно в этом месте кода. Осталось только исправить его.  Какой фрагмент кода или функция отвечает за то, чтобы необходимый функционал работал? Какие переменные используются в данном фрагменте кода? А доходит ли вообще интерпретатор до выполнения необходимой строчки кода? Что если необходимый код в принципе игнорируется? Или вдруг мы дебажим не тот код? Но что нужно, чтобы у формы очистились поля?  Найти элемент form; Вызвать метод form.reset(). В коде на скриншоте выше мы не видим этих действий. Может, они есть внутри одной из вложенных функций? К сожалению, и тут их нет. Приходим к выводу, что это вовсе не баг, а просто недоработка: мы забыли дописать эту логику. Но зато теперь знаем, где именно. Давайте допишем одну строчку, чтобы поправить это место:  Порой бывают не просто недоработки (отсутствие кода) как в примере выше, а именно баги — некорректное поведение написанного кода. Давайте рассмотрим пример. В приложении есть валидация. Если хотя бы одно из полей формы введено неправильно и не проходит валидацию, то кнопка «Создать» должна блокироваться.  На видео видно, что при вводе некорректного URL это делает одно из полей формы невалидным. Тем не менее сама кнопка «Создать» работает, и карточка создается с некорректной ссылкой.  По описанному выше в статье опыту — находим связанный с проблемой код. Играемся с поиском, ищем что-то связанное с необходимыми селекторами и в итоге находим:  Часто ошибки кроются внутри условных конструкций if/else. У нас есть функция, где в конструкции if условие на самом деле вычисляется. Мы полагаем, что баг находится где-то здесь. Как быстрее всего его можно отловить?  Сначала нам нужно убедиться, что это именно то место. Поставим debugger и попробуем воспроизвести баг. Если при повторении действий код остановится, то значит, это то самое место. Ставим debugger там, где вызывается условие (перед строкой 47 на скриншоте):  А ещё поставим там, где вычисляется условие, — внутри самой функции hasInvalidInput: Теперь давайте воспроизведем код и посмотрим состояние приложения, когда интерпретатор доходит до выполнения этих строчек (не забываем открыть DevTools):  Также мы можем нажимать на строчки кода в DevTools во время дебага, что тоже остановит выполнение кода на этих строках. Итак, сейчас мы смогли проследить:  В каких ветках условий и при каких действиях останавливается код. Это действительно тот код, который надо дебажить, поскольку дебагер остановился. Условие работает не совсем корректно: оно срабатывает, только когда в обоих input есть ошибка, а когда ошибка есть только в одном из них — не срабатывает.  Что проблема в том, как вычисляется условие, — в функции hasInvalidInput, а не где-либо ещё. Давайте посмотрим, что на самом деле возвращает функция hasInvalidInput. Для этого сохраним результат вычисления в переменную, выведем её в консоль и возвратим. А также на всякий случай поставим debugger:  В остальных местах debugger теперь можно удалить, чтобы не мешал. Действительно: result становится равен true только тогда, когда оба input невалидны. А когда валиден лишь один из них, то result равен false. Поскольку result вычисляется стандартными функциями, баг скрывается на этом уровне. Смотрим внимательнее и обнаруживаем ошибку: вместо every на самом деле нужно было написать some:  Some (означает «какие-либо») споткнется при первом же true при обходе массива. Every (означает «все») споткнется лишь тогда, когда при обходе массива функция вернет true ко всем элементам.  Вывод: баг может возникнуть как в функции, которую мы подозреваем, так и во вложенных. Нам важно найти его истинный источник. Чтобы найти ошибку в исполнении кода, нужно думать как исполнитель кода — то есть как интерпретатор. Точнее всего истинное восприятие кода нам может подсказать ключевое слово debugger — оно остановит наш код и покажет процесс исполнения глазами интерпретатора. Лично я использую ключевое слово debugger в своей работе каждый день и считаю, что его много не бывает, поэтому не стесняюсь его писать во все подозрительные места до тех пор, пока не найду баг. Главное — не забыть потом его удалить.  Совет #3. Интерпретатор JS может мыслить иначе, чем вы задумывали. Почаще смотрите на код глазами интерпретатора: в этом помогут debugger и консоль.  Есть сложности, с которыми сталкиваются все начинающие разработчики, делающие первые шаги в дебагинге. Я часто замечаю, что студенты могут написать много кода — несколько функций — и за 30 строчек ни разу не посмотреть, как этот код в итоге работает. В этом случае, когда код запускается и падает, сложнее понять, что именно привело к ошибке. Придется тратить время и когнитивный ресурс на восстановление контекста в голове. Да, это довольно быстро, но 10 таких ошибок в день съедают немало времени и сил.  Я советую прямо в процессе написания кода, каждый раз когда вы объявляете новую переменную и сохраняете в неё значение, сразу же выводить её в консоль и убеждаться, что все правильно. То есть не после того, как написали целиком функцию, а прямо после объявления каждой переменной. Так вы сможете быстрее находить ошибки и сразу же их фиксить. Выводы в консоль потом можно удалить, когда они станут не нужны. Даже если функция кажется несложной, всегда можно забыть передать в нее аргумент при вызове или опечататься/задуматься и передать не то, что нужно. Намного проще это выявить в самом начале, чем искать потом. Кстати, обратите внимание: я вывожу в консоль значения в фигурных скобках. На самом деле создастся объект, и в консоль он выведется в более удобном виде — сразу с подписью, что за переменную мы выводим:  Очень советую так делать, потому что, когда логов становится много, ориентироваться в них сложно.  В программировании никто не накажет за то, что вы почаще проверяете, что у вас получается. Написали пять строчек какого-то микрофункционала — запустили приложение, посмотрели, что оно не падает с ошибками, и только после этого пишете дальше. Потом, с опытом, когда код начнет выполняться в голове вместо браузера, не нужно будет так часто сверяться, но в начале пути такой способ позволяет избежать многих ошибок. Кроме консоли можно использовать debugger — порой это удобнее.  Совет #4. Ешьте слона по частям. Проверяйте, что находится в переменной, сразу же после её объявления. Часто из-за опечатки в переменной может оказаться не то значение. Лучше узнать это до того, как переменная повлияет на результат последующего кода.  Уделять внимание наименованиям нужно для того, чтобы было проще не только отлаживать код, но и писать его в процессе. Есть разные договоренности о том, как называть переменные и функции, от команды к команде они могут отличаться. Но общепринято соблюдать такие правила:  Переменная — это существительное в единственном числе. Например, value, popup, image, button.  Переменная, в которой лежит массив, — это существительное во множественном числе. Это очень важно. Например, недавно искали баг в таком коде:  Была следующая ошибка:  Вывод в массив вроде показывает что-то связанное с нашим DOM-элементом, при наведении даже подсвечивается в браузере:  Если внимательно присмотреться, то эта переменная хранит в себе множество значений, а не одно. Посмотрим на объявление этой переменной:  Она названа в единственном числе, хотя вызывается метод document.querySelectorAll, который возвращает псевдомассив. Если бы переменная была названа overlays (во множественном числе), то мы бы сэкономили время на понимание происхождения этой ошибки.  Название функции должно описывать действие, то есть быть глаголом. Давайте сравним разные названия функций: При правильном наименовании функций можно извлечь больше полезной информации. Давайте сравним:  Грамотные наименования сэкономят много времени тому, кто будет читать ваш код, и чаще всего это будете вы сами.  В идеале код должен читаться почти как стихотворение: чтобы получить представление о происходящем, мозг должен напрягаться как можно меньше. Да и код намного проще пишется, когда вы пишете дословно то, что имеете в виду.  Совет #5. Уделяйте внимание наименованиям. Это очень важно! Старайтесь почаще перечитывать код и воспринимать написанное дословно: так вы поймете, насколько логично названы переменные. Если есть сложности с английским языком, не стесняйтесь переводить дословно смысл переменной с русского на английский через переводчик. Если вы видите ошибку — не стесняйтесь её, не бойтесь. На самом деле она — ваш друг. В ней чаще всего написано, что именно не так. Не стесняйтесь пользоваться переводчиком, если уровня английского пока не хватает. Намного хуже, когда есть баг, а ошибки нет.  Конечно, с повышением уровня сложности будут возникать ошибки, которые вообще ни о чем вам не скажут. Но на начальном уровне ошибка в большинстве случаев объясняет практически всё. Совет #6. Полюбите ошибки в консоли! Ошибки — ваши помощники, а не враги. Гораздо хуже, когда ошибки нет, а код не работает. Держите инструменты разработчика включенными, они помогут разобраться.  Самых распространённых ошибок — не так много, с ними легко разобраться. Если ошибки нет, но код не работает, ищем строчку, на которой всё ломается, смотрим, доходит ли интепретатор до нужной строчки. Когда пишете код, его необходимо время от времени запускать и проверять его работоспособность. Нельзя писать большие куски кода без проверок. Переменные и функции нужно называть понятно, так, чтобы вы потом при поиске ошибки смогли прочитать свой код и разобраться в нём.  Software Developer