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

Вы разработали какую-то программу. Есть четыре случая:

  • Все работает => все хорошо
  • Что-то не компилируется => обычно нужно что-то не очень сложное поправить (опечатались и т.п.) - компилятор подсказывает где проблема и какая, достаточно внимательно прочитать и перевести сообщение об ошибке и открыв строчку вашего кода указанную в ошибке - поправить
  • Программа падает в процессе исполнения - это уже проблема хуже, но если вы умеете воспроизводить падение вашей программы - вы можете под отладчиком (об этом ниже) исполнить программу шаг за шагом вплоть до падения, и таким образом найти проблему и поправить ее => все хорошо, но отладка - довольно длительный процесс
  • Программа ведет себя не так, как ожидалось - это худший случай, т.к. вы наблюдаете проблему, но с какого момента реально что-то пошло не так - т.е. где именно возникли проблемы - угадать довольно сложно, поэтому отладка превращается в муторный процесс => все плохо

Ниже рассказывается как выполнять отладку на примере IDEA.

Breakpoints

Вы запускаете программу - и она исполняется, но часто хочется поставить ее исполнение на паузу в каком-нибудь сложном месте (например где ваша арифметическая формула дает не тот результат, который вы ожидаете) и хочется посмотреть на значения всех переменных, чтобы понять, что привело в формуле к неверному результату.

Для таких “остановок в конкретном месте” почти во всех IDE есть возможность поставить точку остановки (breakpoint).

Это делается следующим образом:

Открываете строку, в которой вы хотите приостановиться, и слева от нее делаете левый клик мыши:

Left click at line

Таким образом вы поставили точку остановки:

Breakpoint at line

Теперь вам надо запустить программу под отладчиком (если вы запускаете правым кликом по файлу - то вместо Run вам достаточно нажать Debug, если вы запускаете зеленой стрелочкой, то вместо нее вам надо нажать на зеленого жука).

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

Debug view

  1. Здесь есть зеленая стрелочка - продолжить исполнение до следующей точки остановки, пауза - поставить исполнение на паузу в той строчке, в которой прямо сейчас она исполняется, и остановка - завершить исполнение программы
  2. Stacktrace - все вложенные вызовы функций которые привели вас в то место, в котором сейчас программа отлаживается (т.е. это последовательность вызовов в коде, при нажатии на одну из строк стектрейса - IDE телепортирует вас в соответствующее место в коде)
  3. Здесь можно посмотреть вывод программы в консоль, т.е. это то же самое, что и показывается по умолчанию при обычном запуске приложения
  4. Эти кнопки говорят: исполни одну строку программы и опять встань на паузу, зайди внутрь вызываемой функции и т.п..
  5. Самое полезное - значения локальных переменных. Сюда же можно добавить в наблюдение произвольное выражение, нажав на кнопку зеленого плюсика в левой части Variables - туда можно написать например 2 + 2, или какое-то арифметическое действие над несколькими локальными переменными, и тогда среди Variables будет так же наблюдаться это выражение

Условный breakpoint

Иногда полезно останавливаться в точке остановки только в некоторых случаях, т.е. по условному выражению.

Это можно сделать нажав правой кнопкой на установленную точку остановки (красный кружочек), и там написать любое логическое выражение в поле Condition, а затем нажать Done. Тогда при исполнении программы эта точка остановки приостановит исполнение если выражение возвращает true.

Пример:

int w = 100;
int h = 200;
int[] xs = new int[w * h];
for (int x = 0; x < w; ++x) {
    for (int y = 0; y < h; ++y) {
        xs[y * h + x] = 239;
    }
}

Этот код падает: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 20000 с указанием строчки xs[y * h + x] = 239;. Простая точка остановки не подходит, т.к. исполнение падает не сразу, и лишь где-то в конце.

Давайте скопируем индекс обращения из кода (y * h + x) и добавим явную проверку в условный breakpoint:

Conditional breakpoint

Т.к. это явная и надежная проверка выхода за пределы массива в этом месте - то при запуске под отладчиком приложение остановится на паузе ровно в тот момент, когда произошел бы выход за пределы массива, и мы сможем посмотрев на код и на значения локальных переменных в этом случае понять, что надо было писать вместо y * h + x так: y * w + x.

Отладка программы по ошибке

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

Например тогда нам бы не пришлось писать условную точку остановки при выходе за пределы массива, а было бы достаточно сказать “приостанови исполнение под отладчиком в момент первого выхода за пределы массива”.

Чтобы так сделать - надо зайти в настройки точек остановки (Ctrl+Shift+F8 или просто нажав правой кнопкой на любую установленную точку остановки и там нажать More):

Breakpoints on any exception

Кликнуть левой кнопкой на Java Exception Breakpoints->Any exception и Поставить галочки на:

  • Java Exception Breakpoints
  • Any exception
  • Caugh exception (остановка исполнения по исклчениям которые где-то будут обработаны)
  • Uncaugh exception (остановка исполнения по исклчениям которые приведут к падению потока исполнения)

Но установка галки Caugh exception приведет к тому, что при запуске JVM (Java virtual machine - т.е. интерпретатора который исполняет запущенную программу) ваше исполнение будет много раз останавливаться при бросании ошибок внутри JVM (т.е. это исключения, которые бросает реализация Java интерпретатора, и это абсолютно нормально, эти исключения сам же интерпретатор поймает и корректно обработает).

Проблема в том, что из-за множества таких исключений до исполения собственно кода приложения надо будет очень много раз нажать зеленую стрелочку “продолжения исполнения”. Поэтому галку Caugh exception часто имеет смысл не устанавливать (почти всегда достаточно Uncaugh exception).