[Программирование, Отладка, Julia] Debugging в Julia — два способа (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
08-Дек-2020 17:31


скришнот из metal slug 3
2020 год — это определенно год странностей. Мой код тоже часто включает в себя некоторые странные ошибки. И в данном посте я хочу показать вам несколько методов отладки кода на языке julia.
Я ни с какой стороны не профессионал в этом деле, да и это справедливо для всего, о чем я пишу в блоге, так что просто имейте это в виду… Ну, на самом деле некоторые из вас платят за мою работу, так что технически я могу назвать себя профессиональным блогером, не так ли?
Во всяком случае, давайте не будем отвлекаться на эту мысль. Добро пожаловать в мой блог, если вы новичок, и добро пожаловать обратно в противном случае. Хорошо, что ваш компьютер запрашивает что-то с моего сервера.
Я предполагаю, что у вас есть некоторые базовые знания о Джулии. Следующие посты могут дать вам основы, если вы заинтересованы:

Кроме того, нужно знание базового синтаксиса.
Пример кода
Прежде чем мы начнем отладку, я хочу продемонстрировать это на некотором коде. Он достаточно короткий, чтобы показать его здесь, и содержит по крайней мере одну ошибку.
В качестве примера возьмем задачку ProjectEuler problem #21. Можете попробовать решить сами. Тут будет начало реализации возможной наивной версии.
Задача заключается в следующем: мы ищем дружественные числа меньше 10 000. Дружественное число определяется как элемент дружественной пары…
Пара двух целых чисел (a,b) дружна, если d(a) = b и d(b) = a, где d — сумма делителей, так что d(4) = 1+2 = 3.
Дана дружная пара — a = 220 и b = 284.
Мы могли бы начать с функции, которая просто берет пару и решает, является ли она дружественной.
function is_amicable(a, b)
    sum_divisors(a) == b && sum_divisors(b) == a
end

Джулия всегда возвращает выходные данные последнего выполненного выражения в функции. Это означает, что ключевое слово return не обязательно.
Затем нам понадобится функция sum_divisors
function sum_divisors(a)
    result = 0
    for i = 1:a
        if a % i == 0
            result += i
        end
    end
    return result
end

которая вызывается так
julia> is_amicable(220, 284)
false

Возможно, вы заметили ошибку, но если нет, то, вероятно, лучше не искать ее сейчас. Вместо этого следуйте по пути отладки.
Отладка с помощью Debugger.jl в REPL
Этот пост показывает вам два различных варианта отладки, и первый вариант может быть выполнен в REPL или в вашей IDE, то есть VSCode.
В этом разделе я объясню, как работать с отладчиком на REPL. (Debugger.jl)
julia> ] add Debugger
julia> using Debugger

Вы можете глянуть в пост про менеджер пакетов, если что-то не ясно.
julia> @enter is_amicable(220, 284)
In is_amicable(a, b) at REPL[7]:1
1  function is_amicable(a, b)
>2      sum_divisors(a) == b && sum_divisors(b) == a
3  end
About to run: (sum_divisors)(220)
1|debug>

Я набил @enter is_amicable(220, 284), чтобы получить этот вывод. Кстати, я только что скопировал две функции, которые я определил ранее, в REPL. С другой стороны, Вы можете создать для этого файл amicable.jl и использовать Revise и include (см. REPL and Revise.jl).
В случае файла номера строк, вероятно, более полезны.
Я вернусь через секунду...
julia> using Revise
julia> includet("amicable.jl")
julia> using Debugger
julia> @enter is_amicable(220, 284)
In is_amicable(a, b) at /home/ole/Julia/opensources/blog/2020-10-27-basics-debugging/amicable.jl:1
1  function is_amicable(a, b)
>2      sum_divisors(a) == b && sum_divisors(b) == a
3  end
About to run: (sum_divisors)(220)
1|debug>

