Как делать11 октября 2026 г.6 мин чтения
Основы программированияШаг 14 из 15

Отладка: breakpoints, console и чтение stack trace

Коротко

Отладка — это проверка гипотез, а не перебор правок наугад: воспроизвести ошибку, сузить место, где она возникает, и подтвердить причину фактами. Stack trace читают сверху вниз: первая строка — тип и текст ошибки, первая строка at с вашим файлом — место, где программа упала. Когда исключения нет, а результат неверный, ставят breakpoint и смотрят значения переменных в момент выполнения — в Chrome DevTools, VS Code или через node --inspect.

РуководствоУровень: НачальныйОтладкаПрактика: ~30–60 мин

После материала вы сможете

  • прочитать stack trace Node.js или браузера до первой строки своего кода и назвать файл, строку и функцию
  • найти баг без исключения через console.table и проверку типов значений
  • поставить breakpoint, посмотреть стек вызовов и значения переменных в Chrome DevTools, VS Code или node inspect
  • сформулировать гипотезу о причине бага и подтвердить её до исправления

Проверено на

Node.js 24 LTS (24.18)Chrome DevToolsVS Code

Сверено с документацией: 11 октября 2026 г.

Содержание
  1. 01Отладка как метод: воспроизвести, сузить, проверить
  2. 02Баг 1: читаем stack trace сверху вниз
  3. 03Баг 2: когда исключения нет — console.log и console.table
  4. 04Баг 3: breakpoint, стек вызовов и watch
  5. 05Тот же поиск в DevTools и VS Code
  6. 06Чек-лист перед тем, как написать «не работает»

Отладка как метод: воспроизвести, сузить, проверить

Самая дорогая привычка начинающего — менять код наугад, пока ошибка не исчезнет. Иногда она исчезает, но причина остаётся и возвращается в другом месте. Отладка (debugging) — это короткий цикл из четырёх шагов, который работает на любом языке.

  1. Воспроизвести

    Найти минимальные действия или данные, при которых ошибка повторяется каждый раз. Ошибку, которую нельзя повторить, нельзя и проверить.

  2. Сузить

    Определить, в каком файле, функции и строке значения становятся неправильными. Здесь помогают stack trace, console и breakpoints.

  3. Сформулировать гипотезу

    Одно проверяемое предложение: «доставка приходит строкой, и + склеивает строки вместо сложения».

  4. Проверить и исправить

    Подтвердить гипотезу фактом — выводом в консоль или значением в отладчике — и только потом менять код. После правки повторить шаг 1.

Разберём цикл на учебной программе: она считает сумму заказа в кофейне с доставкой и промокодом. В ней три намеренных бага разного вида: падение с исключением, неверный результат из-за типов и условие, которое никогда не срабатывает.

cart.mjs
export function calcSubtotal(items) {
  let total = 0;
  for (let i = 0; i <= items.length; i++) {
    total += items[i].price * items[i].qty;
  }
  return total;
}

export function addDelivery(subtotal, delivery) {
  return subtotal + delivery;
}

export function applyPromo(total, code) {
  if (code === "sale") return Math.round(total * 0.9);
  return total;
}

export function checkout(cart) {
  const subtotal = calcSubtotal(cart.items);
  const withDelivery = addDelivery(subtotal, cart.delivery);
  return applyPromo(withDelivery, cart.promo);
}
Модуль расчёта с тремя багами — не исправляйте заранее
main.mjs
import { checkout } from "./cart.mjs";

const cart = {
  items: [
    { name: "latte", price: 1500, qty: 2 },
    { name: "croissant", price: 1200, qty: 1 },
  ],
  delivery: "500",
  promo: "SALE",
};

console.log("Total:", checkout(cart));
Точка входа: ожидаемый итог — (1500 × 2 + 1200 + 500) × 0,9 = 4230

Баг 1: читаем stack trace сверху вниз

Запускаем node main.mjs и получаем падение. Вывод — реальный, с Node.js 24; длинный путь к папке заменён на …:

