Отладка
Ошибки – неотъемлемая часть любого проекта, поэтому отладка очень важна.
В этом уроке мы узнаем об отладке 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
работает некорректно.
Конечно, этот баг был обнаружен самим интерпретатором языка программирования при первом же запуске, но это был всего лишь простейший пример.
IEx.Helpers
Одна из самых раздражающих особенностей работы с IEx заключается в том, что у него нет истории команд, которые вы выполняли в предыдущих запускаж.
Для решения этой проблемы существует отдельная секция документации на IEx документации, где вы можете найти решения для вашей платформы по выбору.
Также можете просмотреть список других источников по ссылке IEx.Helpers документация.
Dialyxir и Dialyzer
Dialyzer – DIscrepancy 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
позволяет настраивать его:
- Можно передать в качестве аргумента путь к внешнему файлу конфигураций.
-
Дополнительные аргументы
:local
и:global
оказывают следующий эффект:-
:global
– отладчик будет интерпретировать код на всех известных нодах. Это аргумент по умолчанию.останова -
:local
– отладчик будет интерпретировать код только на текущей ноде.
-
Следующим шагом подключим наш модуль к отладчику:
iex > :int.ni(Example)
{:module, Example}
Модуль :int
– это интерпретатор, дающий возможность ставить точки останова и пошагово выполнять код программы.
Когда вы запустите отладчик вы увидите примерно такое окно:
После того, как мы подключили наш модуль к отладчику, он появится в меню слева:
Точки останова
Точка останова – это точка в коде, где выполнение программы будет остановлено. Есть два способа поставить точку останова:
-
вызвать
:int.break/2
в нашем коде - через графический интерфейс
Попробуем поставить точку останова в IEx:
iex > :int.break(Example, 8)
:ok
Точка установлена в строке 8 модуля Example
.
Теперь, когда мы вызовем нашу функцию:
iex > Example.cpu_burns(1, 1, 1)
Выполнение поставится на паузу и окно отладчика будет выглядеть следующим образом:
Также появится дополнительное окно с нашим исходным кодом:
В этом окне можно просмотреть значения переменных, выполнить следующую строку кода или произвольное выражение.
Функция :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 можно поставить точку останова. Если мы выберем строку, не содержащую кода, точка останова будет проигнорирована интерпретатором, но отобразится в окне отладчика. Есть три типа точек останова:
-
Точка останова в строке – отладчик остановит выполнение сразу же, как только мы достигнем нужной строки. Ставится при помощи
:int.break/2
-
Условная точка останова – похожа на предыдущую, но отладчик остановит ход выполнения, только если выполнится определенное условие. Ставится функцией
:int.get_binding/2
-
Точка останова в функции – отладчик остановит ход выполнения на первой строке функции. Устанавливается вызовом
:int.break_in/3
Вот и всё! Счастливой отладки!
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!