Готово. Хорошо, теперь как уже упоминалось, в конце мы собираемся запустить sum_divisors(220).
Последняя строка 1|debug> дает нам возможность исследовать дальше, прыгая по коду, в том числе и низкоуровневому, и много чего еще всякого интересного.
Можно посмотреть полный список команд: Debugger.jl commands
Вы также можете ввести ? в режиме отладчика и нажать клавишу enter, чтобы увидеть список команд
Давайте начнем с n — шаг к следующей строке.
1|debug> n
In is_amicable(a, b) at /home/ole/Julia/opensources/blog/2020-10-27-basics-debugging/amicable.jl:1
1  function is_amicable(a, b)
>2      sum_divisors(a) == b && sum_divisors(b) == a
3  end
About to run: return false

Значит sum_divisors(220) != 284. Мы, вероятно, хотим перейти к вызову sum_divisors(220).
Мы всегда можем выпрыгнуть из сеанса отладки с помощью q, а затем начать все сначала
Начнем снова с @enter is_amicable(220, 284) и используем s для шага в функцию
1|debug> s
In sum_divisors(a) at /home/ole/Julia/opensources/blog/2020-10-27-basics-debugging/amicable.jl:5
  5  function sum_divisors(a)
> 6      result = 0
  7      for i = 1:a
  8          if a % i == 0
  9              result += i
10          end
About to run: 0
1|debug>

А дальше продолжаем использовать n, но вы, вероятно, можете себе представить, что это займет некоторое время.
Какие еще инструменты у нас есть, чтобы проверить, что происходит?
Некоторые из вас могут подумать: Хорошо, мы должны, по крайней мере, выяснить, что мы возвращаем, и мы можем просто вызвать sum_divisors(220). Это, вероятно, правильно, но не показывает возможности отладчика. Давайте представим, что мы имеем доступ только к режиму отладчика и не можем просто вызвать функцию.
В общем, этот способ узнавания нового, скрывая то, что мы уже знаем, довольно эффективен.
Я думаю, что пришло время, чтобы представить силу точек останова.
Вместо того, чтобы ползти по программе строка за строкой, часто разумно перейти к определенной точке, запустив код до тех пор, пока эта точка не будет достигнута.
Вы можете сделать это с помощью bp add, а затем указать файл, номер строки и возможное условие. Вы можете увидеть все параметры с помощью ? в режиме отладки.
В наших интересах будет поставить bp add 12. После этого мы можем использовать команду c, которая расшифровывается как continue (до точки останова).
1|debug> c
Hit breakpoint:
In sum_divisors(a) at /home/ole/Julia/opensources/blog/2020-10-27-basics-debugging/amicable.jl:5
  8          if a % i == 0
  9              result += i
10          end
11      end
>12      return result
13  end
About to run: return 504

Итак, теперь мы знаем, что оно возвращает 504 вместо 284. Теперь мы можем использовать `, чтобы перейти в режим Джулии. (Я знаю, что это вроде как запрещено нашими правилами, но время от времени это имеет смысл, и мы видим, что мы находимся в 1|julia>, а не в julia>, так что я думаю, что все в порядке...)
504-284 — это не самый сложный расчет, но мы можем использовать julia, чтобы сделать это за нас, не выходя полностью из режима отладки, используя:
1|debug> `
1|julia> 504-284
220

Похоже, мы нашли ошибку. Мы добавляем само число к результату, но оно на самом деле не считается за множитель.
А это значит, что мы можем сделать:
function sum_divisors(a)
    result = 0
    #for i = 1:a
    for i = 1:a-1
        if a % i == 0
            result += i
        end
    end
    return result
end

чтобы избежать эту проблему.
Да, я знаю, что мы можем избежать большего количества чисел, чтобы быть быстрее
Мы можем выйти из режима вычислений с помощью backspace, а затем q, чтобы выйти из режима отладки. Запускаем
julia> is_amicable(220, 284)
true

и видим, что мы выковыряли этот баг.
Давайте запустим его в последний раз в сеансе отладки и посмотрим на переменные. Снова перейдем к точке останова c и запустим
1|debug> w add i
1] i: 219
1|debug> w add a
1] i: 219
2] a: 220

Теперь мы видим переменные. Если мы снова нажмем c, то снова перейдем к точке разрыва (для очередного вычисления sum_divisors(284) == 220).
Мы можем снова использовать букву w, чтобы увидеть список переменных в области видимости:
1|debug> w
1] i: 283
2] a: 284

