Вчера всё работало, а сегодня не работает / Код не работает как задумано
или
Debugging (Отладка)
В чем заключается процесс отладки? Что это такое?
Процесс отладки состоит в том, что мы останавливаем выполнения скрипта в любом месте, смотрим, что находится в переменных, в функциях, анализируем и переходим в другие места; ищем те места, где поведение отклоняется от правильного.
Будет рассмотрен пример с Сhrome, но отладить код можно и в любом другом браузере и даже в IDE.
Открываем инструменты разработчика. Обычно они открывается по кнопке F12 или в меню Инструменты → Инструменты Разработчика. Выбираем вкладку Sources
Цифрами обозначены:
- Иерархия файлов, подключенных к странице (js, css и другие). Здесь можно выбрать любой скрипт для отладки.
- Сам код.
- Дополнительные функции для контроля.
В секции №2 в левой части на любой строке можно кликнуть ЛКМ, тем самым поставив точку останова (breakpoint — брейкпойнт). Это то место, где отладчик автоматически остановит выполнение JavaScript, как только до него дойдёт. Количество breakpoint’ов не ограничено. Можно ставить везде и много. На изображении выше отмечен зеленым цветом.
В остановленном коде можно посмотреть текущие значения переменных, выполнять различные команды и др.
А во вкладке Breakpoints можно:
- На время выключить брейкпойнт(ы)
- Удалить брейкпойнт(ы), если не нужен
- Быстро перейти на место кода, где стоит брейкпойнт кликнув на текст.
Запускаем отладку
В данном случае, т.к. функция выполняется сразу при загрузке страницы, то для активации отладчика достаточно её перезагрузить. В ином случае, для активации требуется исполнить действие, при котором произойдет исполнение нужного участка кода (клик на кнопку, заполнение инпута данными, движение мыши и другие действия)
В данном случае после перезагрузки страницы выполнение «заморозится» на 4 строке:
-
Вкладка Watch — показывает текущие значения любых переменных и выражений. В любой момент здесь можно нажать на
+, вписать имя любой переменной и посмотреть её значение в реальном времени. Напримерdataилиnums[0], а можно иnums[i]иitem.test.data.name[5].info[key[1]]и т.д. -
Вкладка Call Stack — стэк вызовов, все вложенные вызовы, которые привели к текущему месту кода. На данный момент отладчик стоит в функции
getSum, 4 строка. -
Вкладка Scope Variables — переменные. На текущий момент строки ниже номера 4 ещё не выполнилась, поэтому
sumиoutputравныundefined.
В Local показываются переменные функции: объявленные через var и параметры.
В Global – глобальные переменные и функции.
Процесс
Для самого процесса используются элементы управления (см. изображение выше, выделено зеленым прямоугольником)






