Отладка

Ошибки – неотъемлемая часть любого проекта, поэтому отладка очень важна.

В этом уроке мы узнаем об отладке Elixir-кода и инструментах статического анализа, позволяющих находить потенциальные проблемы.

IEx

Самый простой инструмент для отладки кода на Elixir - это IEx.

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

IEx значит Elixir's interactive shell (интерактивная оболочка).

Вы уже могли видеть IEx в предыдущем уроке Основы, где мы запускали код Elixir в интерактивном режиме в оболочке.

Идея проста.

Вы получаете интерактивную оболочку в контексте того места, которое хотите отлаживать.

Давайте попробуем.

Для этого создайте файл с именем test.exs и поместите в него следующий код:

defmodule TestMod do
  def sum([a, b]) do
    b = 0

    a + b
  end
end

IO.puts(TestMod.sum([34, 65]))

И если вы запустите его - вы получите очевидный результат 34:

$ elixir test.exs
warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

34

И теперь мы переходим к самой захватывающей части - отладке.

Поместите require IEx; IEx.pry после строки b = 0, и попробуйте запустить еще раз.

Вы получите что-то вроде этого:

$ elixir test.exs
warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

Cannot pry #PID<0.92.0> at TestMod.sum/1 (test.exs:5). Is an IEx shell running?
34

Обратите внимание на это важное сообщение.

Во время обычного запуска прилоежния IEx показывает вам это сообщение вместо того, чтобы заблокировать выполнение программы.

Для корректного запуска дебаггера вам нужно запустить команду с ключом -S: iex -S.

Это запускает mix внутри iex таким образом, что приложение выполняется в специальном режиме, и вызовы к IEx.pry останавливают выполнение приложения.

Например, выполните iex -S mix phx.server для отладки приложения Phoenix.

В нашем случае запустим команду iex -r test.exs для нашего файла:

$ iex -r test.exs
Erlang/OTP 21 [erts-10.3.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

Request to pry #PID<0.107.0> at TestMod.sum/1 (test.exs:5)

    3:     b = 0
    4:
    5:     require IEx; IEx.pry
    6:
    7:     a + b

Allow? [Yn]

Вы попадаете в интерактивный режим после нажатия клавиш y или Enter.

$ iex -r test.exs
Erlang/OTP 21 [erts-10.3.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

Request to pry #PID<0.107.0> at TestMod.sum/1 (test.exs:5)

    3:     b = 0
    4:
    5:     require IEx; IEx.pry
    6:
    7:     a + b

Allow? [Yn] y
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> a
34
pry(2)> b
0
pry(3)> a + b
34
pry(4)> continue
34

Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution

Чтобы выйти из IEx, вы можете или два раза нажать Ctrl+C для выхода из приложения, или напечатать continue для перехода к следующей точке останова.

Очевидно, что таким образом вы можете выполнить любой код на Elixir.

Однако, существуют ограничения: вы не можете изменять переменные у существуюего кода из-за неизменности языка (immutability).

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

В данном примере баг заключается в том, что переменной b присваивается 0 и функция sum работает некорректно.

Конечно, этот баг был обнаружен самим интерпретатором языкa программирования при первом же запуске, но это был всего лишь простейший пример.

IEx.Helpers

Одна из самых раздражающих особенностей работы с IEx заключается в том, что у него нет истории команд, которые вы выполняли в предыдущих запускаж.

Для решения этой проблемы существует отдельная секция документации на IEx документации, где вы можете найти решения для вашей платформы по выбору.

Также можете просмотреть список других источников по ссылке IEx.Helpers документация.

Dialyxir и Dialyzer

DialyzerDIscrepancy AnaLYZer for ERlang programs – инструмент статического анализа кода. Его задача – не выполняя сам программный код, проанализировать его на наличие ошибок, лишних выражений, недостижимых участков и т.д.

Dialyxir – это mix-команда, облегчающая использование Dialyzer в Elixir.

Спецификации позволяют таким инструментам как Dialyzer лучше понимать код. В отличие от документации, которая может быть прочитана только живыми людьми (если она, конечно, существует и хорошо написана), @spec использует более формальный, понятный компьютеру синтаксис.

Давайте добавим Dialyxir в наш проект. Проще всего сделать это, добавив зависимость в mix.exs:

defp deps do
  [{:dialyxir, "~> 0.4", only: [:dev]}]
end

Теперь выполним:

$ mix deps.get
...
$ mix deps.compile

Первая команда загрузит и установит Dialyxir. Возможно, вместе с ним понадобится установить также Hex. Вторая команда компилирует Dialyxir. Если вы хотите установить его глобально, пожалуйста, обратитесь к документации.

Последним шагом мы запустим Dialyxir, чтобы он построил PLT (Персистентную Таблицу Поиска). Это придется делать после установки каждой новой версии Erlang или Elixir. К счастью, Dialyzer не будет пытаться анализировать стандартную библиотеку при каждом использовании. На загрузку потребуется несколько минут.

$ mix dialyzer --plt
Starting PLT Core Build ...
this will take awhile
dialyzer --build_plt --output_plt /.dialyxir_core_18_1.3.2.plt --apps erts kernel stdlib crypto public_key -r /Elixir/lib/elixir/../eex/ebin /Elixir/lib/elixir/../elixir/ebin /Elixir/lib/elixir/../ex_unit/ebin /Elixir/lib/elixir/../iex/ebin /Elixir/lib/elixir/../logger/ebin /Elixir/lib/elixir/../mix/ebin
  Creating PLT /.dialyxir_core_18_1.3.2.plt ...
...
 done in 5m14.67s
done (warnings were emitted)

Статический анализ кода

Все готово к использованию Dialyxir:

$ mix dialyzer
...
examples.ex:3: Invalid type specification for function 'Elixir.Examples':sum_times/1.
The success typing is (_) -> number()
...

Сообщение от Dialyzer понятно: тип возвращаемого значения нашей функции sum_times/1 отличается от объявленного. Причина в том, что Enum.sum/1 возвращает число типа number, а не integer, но при этом заявлено, что sum_times/1 возвращает integer.

Поскольку number это не integer мы получаем ошибку. Как её исправить? Нужно использовать функцию round/1, чтобы превратить число типа number в integer:

@spec sum_times(integer) :: integer
def sum_times(a) do
  [1, 2, 3]
  |> Enum.map(fn el -> el * a end)
  |> Enum.sum()
  |> round
end

И теперь:

$ mix dialyzer
...
  Proceeding with analysis...
done in 0m0.95s
done (passed successfully)

Использование спецификаций вместе с инструментами статического анализа позволяет писать код, тестирующий сам себя и содержащий меньше ошибок.

Отладка

Иногда статического анализа кода не достаточно. Часто, чтобы найти ошибки, нужно понять логику выполнения программы. Самый простой способ – это добавить в наш код выражения вывода, такие как IO.puts/2, чтобы отследить значения переменных и ход выполнения. Однако он примитивен и обладает рядом ограничений. К счастью для нас, мы можем использовать отладчик Erlang для работы с Elixir-кодом.

Взглянем на простейший модуль:

defmodule Example do
  def cpu_burns(a, b, c) do
    x = a * 2
    y = b * 3
    z = c * 5

    x + y + z
  end
end

Теперь запустим iex:

$ iex -S mix

И отладчик:

iex > :debugger.start()
{:ok, #PID<0.307.0>}

Модуль :debugger отвечает за доступ к отладчику. Функция start/1 позволяет настраивать его:

Следующим шагом подключим наш модуль к отладчику:

iex > :int.ni(Example)
{:module, Example}

Модуль :int – это интерпретатор, дающий возможность ставить точки останова и пошагово выполнять код программы.

Когда вы запустите отладчик вы увидите примерно такое окно:

Скриншот отладчика 1

После того, как мы подключили наш модуль к отладчику, он появится в меню слева:

Скриншот отладчика 2

Точки останова

Точка останова – это точка в коде, где выполнение программы будет остановлено. Есть два способа поставить точку останова:

Попробуем поставить точку останова в IEx:

iex > :int.break(Example, 8)
:ok

Точка установлена в строке 8 модуля Example. Теперь, когда мы вызовем нашу функцию:

iex > Example.cpu_burns(1, 1, 1)

Выполнение поставится на паузу и окно отладчика будет выглядеть следующим образом:

Скриншот отладчика 3

Также появится дополнительное окно с нашим исходным кодом:

Скриншот отладчика 4

В этом окне можно просмотреть значения переменных, выполнить следующую строку кода или произвольное выражение. Функция :int.disable_break/2 позволяет деактивировать точку останова:

iex > :int.disable_break(Example, 8)
:ok

Реактивировать точку останова можно вызовом функции :int.enable_break/2. Удалить точку совсем можно так:

iex > :int.delete_break(Example, 8)
:ok

Все те же операции доступны в окне отладчика. В верхнем меню Break при помощи Line Break можно поставить точку останова. Если мы выберем строку, не содержащую кода, точка останова будет проигнорирована интерпретатором, но отобразится в окне отладчика. Есть три типа точек останова:

Вот и всё! Счастливой отладки!

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!