Есть еще несколько способов поиграть, например, шагнуть в код, показать низкоуровневый код и многое другое. Этого должно быть достаточно для знакомства.
В следующем разделе я хочу привести вам тот же пример с помощью редактора кода visual studio с расширением julialang.
Использование VSCode
Я думаю, что большинство разработчиков Julia используют VSCode IDE и, по крайней мере, иногда, vim, emacs или еще что-то такое неудобное… Ладно, это, наверное, просто слишком неудобно для меня
Определенно пришло время переключиться на VSCode с Atom/Juno, поскольку расширение Julia теперь разработано для VSCode вместо Atom.
Поскольку это IDE, имеет смысл иметь более визуальный отладчик, чем тот, который описан в предыдущем разделе.

Он довольно прост в навигации, и по умолчанию вы получаете больше выходных данных.
Чтобы начать сеанс отладки, вы нажимаете на кнопку с ошибкой и воспроизводите знак слева, пока у вас открыт файл julia.
Я добавил последнюю строку is_amicable(220, 284), так как VSCode просто запускает программу.
Вы можете добавить точку останова, щелкнув слева от номера каждой строки.
Я сделал снимок экрана после того, как сделал эти шаги, и последним шагом было нажатие на кнопку отладки.
Через несколько секунд сеанс отладки приостанавливается по мере достижения точки останова. С левой стороны можно увидеть локальные переменные в этой позиции. Это этап после того, как я исправил ошибку, так что вы можете видеть, что возвращается правильный результат "284". Однако вы также получаете значение для a и i.
Короче, все то же, что мы делали раньше с нашими переменными, но там нам пришлось вручную добавлять их.
Теперь мы также можем вручную добавлять выражения для наблюдения. Это можно сделать в части Watch ниже Variables, которая находится за пределами скриншота. Довольно приятно иметь возможность добавлять точки останова одним щелчком мыши, а также иметь локальные переменные, показанные слева по умолчанию.
Вы можете спросить себя: Ну, на самом деле это не два способа отладки, не так ли? Это примерно одно и то же, только с другим графическим интерфейсом.
Это правда! Вот почему я сейчас перехожу к следующему разделу поста
Infiltrator.jl для скорости
Существует одна огромная проблема с отладчиком Julia, которая решается по-разному различными пакетами. Проблема в том, что отладчик работает в интерпретируемом режиме, что делает его очень медленным. Если вы отлаживали код C++, то знаете, что отладчик там тоже работает медленнее, чем выполнение, но для Джулии это, на мой взгляд, огромная проблема.
Можно перейти в скомпилированный режим с отладчиком, но это экспериментально, и, по крайней мере, для меня он никогда не останавливался на точке останова.
Некоторые другие пакеты пытаются исправить эту проблему, делая какую-то причудливую магию, но я лично большой поклонник Infiltrator.jl. Правда, некоторое время не было никаких обновлений, и у меня есть некоторые проблемы с ним, но мне нравится сама идея.
Это также один из тех проектов с менее чем 100 звездами. Я хочу подтолкнуть его к этой вехе, так что если вам нравится то, что вы видите в этом разделе, пожалуйста, щелкните им звездочку.
Infiltrator.jl идет совершенно другим путем. Прежде всего, вам нужно немного изменить свой код. Он предоставляет макрос @infiltrate. О боже, как я люблю это название
Макрос примерно такой же, как и предыдущая точка останова. Всякий раз, когда достигается нужная строка, открывается новый вид режима REPL. Это немного усложняет переключение между режимом отладки и обычным режимом запуска, так как вам нужно добавить или удалить макросы @infiltrate, но я думаю, что это нормально.
Я снова продемонстрирую это на примере разобранном выше. Подобного рода использование было в debugging ConstraintSolver.jl.
Я скопировал код сверху и просто добавил using Infiltrator и @infiltrate.
using Infiltrator
function is_amicable(a, b)
    sum_divisors(a) == b && sum_divisors(b) == a
end
function sum_divisors(a)
    result = 0
    for i = 1:a-1
        if a % i == 0
            result += i
        end
    end
    @infiltrate
    return result
end
is_amicable(220, 284)

При запуске кода с include("amicable.jl") получаем:
Hit `@infiltrate` in sum_divisors(::Int64) at amicable.jl:14:
debug>