…
Итак, в текущем коде видно значение входного параметра:
data = "23 24 11 18"— строка с данными через пробелnums = (4) ["23", "24", "11", "18"]— массив, который получился из входной переменной.
Если нажмем F10 2 раза, то окажемся на строке 7; во вкладках Watch, Scope > Local и в самой странице с кодом увидим, что переменная sum была инициализирована и значение равно 0.
Если теперь нажмем F11, то попадем внутрь функции-замыкания nums.forEach
Нажимая теперь F10 пока не окончится цикл, можно будет наблюдать, как на каждой итерации цикла постоянно изменяются значения num и sum. Тем самым мы можем проследить шаг за шагом весь процесс изменения любых переменных и значений на любом этапе, который интересует.
Дальнейшие нажатия F10 переместит линию кода на строки 11, 12 и, наконец, 15.
Дополнительно
-
Остановку можно инициировать принудительно без всяких точек останова, если непосредственно в коде написать ключевое слово
debugger:function getSum(data) { ... debugger; // <-- отладчик остановится тут ... } -
Если нажать ПКМ на строке с брейкпойнтом, то это позволит еще более тонко настроить условие, при котором на данной отметке надо остановиться.
В функции выше, например, нужно остановиться только когдаsumпревысит значение 20.Это удобно, если останов нужен только при определённом значении, а не всегда (особенно в случае с циклами).
Больше информации о возможностях инструментов например Chrome — можно прочитать здесь
Дополнительно 2
Принудительную отладку можно инициировать событием, происходящим на странице/элементах. Это полезно, если не знаешь где обрабатывающая функция находится.
Пример для Chrome:
Нажимаем F12, заходим на вкладку Sources и в функциях контроля видим вкладку Event Listener Breakpoints, в которой можно назначить в качестве триггера любые события, при которых исполнение скрипта будет остановлено.
На изображении ниже выбран пункт на событие onchange элементов.
Для Firefox:
Если функция инлайновая, например
<input type="checkbox" onchange="testFunction(this);" />
то можно зайти в Инспектор, найти тот самый элемент, в котором прописано событие и обнаружить рядом значок em:
Кликнув на него, как утверждает developer.mozilla.org/ru/docs можно увидеть строчки:
где можно увидеть события, навешанные на элемент, скрипт, строку, возможность нажать на паузу и т.д.
В других случаях, а также если кнопка паузы не обнаружена, то на вкладке Debugger(отладчик) надо найти стрелку, при наведении на которую будет написано «Events». Там должно быть событие выделенного элемента.
А вот таких полезных вкладок как у Chrome к сожалению у Firefox там нет.
Время на прочтение
15 мин
Количество просмотров 56K
Содержание
- Введение
- Величины, типы и операторы
- Структура программ
- Функции
- Структуры данных: объекты и массивы
- Функции высшего порядка
- Тайная жизнь объектов
- Проект: электронная жизнь
- Поиск и обработка ошибок
- Регулярные выражения
- Модули
- Проект: язык программирования
- JavaScript и браузер
- Document Object Model
- Обработка событий
- Проект: игра-платформер
- Рисование на холсте
- HTTP
- Формы и поля форм
- Проект: Paint
- Node.js
- Проект: веб-сайт по обмену опытом
- Песочница для кода
Отладка изначально вдвое сложнее написания кода. Поэтому, если вы пишете код настолько заумный, насколько можете, то по определению вы не способны отлаживать его.
Брайан Керниган и П.Ж.Плауэр, «Основы программного стиля»
Юан-Ма написал небольшую программу, использующую много глобальных переменных и ужасных хаков. Ученик, читая программу, спросил его: «Вы предупреждали нас о подобных техниках, но при этом я нахожу их в вашей же программе. Как это возможно?» Мастер ответил: «Не нужно бежать за поливальным шлангом, если дом не горит».
Мастер Юан-Ма, «Книга программирования».
Программа – это кристаллизованная мысль. Иногда мысли путаются. Иногда при превращении мыслей в программу в код вкрадываются ошибки. В обоих случаях получается повреждённая программа.
Недостатки в программах обычно называют ошибками. Это могут быть ошибки программиста или проблемы в системах, с которыми программа взаимодействует. Некоторые ошибки очевидны, другие – трудноуловимы, и могут скрываться в системах годами.
Часто проблема возникает в тех ситуациях, возникновение которых программист изначально не предвидел. Иногда этих ситуаций нельзя избежать. Когда пользователя просят ввести его возраст, а он вводит «апельсин», это ставит программу в непростую ситуацию. Эти ситуации необходимо предвидеть и как-то обрабатывать.
Ошибки программистов
В случае ошибок программистов наша цель ясна. Нам надо найти их и исправить. Таковые ошибки варьируются от простых опечаток, на которые компьютер пожалуется сразу же, как только увидит программу, до скрытых ошибок в нашем понимании того, как программа работает, которые приводят к неправильным результатам в особых случаях. Ошибки последнего рода можно искать неделями.
Разные языки по-разному могут помогать вам в поиске ошибок. К сожалению, JavaScript находится на конце этой шкалы, обозначенном как «вообще почти не помогает». Некоторым языкам надо точно знать типы всех переменных и выражений ещё до запуска программы, и они сразу сообщат вам, если типы использованы некорректно. JavaScript рассматривает типы только во время исполнения программ, и даже тогда он разрешает делать не очень осмысленные вещи без всяких жалоб, например
x = true * "обезьяна"
На некоторые вещи JavaScript всё-таки жалуется. Написание синтаксически неправильной программы сразу вызовет ошибку. Другие ошибки, например вызов чего-либо, не являющегося функцией, или обращение к свойству неопределённой переменной, возникнут при выполнении программы, когда она сталкивается с такой бессмысленной ситуацией.
Но часто ваши бессмысленные вычисления просто породят NaN (not a number) или undefined. Программа радостно продолжит, будучи уверенной в том, что она делает что-то осмысленное. Ошибка проявит себя позже, когда такое фиктивное значение уже пройдёт через несколько функций. Она может вообще не вызвать сообщение об ошибке, а просто привести к неправильному результату выполнения. Поиск источника таких проблем – сложная задача.
Процесс поиска ошибок (bugs) в программах называется отладкой (debugging).
Строгий режим (strict mode)
JavaScript можно заставить быть построже, переведя его в строгий режим. Для этого наверху файла или тела функции пишется «use strict». Пример:
function canYouSpotTheProblem() {
"use strict";
for (counter = 0; counter < 10; counter++)
console.log("Всё будет офигенно");
}
canYouSpotTheProblem();
// → ReferenceError: counter is not defined
Обычно, когда ты забываешь написать var перед переменной, как в примере перед counter, JavaScript по-тихому создаёт глобальную переменную и использует её. В строгом режиме выдаётся ошибка. Это очень удобно. Однако, ошибка не выдаётся, когда глобальная переменная уже существует – только тогда, когда присваивание создаёт новую переменную.
Ещё одно изменение – привязка this содержит undefined в тех функциях, которые вызывали не как методы. Когда мы вызываем функцию не в строгом режиме, this ссылается на объект глобальной области видимости. Поэтому если вы случайно неправильно вызовете метод в строгом режиме, JavaScript выдаст ошибку, если попытается прочесть что-то из this, а не будет радостно работать с глобальным объектом.
К примеру, рассмотрим код, вызывающий конструктор без ключевого слова new, в случае чего this не будет ссылаться на создаваемый объект.
function Person(name) { this.name = name; }
var ferdinand = Person("Евлампий"); // ой-вэй
console.log(name);
// → Евлампий
Некорректный вызов Person успешно происходит, но возвращается как undefined и создаёт глобальную переменную name. В строгом режиме всё по-другому:
"use strict";
function Person(name) { this.name = name; }
// Опаньки, мы ж забыли 'new'
var ferdinand = Person("Евлампий ");
// → TypeError: Cannot set property 'name' of undefined
Нам сразу сообщают об ошибке. Очень удобно.
Строгий режим умеет ещё кое-что. Он запрещает вызывать функцию с несколькими параметрами с одним и тем же именем, и удаляет некоторые потенциально проблемные свойства языка (например, инструкцию with, которая настолько ужасна, что даже не обсуждается в этой книге).
Короче говоря, надпись «use strict» перед текстом программы редко причиняет проблемы, зато помогает вам видеть их.
Тестирование
Если язык не собирается помогать нам в поиске ошибок, приходится искать их сложным способом: запуская программу и наблюдая, делает ли она что-то так, как надо.
Делать это вручную, снова и снова – верный способ сойти с ума. К счастью, часто возможно написать другую программу, которая автоматизирует проверку вашей основной программы.
Для примера вновь обратимся к типу Vector.
function Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
Мы напишем программу, которая проверит, что наша реализация Vector работает, как нужно. Затем после каждого изменения реализации мы будем запускать проверочную программу, чтобы убедиться, что мы ничего не сломали. Когда мы добавим функциональности (к примеру, новый метод) к типу Vector, мы добавим проверки к этой новой функциональности.
function testVector() {
var p1 = new Vector(10, 20);
var p2 = new Vector(-10, 5);
var p3 = p1.plus(p2);
if (p1.x !== 10) return "облом: x property";
if (p1.y !== 20) return "облом: y property";
if (p2.x !== -10) return "облом: negative x property";
if (p3.x !== 0) return "облом: x from plus";
if (p3.y !== 25) return "облом: y from plus";
return "всё пучком";
}
console.log(testVector());
// → всё пучком
Написание таких проверок приводит к появлению повторяющегося кода. К счастью, есть программные продукты, помогающие писать наборы проверок при помощи специального языка, приспособленного именно для написания проверок. Их называют testing frameworks.
Отладка (debugging)
Когда вы заметили проблему в программе,– она ведёт себя неправильно и выдаёт ошибки,- самое время выяснить, в чём проблема.
Иногда это очевидно. Сообщение об ошибке наводит вас на конкретную строку программы, и если вы прочтёте описание ошибки и эту строку, вы часто сможете найти проблему.
Но не всегда. Иногда строчка, приводящая к ошибке, просто оказывается первым местом, где некорректное значение, полученное где-то ещё, используется неправильно. Иногда вообще нет сообщения об ошибке – есть просто неверный результат. Если вы делали упражнения из предыдущих глав, вы наверняка попадали в такие ситуации.
Следующий пример пробует преобразовать целое число в строку в заданной системы счисления (десятичной, двоичной или другой), отнимая последнюю ((цифру)) и совершая деление, чтобы избавиться от этой цифры.
function numberToString(n, base) {
var result = "", sign = "";
if (n < 0) {
sign = "-";
n = -n;
}
do {
result = String(n % base) + result;
n /= base;
} while (n > 0);
return sign + result;
}
console.log(numberToString(13, 10));
// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
Даже если вы нашли проблему – притворитесь, что ещё не нашли. Мы знаем, что программа сбоит, и нам нужно узнать, почему.
Здесь вам надо преодолеть желание начать вносить случайные изменения в код. Вместо этого подумайте. Проанализируйте результат и придумайте теорию, по которой это происходит. Проведите дополнительные наблюдения для проверки теории – а если теории нет, проведите наблюдения, которые бы помогли вам изобрести её.
Размещение нескольких вызовов console.log в стратегических местах – хороший способ получить дополнительную информацию о том, что программа делает. В нашем случае нам нужно, чтобы n принимала значения 13, 1, затем 0. Давайте выведем значения в начале цикла:
13
1.3
0.13
0.013
…
1.5e-323
Н-да. Деление 13 на 10 выдаёт не целое число. Вместо n /= base нам нужно n = Math.floor(n / base), тогда число будет корректно «сдвинуто» вправо.
Кроме console.log можно воспользоваться отладчиком в браузере. Современные браузеры умеют ставить точку остановки на выбранной строчке кода. Это приведёт к приостановке выполнения программы каждый раз, когда будет достигнута выбранная строчка, и тогда вы сможете просмотреть содержимое переменных. Не буду подробно расписывать процесс, поскольку у разных браузеров он организован по-разному – поищите в вашем браузере “developer tools”, инструменты разработчика. Ещё один способ установить точку остановки – включить в код инструкцию для отладчика, состоящую из ключевого слова debugger. Если инструменты разработчика активны, исполнение программы будет приостановлено на этой инструкции, и вы сможете изучить состояние программы.
Распространение ошибок
К сожалению, программист может предотвратить появление не всех проблем. Если ваша программа общается с внешним миром, она может получить неправильные входные данные, или же системы, с которыми она пытается взаимодействовать, окажутся сломанными или недоступными.
Простые программы, или программы, работающие под вашим надзором, могут просто «сдаваться» в такой момент. Вы можете изучить проблему и попробовать снова. «Настоящие» приложения не должны просто «падать». Иногда приходится принимать неправильные входные данные и как-то с ними работать. В других случаях, нужно сообщить пользователю, что что-то пошло не так – и потом уже сдаваться. В любом случае программа должна что-то сделать в ответ на возникновение проблемы.
Допустим, у вас есть функция promptInteger, которая запрашивает целое число и возвращает его. Что она должна сделать, если пользователь введёт «апельсин»?
Один из вариантов – вернуть особое значение. Обычно для этих целей используют null и undefined.
function promptNumber(question) {
var result = Number(prompt(question, ""));
if (isNaN(result)) return null;
else return result;
}
console.log(promptNumber("Сколько пальцев видите?"));
Это надёжная стратегия. Теперь любой код, вызывающий promptNumber, должен проверять, было ли возвращено число, и если нет, как-то выйти из ситуации – спросить снова, или задать значение по-умолчанию. Или вернуть специальное значение уже тому, кто его вызвал, сообщая о неудаче.
Во многих таких случаях, когда ошибки возникают часто и вызывающий функцию код должен принимать их во внимание, совершенно допустимо возвращать специальное значение как индикатор ошибки. Но есть и минусы. Во-первых, что, если функция и так может вернуть любой тип значения? Для неё сложно найти специальное значение, которое будет отличаться от допустимого результата.
Вторая проблема – работа со специальными значениями может замусорить код. Если функция promptNumber вызывается 10 раз, то надо 10 раз проверить, не вернула ли она null. Если реакция на null заключается в возврате null на уровень выше, тогда там, где вызывался этот код, тоже нужно встраивать проверку на null, и так далее.
Исключения
Когда функция не может работать нормально, мы бы хотели остановить работу и перепрыгнуть туда, где такая ошибка может быть обработана. Этим занимается обработка исключений.
Код, встретивший проблему в момент выполнения, может поднять (или выкинуть) исключение (raise exception, throw exception), которое представляет из себя некое значение. Возврат исключения напоминает некий «прокачанный» возврат из функции – он выпрыгивает не только из самой функции, но и из всех вызывавших её функций, до того места, с которого началось выполнение. Это называется развёртыванием стека (unwinding the stack). Может быть, вы помните стек функций из главы 3… Исключение быстро проматывает стек вниз, выкидывая все контексты вызовов, которые встречает.
Если бы исключения сразу доходили до самого низа стека, пользы от них было бы немного. Они бы просто предоставляли интересный способ взорвать программу. Их сила в том, что на их пути в стеке можно поставить «препятствия», которые будут ловить исключения, мчащиеся по стеку. И тогда с этим можно сделать что-то полезное, после чего программа продолжает выполняться с той точки, где было поймано исключение.
Пример:
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new Error("Недопустимое направление: " + result);
}
function look() {
if (promptDirection("Куда?") == "L")
return "дом";
else
return "двоих разъярённых медведей";
}
try {
console.log("Вы видите", look());
} catch (error) {
console.log("Что-то не так: " + error);
}
Ключевое слово throw используется для выбрасывания исключения. Ловлей занимается кусок кода, обёрнутый в блок try, за которым следует catch. Когда код в блоке try выкидывает исключение, выполняется блок catch. Переменная, указанная в скобках, будет привязана к значению исключения. После завершения выполнения блока catch, или же если блок try выполняется без проблем, выполнение переходит к коду, лежащему после инструкции try/catch.
В данном случае для создания исключения мы использовали конструктор Error. Это стандартный конструктор, создающий объект со свойством message. В современных окружениях JavaScript экземпляры этого конструктора также собирают информацию о стеке вызовов, который был накоплен в момент выкидывания исключения – так называемое отслеживание стека (stack trace). Эта информация сохраняется в свойстве stack, и может помочь при разборе проблемы – она сообщает, в какой функции случилась проблема и какие другие функции привели к данному вызову.
Обратите внимание, что функция look полностью игнорирует возможность возникновения проблем в promptDirection. Это преимущество исключений – код, обрабатывающий ошибки, нужен только в том месте, где происходит ошибка, и там, где она обрабатывается. Промежуточные функции просто не обращают на это внимания.
Ну, почти.
Подчищаем за исключениями
Представьте следующую ситуацию: функция withContext желает удостовериться, что во время её выполнения переменная верхнего уровня context получит специальное значение контекста. После завершения выполнения, она восстанавливает её старое значение.
var context = null;
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
var result = body();
context = oldContext;
return result;
}
Что, если функция body выбросит исключение? В таком случае вызов withContext будет выброшен исключением из стека, и переменной context никогда не будет возвращено первоначальное значение.
Но у инструкции try есть ещё одна особенность. За ней может следовать блок finally, либо вместо catch, либо вместе с catch. Блок finally означает «что бы ни произошло, выполнить код в любом случае после выполнения блока try». Если функции надо что-то подчистить, то подчищающий код нужно включать в блок finally.
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
try {
return body();
} finally {
context = oldContext;
}
}
Заметьте, что нам больше не нужно сохранять результат вызова body в отдельной переменной, чтобы вернуть его. Даже если мы возвращаемся из блока try, блок finally всё равно будет выполнен. Теперь мы можем безопасно сделать так:
try {
withContext(5, function() {
if (context < 10)
throw new Error("Контекст слишком мал!");
});
} catch (e) {
console.log("Игнорируем: " + e);
}
// → Игнорируем: Error: Контекст слишком мал!
console.log(context);
// → null
Несмотря на то, что вызываемая из withContext функция «сломалась», сам по себе withContext по-прежнему подчищает значение переменной context.
Выборочный отлов исключений
Когда исключение доходит до низа стека и его никто не поймал — его обрабатывает окружение. Как именно – зависит от конкретного окружения. В браузерах описание ошибки выдаётся в консоль (она обычно доступна в меню «Инструменты» или «Разработка»).
Если речь идёт об ошибках или проблемах, которые программа не может обработать в принципе, допустимо просто пропустить такую ошибку. Необработанное исключение – разумный способ сообщить о проблеме в программе, и консоль в современных браузерах выдаст вам необходимую информацию о том, какие вызовы функций были в стеке в момент возникновения проблемы.
Если возникновение проблемы предсказуемо, программа не должна падать с необработанным исключением — это не очень дружественно по отношению к пользователю.
Недопустимое использование языка – ссылки на несуществующую переменную, запрос свойств у переменной, равной null, или вызов чего-то, что не является функцией, тоже приводит к выбрасыванию исключений. Такие исключения можно отлавливать точно так же, как свои собственные.
При входе в блок catch мы знаем только, что что-то внутри блока try привело к исключению. Мы не знаем, что именно, и какое исключение произошло.
JavaScript (что является вопиющим упущением) не предоставляет непосредственной поддержки выборочного отлова исключений: либо ловим все, либо никакие. Из-за этого люди часто предполагают, что случившееся исключение – именно то, ради которого и писался блок catch.
Но может быть и по-другому. Нарушение произошло где-то ещё, или в программу вкралась ошибка. Вот пример, где мы пробуем вызывать promptDirection до тех пор, пока не получим допустимый ответ:
for (;;) {
try {
var dir = promtDirection("Куда?"); // ← опечатка!
console.log("Ваш выбор ", dir);
break;
} catch (e) {
console.log("Недопустимое направление. Попробуйте ещё раз.");
}
}
Конструкция for (;;) – способ устроить бесконечный цикл. Мы вываливаемся из него, только когда получаем допустимое направление. Но мы неправильно написали название promptDirection, что приводит к ошибке “undefined variable”. А так как блок catch игнорирует значение исключения e, предполагая, что он разбирается с другой проблемой, он считает, что выброшенное исключение является результатом неправильных входных данных. Это приводит к бесконечному циклу и скрывает полезное сообщение об ошибке насчёт неправильного имени переменной.
Как правило, не стоит так ловить исключения, если только у вас нет цели перенаправить их куда-либо – к примеру, по сети, чтобы сообщить другой системе о падении нашей программы. И даже тогда внимательно смотрите, не будет ли скрыта важная информация.
Значит, нам надо поймать определённое исключение. Мы можем в блоке catch проверять, является ли случившееся исключение интересующим нас исключением, а в противном случае заново выбрасывать его. Но как нам распознать исключение?
Конечно, мы могли бы сравнить свойство message с сообщением об ошибке, которую мы ждём. Но это ненадёжный способ писать код – использовать информацию, предназначающуюся для человека (сообщение), чтобы принять программное решение. Как только кто-нибудь поменяет или переведёт это сообщение, код перестанет работать.
Давайте лучше определим новый тип ошибки и используем instanceof для его распознавания.
function InputError(message) {
this.message = message;
this.stack = (new Error()).stack;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";
Прототип наследуется от Error.prototype, поэтому instanceof Error тоже будет выполняться для объектов типа InputError. И ему назначено свойство name, как и другим стандартным типам ошибок (Error, SyntaxError, ReferenceError, и т.п.)
Присвоение свойству stack пытается передать этому объекту отслеживание стека, на тех платформах, которые это поддерживают, путём создания объекта Error и использования его стека.
Теперь promptDirection может сотворить такую ошибку.
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new InputError("Invalid direction: " + result);
}
А в цикле её будет ловить сподручнее.
for (;;) {
try {
var dir = promptDirection("Куда?");
console.log("Ваш выбор", dir);
break;
} catch (e) {
if (e instanceof InputError)
console.log("Недопустимое направление. Попробуйте ещё раз.");
else
throw e;
}
}
Код отлавливает только экземпляры InputError и пропускает другие исключения. Если вы снова сделаете такую же опечатку, будет корректно выведено сообщение о неопределённой переменной.
Утверждения (Assertions)
Утверждения – инструмент для простой проверки ошибок. Рассмотрим вспомогательную функцию assert:
function AssertionFailed(message) {
this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);
function assert(test, message) {
if (!test)
throw new AssertionFailed(message);
}
function lastElement(array) {
assert(array.length > 0, "пустой массив в lastElement");
return array[array.length - 1];
}
Это – компактный способ ужесточения требований к значениям, который выбрасывает исключение в случае, когда заданное условие не выполняется. К примеру, функция lastElement, добывающая последний элемент массива, вернула бы undefined для пустых массивов, если б мы не использовали assertion. Извлечение последнего элемента пустого массива не имеет смысла, и это явно было бы ошибкой программиста.
Утверждения – способ убедиться в том, что ошибки провоцируют прерывание программы в том месте, где они совершены, а не просто выдают странные величины, которые передаются по системе и вызывают проблемы в каких-то других, не связанных с этим, местах.
Итог
Ошибки и недопустимые входные данные случаются в жизни. Ошибки в программах надо искать и исправлять. Их легче найти, используя автоматические системы проверок и добавляя утверждения в ваши программы.
Проблемы, вызванные чем-то, что неподвластно вашей программе, нужно обрабатывать достойно. Иногда, когда проблему можно решить локально, допустимо возвращать специальные значения для отслеживания таких случаев. В других случаях предпочтительно использовать исключения.
Выброс исключения приводит к разматыванию стека до тех пор, пока не будет встречен блок try/catch или пока мы не дойдём до дна стека. Значение исключения будет передано в блок catch, который сможет удостовериться в том, что это исключение действительно то, которое он ждёт, и обработать его. Для работы с непредсказуемыми событиями в потоке программы можно использовать блоки finally, чтобы определённые части кода были выполнены в любом случае.
Упражнения
Повтор
Допустим, у вас есть функция primitiveMultiply, которая в 50% случаев перемножает 2 числа, а в остальных случаях выбрасывает исключение типа MultiplicatorUnitFailure. Напишите функцию, обёртывающую эту, и просто вызывающую её до тех пор, пока не будет получен успешный результат.
Убедитесь, что вы обрабатываете только нужные вам исключения.
function MultiplicatorUnitFailure() {}
function primitiveMultiply(a, b) {
if (Math.random() < 0.5)
return a * b;
else
throw new MultiplicatorUnitFailure();
}
function reliableMultiply(a, b) {
// Ваш код
}
console.log(reliableMultiply(8, 8));
// → 64
Запертая коробка
Рассмотрим такой, достаточно надуманный, объект:
var box = {
locked: true,
unlock: function() { this.locked = false; },
lock: function() { this.locked = true; },
_content: [],
get content() {
if (this.locked) throw new Error("Заперто!");
return this._content;
}
};
Это коробочка с замком. Внутри лежит массив, но до него можно добраться только, когда коробка не заперта. Напрямую обращаться к свойству _content нельзя.
Напишите функцию withBoxUnlocked, принимающую в качестве аргумента функцию, которая отпирает коробку, выполняет функцию, и затем обязательно запирает коробку снова перед выходом – неважно, выполнилась ли переданная функция правильно, или она выбросила исключение.
function withBoxUnlocked(body) {
// Ваш код
}
withBoxUnlocked(function() {
box.content.push("золотишко");
});
try {
withBoxUnlocked(function() {
throw new Error("Пираты на горизонте! Отмена!");
});
} catch (e) {
console.log("Произошла ошибка:", e);
}
console.log(box.locked);
// → true
В качестве призовой игры убедитесь, что при вызове withBoxUnlocked, когда коробка не заперта, коробка остаётся незапертой.
Типы ошибок
JavaScript — это обычный алгоритмический язык программирования. Поэтому принципы поиска и исправления ошибок,
предложенные в этой теме, подходят и для других языков.
Ошибки делятся на два типа:
- Синтаксические
- Алгоритмические
Синтаксические — это ошибки, которые нарушают правила написания программы. Из-за них код становится
непонятным браузеру. Поэтому скрипт перестаёт выполняться или вообще не запускается. Например — пропущенная
буква в названии переменной или функции, незакрытая скобка или кавычка.
При алгоритмических ошибках программа выполняется, но работает не так как нужно и производит не
те действия, которые ожидает от неё программист.
Поиск синтаксических ошибок
Если Вы неправильно написали имя переменной или функции, то об этом вы можете узнать с помощью средств
отладки, которые будут рассмотрены далее.
Неправильно расставить кавычки вообще маловероятно, потому что в
текстовых редакторах текст в кавычках обычно выделяется своим цветом.
Найти незакрытую скобку помогут текстовые редакторы для
программирования. Соответсвующие скобки обычно выделяются определённым цветом.
Это работает со всеми видами скобок.
Поиск алгоритмических ошибок
Искать алгоритмические ошибки намного сложнее, потому что
они никак не проявляются. Чтобы найти такую ошибку,
нужно выбрать значимое место скрипта. Это могут быть последние
строки программы, отдельной функции или другая важная чать программы.
Затем нужно ответить на два вопроса:
- Доходит ли выполнение программы до этого места?
- Какое значение имеют переменные, имеющие отношение к ошибке?
Ответы на оба вопроса вы получаете, если выводите на экран значения важных переменных в выбранном месте
программы.
Выполнение программы может не дойти до определённого места из-за того, что не выполняется условие в
операторе if. Поэтому нужно смотреть значения выражений в ближайшем операторе
if. Если в нём сравниваются переменные, то нужно вывести на экран значения этих
переменных перед оператором. А если сравниваются выражения, то вывести на экран значения этих выражений.
Если выполнение программы дошло до выбранного места, но программа работает неправильно, значит какая-то
переменная имеет не то значение, какое должна иметь. Когда вы поймёте, какая это переменная, то нужно отследить
как меняется её значение по ходу всего выполнения. Нужно внимательно посмотреть те строки кода, в которых она
получает новые значения. Если визуально найти ошибку не удалось, то можно после каждого изменения значения
выводить переменную на экран. Так Вы найдёте строку, в которой нарушается алгоритм программы.
Средства отладки JavaScript кода
В браузерах есть средства отладки, которые помогают найти и исправить ошибки в JavaScript коде.
Мы используем «Инструменты
разработчика» браузера FireFox, которые уже рассматривались
в теме про отладку CSS. Нам будут полезны две вкладки панели инструментов — «Консоль» и «Отладчик».