terminal
$ node main.mjs
file:///…/cart.mjs:4
    total += items[i].price * items[i].qty;
                      ^

TypeError: Cannot read properties of undefined (reading 'price')
    at calcSubtotal (file:///…/cart.mjs:4:23)
    at checkout (file:///…/cart.mjs:19:20)
    at file:///…/main.mjs:12:23
    at ModuleJob.run (node:internal/modules/esm/module_job:439:25)
    at async node:internal/modules/esm/loader:643:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v24.18.0
Stack trace необработанного исключения в Node.js 24

cart.mjs:4 и строка с ^

Что она говорит
Node.js показывает исходную строку и ставит знак ^ под местом ошибки

TypeError: Cannot read properties of undefined (reading 'price')

Что она говорит
Тип и текст: у значения undefined попытались прочитать свойство price

at calcSubtotal (…/cart.mjs:4:23)

Что она говорит
Самый свежий вызов: функция, файл, строка 4, колонка 23. Отсюда начинаем

at checkout (…/cart.mjs:19:20)

Что она говорит
Кто вызвал calcSubtotal

at file:///…/main.mjs:12:23

Что она говорит
Кто вызвал checkout: верхний уровень модуля, поэтому без имени функции

at ModuleJob.run (node:internal/…)

Что она говорит
Внутренний код Node.js — обычно пропускаем

По документации Node.js первая строка стека имеет вид «класс ошибки: сообщение», а дальше идут кадры (frames), каждый начинается с at. Самый свежий вызов наверху, точка входа — внизу. Правило чтения: найдите первую строку at с вашим файлом, а не с node:internal или node_modules. Это место, где сломалось, хотя причина может быть выше по стеку — в том, кто передал плохие данные.

Гипотеза: на какой-то итерации items[i] равен undefined. В массиве два элемента с индексами 0 и 1, а условие i <= items.length пускает цикл и на индекс 2. Это классическая ошибка «на единицу» (off-by-one). Исправление — строгое <.

Баг 2: когда исключения нет — console.log и console.table

После исправления цикла программа выводит Total: 4200500. Исключения нет, но результат абсурдный. Stack trace не появится — сужать придётся самим. Самый быстрый инструмент — временный вывод значений прямо перед подозрительным местом. console.table показывает массив объектов таблицей, а typeof — тип значения:

main.mjs
console.table(cart.items);
console.log("delivery:", cart.delivery, typeof cart.delivery);
Фрагмент: две строки добавлены в main.mjs перед вызовом checkout
terminal
$ node main.mjs
┌─────────┬─────────────┬───────┬─────┐
│ (index) │ name        │ price │ qty │
├─────────┼─────────────┼───────┼─────┤
│ 0       │ 'latte'     │ 1500  │ 2   │
│ 1       │ 'croissant' │ 1200  │ 1   │
└─────────┴─────────────┴───────┴─────┘
delivery: 500 string
Total: 4200500
Реальный вывод: цены — числа, доставка — строка

Гипотеза подтверждена: delivery — строка "500", и оператор + склеивает 4200 и "500" в "4200500". Такие данные часто приходят из форм, query-параметров и JSON, где число записано в кавычках. Исправляем на границе — приводим к числу там, где значение входит в расчёт:

cart.mjs
--- a/cart.mjs
+++ b/cart.mjs
@@ -1,11 +1,11 @@
 export function calcSubtotal(items) {
   let total = 0;
-  for (let i = 0; i <= items.length; i++) {
+  for (let i = 0; i < items.length; i++) {
     total += items[i].price * items[i].qty;
   }
   return total;
 }

 export function addDelivery(subtotal, delivery) {
-  return subtotal + delivery;
+  return subtotal + Number(delivery);
 }
Исправления багов 1 и 2
terminal
$ node main.mjs
Total: 4700
Реальный вывод после двух правок: ожидали 4230

Баг 3: breakpoint, стек вызовов и watch

Итог 4700 — без скидки. Ошибки нет, все типы числа. Нужно остановить программу внутри applyPromo и посмотреть, что в ней происходит. Breakpoint (точка останова) — место, где отладчик ставит выполнение на паузу до выполнения строки. Самый простой способ — инструкция debugger;: она работает как breakpoint, если подключён отладчик, и ничего не делает без него.

Добавим debugger; первой строкой applyPromo и запустим встроенный консольный отладчик Node.js — node inspect. Ниже реальная сессия: cont — продолжить до следующей остановки, bt — стек вызовов, exec — вычислить выражение, watch — следить за значением, next — шаг на следующую строку.

terminal
$ node inspect main.mjs
< Debugger listening on ws://127.0.0.1:9229/31bee525-ecb9-4a6c-b9ea-f75545b276bf
< For help, see: https://nodejs.org/learn/getting-started/debugging
< 
connecting to 127.0.0.1:9229 ... ok
< Debugger attached.
< 
debug> Break on start in cart.mjs:1
> 1 export function calcSubtotal(items) {
  2   let total = 0;
  3   for (let i = 0; i < items.length; i++) {
debug> cont
debug> break in cart.mjs:14
 12 
 13 export function applyPromo(total, code) {
>14   debugger;
 15   if (code === "sale") return Math.round(total * 0.9);
 16   return total;
debug> bt
#0 applyPromo cart.mjs:14:2
#1 checkout cart.mjs:22:9
#2 (anonymous) main.mjs:12:22
#3 run node:internal/modules/esm/module_job:439:24
debug> exec code
'SALE'
debug> exec code === "sale"
false
debug> watch('total')
debug> next
debug> step in cart.mjs:15
Watchers:
  0: total = 4700

 13 export function applyPromo(total, code) {
 14   debugger;
>15   if (code === "sale") return Math.round(total * 0.9);
 16   return total;
 17 }
debug> cont
debug> < Total: 4700
< 
debug> < Waiting for the debugger to disconnect...
< 
debug> .exit
Реальная сессия node inspect: отладчик сначала останавливается на первой строке, затем на debugger

Факты из отладчика: code равен 'SALE', а сравнение с "sale" даёт false — регистр не совпадает. Стек bt показывает ту же цепочку, что и stack trace: applyPromo ← checkout ← main.mjs. Исправляем сравнение и убираем debugger;:

cart.mjs
-  if (code === "sale") return Math.round(total * 0.9);
+  if (code?.trim().toLowerCase() === "sale") return Math.round(total * 0.9);
Исправление бага 3: промокод сравнивается без учёта пробелов и регистра
terminal
$ node main.mjs
Total: 4230
Реальный вывод после трёх правок

Тот же поиск в DevTools и VS Code

Консольный отладчик полезен, чтобы понять механику, но в работе удобнее графический. Под капотом он тот же: Node.js с флагом --inspect открывает порт для отладчика, и к нему подключаются Chrome DevTools или VS Code. По умолчанию это 127.0.0.1:9229. Флаг --inspect-brk дополнительно останавливает программу до первой строки вашего кода, чтобы успеть расставить breakpoints:

terminal
$ node --inspect-brk main.mjs
Debugger listening on ws://127.0.0.1:9229/5f699728-1ccb-4e05-ba8a-2afa59a35332
For help, see: https://nodejs.org/learn/getting-started/debugging
Реальный вывод: процесс ждёт подключения отладчика

Подключиться к Node.js

Chrome DevTools
Открыть chrome://inspect (в Edge — edge://inspect), процесс появится в Remote Target
VS Code
Терминал JavaScript Debug Terminal: любой node в нём отлаживается автоматически

Отладка кода страницы

Chrome DevTools
Панель Sources, файл скрипта
VS Code
Через конфигурацию запуска браузера в launch.json

Поставить breakpoint

Chrome DevTools
Клик по номеру строки в Sources
VS Code
Клик слева от номера строки или F9

Условная остановка

Chrome DevTools
Правый клик по номеру строки → Add conditional breakpoint
VS Code
Правый клик на полях → Add Conditional Breakpoint

Вывести без остановки

Chrome DevTools
Logpoint: сообщение в Console без паузы
VS Code
Logpoint: выражения в фигурных скобках, например {code}

Шаги

Chrome DevTools
Resume (F8), шаг по инструкции (F9), Step over / into / out — кнопками
VS Code
Continue F5, Step Over F10, Step Into F11, Step Out Shift+F11

Смотреть значения

Chrome DevTools
Панели Scope, Watch и Call Stack
VS Code
Разделы Variables, Watch и Call Stack
  • Остановка на исключениях: в DevTools можно включить паузу на перехваченных и неперехваченных исключениях — отладчик остановится там, где ошибка брошена, со всеми переменными.
  • Restart frame в DevTools перезапускает текущую функцию без перезапуска всей программы — удобно, чтобы пройти её ещё раз после изменения значения в Scope.
  • Условный breakpoint вроде i === items.length в цикле из бага 1 остановит программу ровно на проблемной итерации, а не на каждой.

Чек-лист перед тем, как написать «не работает»

Если ошибка в веб-приложении связана с данными с сервера, начните с вкладки Network в DevTools: статус-код, тело запроса и ответа часто сразу показывают, на чьей стороне проблема. Подробно о запросах, статусах и JSON — в уроке «HTTP и JSON».

  • Ошибку можно воспроизвести, и вы записали точные шаги или данные
  • Прочитан полный текст ошибки, а не только её тип
  • Найдена первая строка stack trace с вашим файлом: файл, строка, функция
  • Проверены типы и значения на входе подозрительной функции
  • Для запроса к API посмотрены метод, статус-код и тело ответа в Network
  • Гипотеза подтверждена фактом до исправления
  • Временные console.log и debugger удалены из кода
  • В сообщении коллеге есть шаги, ожидаемый и фактический результат и stack trace
Как проверено7 · пройдено1 · не запускалосьсверено 11 октября 2026 г.
  • запущено, работает

    cart.mjs + main.mjs (три бага) и stack trace бага 1

    node main.mjs на Node.js 24.18.0 (Windows 11); вывод вставлен реальный, путь к папке scratchpad заменён на «…» (sed), остальное без правок

  • запущено, работает

    console.table / typeof (баг 2)

    фрагмент добавлен в копию main.mjs после правки бага 1; вывод реальный

  • запущено, работает

    Исправления (diff) багов 1–3

    правки применены sed в копиях step2/step3/fixed; выводы 4200500 → 4700 → 4230 реальные

  • запущено, работает

    Сессия node inspect (debugger; cont, bt, exec, watch, next)

    node inspect main.mjs, команды подавались скриптом drive-inspect.mjs через stdin с паузой 1,5 с; транскрипт вставлен как есть (UUID — из запуска)

  • запущено, работает

    node --inspect-brk main.mjs

    запуск с timeout 4 с, вывод реальный; подключение Chrome DevTools к процессу не выполнялось

  • запущено, работает

    Упражнение repeat: node inspect + breakOnException

    реальная сессия на исходной версии с багом 1: cont без breakpoint — программа завершается; breakOnException — пауза на cart.mjs:4, i = 2, items.length = 2

  • запущено, работает

    Упражнение «Пустая корзина»: empty-cart.mjs и проверка в checkout

    запуск до и после добавления проверки, выводы реальные (путь заменён на «…»)

  • не запускалось

    Шаги в Chrome DevTools и VS Code (таблица, упражнение modify)

    UI-шаги описаны по официальной документации Chrome DevTools и VS Code; вручную в браузере и редакторе не прокликивались

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

Расскажите о продукте или проблеме. Предложим архитектуру, срок и стоимость первой версии после изучения задачи.

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

Нажимая кнопку, вы соглашаетесь с обработкой персональных данных.