Это означает, что мы знаем, какая точка останова была достигнута, и видим тип переменной, которую мы назвали sum_divisors. Однако в отличие от Debugger.jl мы не видим кода.
Вы можете снова увидеть раздел справки с помощью ?
debug> ?
  Code entered is evaluated in the current function's module. Note that you cannot change local
  variables.
  The following commands are special cased:
    - `@trace`: Print the current stack trace.
    - `@locals`: Print local variables.
    - `@stop`: Stop infiltrating at this `@infiltrate` spot.
  Exit this REPL mode with `Ctrl-D`, and clear the effect of `@stop` with `Infiltrator.clear_stop()`.

Существует не так уж много команд, поэтому мы можем просто попробовать их одну за другой:
debug> @trace
[1] sum_divisors(::Int64) at amicable.jl:14
[2] is_amicable(::Int64, ::Int64) at amicable.jl:4
[3] top-level scope at amicable.jl:18
[4] include(::String) at client.jl:457

Таким образом, мы пришли из is_amicable и можем видеть типы, а также имя файла и номер строки, что полезно при использовании multiple dispatch.
debug> @locals
- result::Int64 = 284
- a::Int64 = 220

мы можем видеть локальные переменные, которые похожи на те, которые мы видели в представлении переменных VSCode.
Кроме того, мы можем просто вычислять выражения прям в этом режиме. Для Infiltrator.jl нет необходимости использовать `, чтобы переключиться на вычисления.
debug> a == 220
true

Вы можете использовать @stop, чтобы больше не останавливаться на этой вехе, и Infiltrator.clear_stop(), чтобы очистить эти остановки.
Давайте не будем использовать @stop сейчас, а вместо этого перейдем к следующей точке @infiltrate с помощью CTRL-D:
Hit `@infiltrate` in sum_divisors(::Int64) at amicable.jl:14:
debug>

таким образом, мы находимся в той же точке останова, но со вторым вызовом. К сожалению, невозможно использовать клавишу со стрелкой вверх, чтобы перейти через историю команд, которые мы использовали, так что нам нужно снова ввести @locals, если мы хотим их увидеть.
Я открыл такую тему и попытался разрешить ее сам, но мне это не удалось. Было бы здорово, если бы это когда-нибудь было реализовано, потому что я думаю, что было бы очень полезно отлаживать быстрее таким образом.
Давайте рассмотрим сравнение двух различных способов в следующем разделе.
Выводы
Мы посмотрели на Debugger. jl, который дает вам всю информацию, которая может понадобиться в вашем REPL.
Поэтому он не зависит от редактора.
Следующий инструмент, который я упомянул, был дебагер в VSCode который является в основном просто графическим интерфейсом для Debugger.jl. Вероятно, его удобнее использовать людям, которые любят работать с IDE. Хотя в Debugger.jl могут быть некоторые опции, которые недоступны в графическом интерфейсе, как это часто бывает.
Оба этих инструмента дают то преимущество, что вы можете шаг за шагом переходить через свой код и исследовать все, что захотите. Вы можете взглянуть на низкоуровневый код (по крайней мере, в Debugger.jl). Это, вероятно, то, что каждый ожидает сделать с отладчиком. Проблема просто в том, что он слишком медленный во многих случаях использования, например, когда вы хотите отладить свой собственный пакет с 1000 строками кода.
В таком случае Infiltrator.jl — это путь, по крайней мере для меня, и пока скомпилированный режим Debugger.jl работает недостаточно хорошо. У него есть и другие недостатки, так как не бывает чтоб все и сразу, но я думаю, что он часто превосходит использование println, поскольку можно распечатать все, что в данный момент интересует в данной точке останова, и увидеть все локальные переменные за один раз.
Спасибо за то, что дочитали и особая благодарность моим 10 покровителям!
Я буду держать вас в курсе Twitter OpenSourcES.
===========
Источник:
habr.com
===========

===========
Автор оригинала: Ole Kröger
===========
Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_otladka (Отладка), #_julia, #_julia_language, #_otladka (отладка), #_interpretator (интерпретатор), #_zhuki (жуки), #_pauki (пауки), #_programmirovanie (
Программирование
)
, #_otladka (
Отладка
)
, #_julia
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 25-Ноя 16:57
Часовой пояс: UTC + 5