Консоль браузера используется не только для скриптов, поэтому в ней есть несколько кнопок, для включения и
отключения разного вида информации. Пока вы не разберётесь с ними,
лучше их все включить.

Если скрипт содержит синтаксическую ошибку, то информация
об этом выводится в консоль. Указывается строка, в которой
находится ошибка. Только номер строки не всегда совпадает с номером в редакторе. Но можно кликнуть на цифру
и откроется код скрипта. В нём номер будет совпадать.

В консоль можно вывести данные из скрипта. Текст или значения переменных. Для этого в JavaScript есть
метод console.log(). Пример:
+
|
9 |
let num = 10;
console.log('значение равно ', num);
|
Результат будет выглядеть так:

Справа указана строка, которая вывела эту информацию.
В консоль можно вывести не только переменные, но также массивы и объекты. Сначала они выводятся в свёрнутом
виде. Их можно развернуть и посмотреть все данные, которые они содержат. Удобно отображаются DOM-объекты,
то есть, элементы страницы. Пример:
|
10 |
var div = document.querySelector('div');
console.log(div);
|
В консоли DOM-объект выглядит так:

Вкладка «Отладка» панели инструментов позволяет приостановить выполнение скрипта на любой строке и
посмотреть, какие значения имеют переменные на этом этапе. Вкладка разделена на три части. В левой части
показаны файлы, в которых содержатся скрипты.

Кликните на нужном файле и в средней части вкладки отобразиться код файла. Строки кода пронумерованы. Можно
кликнуть на номерах нужных строк и они будут выделены синим цветом. На этих строках выполнение скрипта будет
приостанавливаться. Такие строки называются точки останова. Они перечислены также в правой части вкладки.

Когда вы выбрали нужные строки, запустите страницу заново. Скрипт остановится на первой выбранной строке и
можно будет посмотреть, как выглядит страница в этот момент. Также можно узнать какие значения имеют
переменные. В правой части вкладки нужно нажать «+», написать имя переменной и нажать Enter. Затем можно
добавить другую переменную.

Когда Вы посмотрели всё, что нужно, переходите к следующей точке останова. Нажмите кнопку «Возобновить».
Скрипт продолжит выполнение, дойдёт до следующей выбранной строки и вновь остановится.

Таким образом проходятся все точки останова, пока скрипт не выполнится. Если выбранная строка находится
внутри цикла, то скрипт останавливается на каждой итерации. Когда Вы
нашли ошибку, не забудьте отменить точки останова, чтобы скрипт выполнялся как обычно.
Рассмотренный инструмент позволяет быстро отследить изменение значений переменных по ходу всей программы.
Он полезен, когда трудно понять, в какой части скрипта находится ошибка. Вы можете выбрать сразу несколько
строк и посмотреть, как ведёт себя программа в этих строках.
Недавно мы рассказали о том, как начать писать программы на JavaScript:
- что такое HTML и JavaScript;
- из чего состоят скрипты;
- как и где их выполнять и куда вставлять;
- где искать готовые решения и что с ними потом делать;
- как работать с разными элементами и обрабатывать нажатия клавиш.
Теперь шагнём дальше — изучим отладку скриптов в браузере и посмотрим, чем она может нам помочь.
Что такое отладка
Отладка — это поиск и исправление ошибок в программе. Например, мы написали скрипт, добавили его на страницу, настроили запуск по нажатию кнопки — а при нажатии ничего не происходит. При этом в консоли нет никаких ошибок — все команды верные, браузер просто что-то делает, а результата нет. Отладка нужна как раз для того, чтобы найти ошибку и исправить её.
Варварская отладка
Самый примитивный вариант отладки — добавить в код на JavaScript метод console.log(), поместив в скобки нужные данные для отладки. Console.log() — это просто способ вывести в консоль какой-нибудь текст.
Например, внутри функции можно сказать: console.log(‘Вызвана такая-то функция’) — и в нужный момент мы увидим, что функция вызвалась (или нет).
Минус этого подхода в том, что в коде появляется много отладочного мусора. А ещё, если мы не предусмотрели логирование для какой-то функции, то мы не поймаем в ней ошибку.
К счастью, помимо console.log() человечество изобрело много удобных инструментов отладки.
Что нужно для отладки
Для несложных проектов на JavaScript проще всего использовать встроенный отладчик в браузере Google Chrome. Единственное ограничение — он работает только с файлами скриптов, а не со встроенным в страницу кодом. Это значит, что если код скрипта находится внутри HTML-файла внутри тега <script>, то отладка не сработает.
Чтобы открыть панель отладки в Chrome, нажимаем ⌘+⌥+I и переходим на вкладку Sources (Источники):
Открываем скрипт
Допустим, мы хотим посмотреть, как работает скрипт из задачи про выпечку и как он перебирает все варианты.
Всё, что у нас есть, — это код. Чтобы мы смогли его отладить, его нужно положить в отдельный файл скрипта, присоединить к HTML-документу и запустить в браузере.
Открываем любой текстовый редактор, например Sublime Text, вставляем код скрипта и сохраняем файл как temp.js. Имя может быть любым, а после точки всегда должно стоять js — так браузер поймёт, что перед нами скрипт.
После этого в новом файле вставляем шаблон пустой HTML-страницы и подключаем наш скрипт — добавляем в раздел <body> такую строку:
<script type="text/javascript" src="temp.js"></script>
Получиться должно что-то вроде такого:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<script type="text/javascript" src="temp.js"></script>
</body>
</html>
Сохраняем этот код как HTML-файл, например index.html, и кладём в ту же папку, что и скрипт. Теперь заходим в папку и дважды щёлкаем по HTML-файлу, чтобы открыть эту страницу в браузере:
На странице ничего нет, но нам нужна не страница, а скрипт, поэтому находим слева наш файл temp.js и нажимаем на него — откроется код скрипта. Теперь можно начинать отладку:
Добавляем точки остановки
Точка остановки — это место, в котором наш скрипт должен остановиться и ждать дальнейших действий программиста. Их ещё называют брейкпоинты, от английского breakpoint — точка, где всё останавливается.
Когда скрипт доходит до этой точки, он ставит скрипт на паузу. При этом все данные и значения переменных скрипта остаются в памяти — в них можно заглянуть.
Брейкпоинт нужен для того, чтобы выполнить скрипт по шагам, начиная с первой команды. Чтобы его установить, нажимаем на номер строки с первой командой — в нашем случае это строка 2:
Обновим страницу и увидим, что скрипт начал работу и остановился. Но он остановился не на второй строке, а на шестой — всё потому, что это первая строка в скрипте, где происходит какое-то действие. Дело в том, что просто объявление новых переменных не влияет на работу скрипта, поэтому он ищет первую команду с действием. В нашем случае — это цикл for:
Пошаговая отладка
Чтобы посмотреть на работу скрипта по шагам, надо нажимать F9 или стрелку вправо с точкой на панели отладки:
Каждый раз, как мы будем нажимать F9 или эту кнопку, скрипт будет переходить к следующей команде, выполнять её и снова становиться на паузу:
Добавляем переменные для отслеживания
Если просто выполнять скрипт по шагам, то мы увидим, какие команды и в каком порядке выполняются, но не будем знать, какие значения лежат в переменных на каждом шагу. Их можно увидеть, просто наведя курсор на любую переменную — над ней появится всплывающая подсказка с текущим значением. Но так работать неудобно — проще сразу видеть значения всех переменных.
Чтобы добавить переменную и видеть её значение во время выполнения, в панели отладки в разделе Watch нажимаем плюсик, вводим имя переменной, выбираем её из списка и нажимаем энтер:
Теперь видно, что на этом шаге значение переменной a равно нулю:
Точно так же добавим остальные переменные: i, b, c. Так мы увидим, что первые два цикла только начались, а внутренний прошёл уже три итерации:
Так, нажимая постоянно F9, мы прогоним весь скрипт до конца и посмотрим, при каких значениях какие условия выполняются и как находится решение:
Но у такого подхода есть минус — если вложенных циклов много или скрипт очень большой, то на пошаговое выполнение уйдёт много времени. Чтобы не перебирать всё вручную, ставят дополнительные брейкпойнты в нужных местах.
Отладка брейкпойнтами
Допустим, нам важно понять, в какой момент скрипт находит и выдаёт решение. Глядя в код, мы понимаем, что как только скрипт дошёл до команды console.log() — он нашёл очередное решение. Это значит, что мы можем поставить брейкпоинт только на эту строчку и не прогонять вручную весь скрипт: он сам остановится, когда дойдёт до неё, а мы сможем посмотреть значения переменных в этот момент.
Для этого:
- Нажимаем снова на строку 2 и убираем предыдущую точку остановки.
- Ставим брейкпоинт на строку 20 — там, где происходит вывод решения в консоль.
- Нажимаем F8.
После этого скрипт продолжит работу сам и снова остановится, как только дойдёт до этой строки. Обратите внимание на значения переменных — они меняются к каждой остановке, а значит, скрипт работает как обычно, но останавливается в нужном нам месте:
Таких точек остановки можно поставить сколько угодно и в любой момент — на каждой из них отладчик остановится и покажет текущее состояние скрипта.
Зачем это всё
Отладка нужна, чтобы найти ошибки в программе. Если мы видим, что на очередном шаге в переменной находится не то, что мы ожидали увидеть, значит, что-то в коде идёт не так. Мы ставим брейкпоинт на начало нужных команд, запускаем отладку и находим команду, которая приводит к ошибке.
В следующей статье мы покажем на примере с реальным кодом, как отладка помогает находить и исправлять такие ошибки. Подпишитесь, чтобы не пропустить это.
Вёрстка:
Кирилл Климентьев
This tutorial teaches you the basic workflow for debugging any JavaScript issue in DevTools. Read on, or watch the video version of this tutorial, below.
Step 1: Reproduce the bug
Finding a series of actions that consistently reproduces a bug is always the first step to debugging.
- Open this demo in a new tab.
- Enter
5in the Number 1 text box. - Enter
1in the Number 2 text box. - Click Add Number 1 and Number 2. The label below the button says
5 + 1 = 51. The result should be6. This is the bug you’re going to fix.
In this example, the result of 5 + 1 is 51. It should be 6.
Step 2: Get familiar with the Sources panel UI
DevTools provides a lot of different tools for different tasks, such as changing CSS, profiling page load performance, and monitoring network requests. The Sources panel is where you debug JavaScript.
-
Open DevTools by pressing Command+Option+I (Mac) or Control+Shift+I (Windows, Linux). This shortcut opens the Console panel.
-
Click the Sources tab.
The Sources panel UI has 3 parts:
- The File Navigator pane. Every file that the page requests is listed here.
- The Code Editor pane. After selecting a file in the File Navigator pane, the contents of that file are displayed here.
- The JavaScript Debugging pane. Various tools for inspecting the page’s JavaScript. If your DevTools window is wide, this pane is displayed to the right of the Code Editor pane.
Step 3: Pause the code with a breakpoint
A common method for debugging a problem like this is to insert a lot of console.log() statements into the code, in order to inspect values as the script executes. For example:
function updateLabel() {
var addend1 = getNumber1();
console.log('addend1:', addend1);
var addend2 = getNumber2();
console.log('addend2:', addend2);
var sum = addend1 + addend2;
console.log('sum:', sum);
label.textContent = addend1 + ' + ' + addend2 + ' = ' + sum;
}
The console.log() method may get the job done, but breakpoints can get it done faster. A breakpoint lets you pause your code in the middle of its execution, and examine all values at that moment in time. Breakpoints have a few advantages over the console.log() method:
- With
console.log(), you need to manually open the source code, find the relevant code, insert theconsole.log()statements, and then reload the page in order to see the messages in the Console. With breakpoints, you can pause on the relevant code without even knowing how the code is structured. - In your
console.log()statements you need to explicitly specify each value that you want to inspect. With breakpoints, DevTools shows you the values of all variables at that moment in time. Sometimes there are variables affecting your code that you’re not even aware of.
In short, breakpoints can help you find and fix bugs faster than the console.log() method.
If you take a step back and think about how the app works, you can make an educated guess that the incorrect sum (5 + 1 = 51) gets computed in the click event listener that’s associated to the Add Number 1 and Number 2 button. Therefore, you probably want to pause the code around the time that the click listener executes. Event Listener Breakpoints let you do exactly that:
-
In the JavaScript Debugging pane, click Event Listener Breakpoints to expand the section. DevTools reveals a list of expandable event categories, such as Animation and Clipboard.
-
Next to the Mouse event category, click Expand
. DevTools reveals a list of mouse events, such as click and mousedown. Each event has a checkbox next to it.
-
Check the click checkbox. DevTools is now set up to automatically pause when any
clickevent listener executes. -
Back on the demo, click Add Number 1 and Number 2 again. DevTools pauses the demo and highlights a line of code in the Sources panel. DevTools should be paused on this line of code:
function onClick() {If you’re paused on a different line of code, press Resume Script Execution
until you’re paused on the correct line.
Note: If you paused on a different line, you have a browser extension that registers a
clickevent listener on every page that you visit. You were paused in the extension’sclicklistener. If you use Incognito Mode to browse in private, which disables all extensions, you can see that you pause on the correct line of code every time.
Event Listener Breakpoints are just one of many types of breakpoints available in DevTools. It’s worth memorizing all the different types, because each type ultimately helps you debug different scenarios as quickly as possible. See Pause Your Code With Breakpoints to learn when and how to use each type.
Step 4: Step through the code
One common cause of bugs is when a script executes in the wrong order. Stepping through your code enables you to walk through your code’s execution, one line at a time, and figure out exactly where it’s executing in a different order than you expected. Try it now:
-
On the Sources panel of DevTools, click Step into next function call
to step through the execution of the
onClick()function, one line at a time. DevTools highlights the following line of code:if (inputsAreEmpty()) { -
Click Step over next function call
. DevTools executes
inputsAreEmpty()without stepping into it. Notice how DevTools skips a few lines of code. This is becauseinputsAreEmpty()evaluated to false, so theifstatement’s block of code didn’t execute.
That’s the basic idea of stepping through code. If you look at the code in get-started.js, you can see that the bug is probably somewhere in the updateLabel() function. Rather than stepping through every line of code, you can use another type of breakpoint to pause the code closer to the probable location of the bug.
Step 5: Set a line-of-code breakpoint
Line-of-code breakpoints are the most common type of breakpoint. When you’ve got a specific line of code that you want to pause on, use a line-of-code breakpoint:
-
Look at the last line of code in
updateLabel():label.textContent = addend1 + ' + ' + addend2 + ' = ' + sum; -
To the left of the code you can see the line number of this particular line of code, which is 32. Click on 32. DevTools puts a blue icon on top of 32. This means that there is a line-of-code breakpoint on this line. DevTools now always pauses before this line of code is executed.
-
Click Resume script execution
. The script continues executing until it reaches line 32. On lines 29, 30, and 31, DevTools shows the values of
addend1,addend2, andsuminline next to their declarations.
In this example, DevTools pauses on the line-of-code breakpoint on line 32.
Step 6: Check variable values
The values of addend1, addend2, and sum look suspicious. They’re wrapped in quotes, which means that they’re strings. This is a good hypothesis for the explaining the cause of the bug. Now it’s time to gather more information. DevTools provides a lot of tools for examining variable values.
Method 1: The Scope pane
When you’re paused on a line of code, the Scope pane shows you what local and global variables are currently defined, along with the value of each variable. It also shows closure variables, when applicable. Double-click a variable value to edit it. When you’re not paused on a line of code, the Scope pane is empty.
Method 2: Watch Expressions
The Watch Expressions tab lets you monitor the values of variables over time. As the name implies, Watch Expressions aren’t just limited to variables. You can store any valid JavaScript expression in a Watch Expression. Try it now:
- Click the Watch tab.
- Click Add Expression
.
- Type
typeof sum. - Press Enter. DevTools shows
typeof sum: "string". The value to the right of the colon is the result of your Watch Expression.
The screenshot above shows the Watch Expression pane (bottom-right) after creating the typeof sum watch expression. If your DevTools window is large, the Watch Expression pane is on the right, above the Event Listener Breakpoints pane.
As suspected, sum is being evaluated as a string, when it should be a number. You’ve now confirmed that this is the cause of the bug.
Method 3: The Console
In addition to viewing console.log() messages, you can also use the Console to evaluate arbitrary JavaScript statements. In terms of debugging, you can use the Console to test out potential fixes for bugs. Try it now:
- If you don’t have the Console drawer open, press Escape to open it. It opens at the bottom of your DevTools window.
- In the Console, type
parseInt(addend1) + parseInt(addend2). This statement works because you are paused on a line of code whereaddend1andaddend2are in scope. - Press Enter. DevTools evaluates the statement and prints out
6, which is the result you expect the demo to produce.
The screenshot above shows the Console drawer after evaluating parseInt(addend1) + parseInt(addend2).
Step 7: Apply a fix
You’ve found a fix for the bug. All that’s left is to try out your fix by editing the code and re-running the demo. You don’t need to leave DevTools to apply the fix. You can edit JavaScript code directly within the DevTools UI. Try it now:
- Click Resume script execution
.
- In the Code Editor, replace line 31,
var sum = addend1 + addend2, withvar sum = parseInt(addend1) + parseInt(addend2). - Press Command + S (Mac) or Control + S (Windows, Linux) to save your change.
- Click Deactivate breakpoints
. Its color changes to blue to indicate that it’s active. While this is set, DevTools ignores any breakpoints you’ve set.
- Try out the demo with different values. The demo now calculates correctly.
Next steps
Congratulations! You now know how to make the most of Chrome DevTools when debugging JavaScript. The tools and methods you learned in this tutorial can save you countless hours.
This tutorial only showed you two ways to set breakpoints. DevTools offers many other ways, including:
- Conditional breakpoints that are only triggered when the condition that you provide is true.
- Breakpoints on caught or uncaught exceptions.
- XHR breakpoints that are triggered when the requested URL matches a substring that you provide.
See Pause Your Code With Breakpoints to learn when and how to use each type.
There’s a couple of code stepping controls that weren’t explained in this tutorial. See Step over line of code to learn more.





























