четверг, 2 декабря 2010 г.

Замечания по отладке в SBCL и Slime

Оригнальный текст: https://gist.github.com/659495#file_sbcl_debugging_notes.txt, Nikodemus Siivola

0 Умение сокращать размеры тестового набора - это важный навык, не зависящий от языка или инструментария. Это целое искусство - сокращать до премлемых размеров количество примеров, приводящих к появлению ошибок, неверных результатов (например, моменты, связанные с указанием полного пути или текущей директории, выполнением кода в Slime или вне него). С одной стороны, чем меньше тестовых примеров, тем лучше, но здесь надо соблюдать осторожность. Если вы в состоянии распознать баг за пару минут каким-либо другим методом, очевидно, глупо тратить несколько часов на сжатие тестового набора. Однако, если другими методами вы не в состоянии этого сделать, придется смириться с временными затратами.

1 Осторожное программирование. Речь не только о том, как избегать ошибок в коде, но и о том, как делать его легкоотлаживаемым. Избегайте IGNORE-ERRORS, не позволяйте компилятору безмолвно проглатывать ошибки. Иногда это может быть полезным, но чем больше таких мест в коде, тем сложнее использовать *BREAK-ON-SIGNALS*. Избегайте SAFETY 0 как чумы - он скроет множество огрехов. Не используйте DEBUG 0 - от этого вы не получите выгоды. Определяйте PRINT-OBJECT методы для собственных объектов, давайте им имена. Никогда не употребляйте INTERRUPT-THREAD или WITH-TIMEOUT, пока вы не будете точно уверены в том, что делаете, и пока не поймете, почему их лучше не использовать.

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

3 Изучайте возможности инструментария. Здесь стоит остановиться подробнее.

   3.0 M-. - это отличная вещь. Нажимая 'v' в отладочном окне Slime, вы также попадаете в участки кода, предоставляющего достаточно много информации для отладки, но M-. можно использовать везде.

   3.1 Slime Inspector - один из главных отладочных инструментов. Пригодится он вам или нет - зависит от задачи, но будет полезно познакомьться с его возможностями, они того стоят.

   3.2 Обратная трассировка (backtrace) в SBCL на настоящий момент реализована не лучшим образом, но ее использование может принести определенные плоды. Просто загляните (вызовите любую ошибку) в REPL и выясните, что происходит. Поразвлекайтесь с штатным отладчиком SBCL и модным Slime Debugger, прежде чем будете пользоваться ими на практике, и тогда они перестанут казаться вам враждебными. О том, как пользоваться обратной трассировкой, я напишу в другой раз.

   3.3 Разберитесь с *BREAK-ON-SIGNALS* и TRACE. Не забывайте, что SBCL предоставляет некоторые расширения возможностей TRACE.

   3.4 Пошаговое выполнение по сути не является отладочным инструментом. Я считаю, что это всего лишь средство наблюдения за ходом исполнения. Иногда это может быть полезным в отладке, но только при компиляции кода с DEBUG 3, когда (STEP (FOO)) доставит вас в нужное место.

   3.5 Пользуйтесь M-RET (macroexpand) в Slime. Попробуйте (SETF *PRINT-GENSYM* NIL) и посмотрите, что будет происходить. Это несет с собой потенциальные опасности, но зато может позволить легко копировать и вставлять код, в который раскрывается ваш тестовый пример. (Замена раскрытий макросов в пакете COMMON-LISP, как правило, нецелесообразна, но в пользовательских пакетах может быть очень полезным.)

   3.6 Если ничего не помогло, попробуйте (sb-ext:restrict-compiler-policy 'safety 3) и (sb-ext:restrict-compiler-policy 'debug 3), после чего перекомпилируйте весь код. Теперь отладка станет легче. Если ошибка исчезла,  либо (а) вы ошиблись с типами (или допустили похожую ошибку) в коде, собранном с SAFETY 0, и она не терялась благодаря IGNORE-ERRORS или HANDLER-CASE, либо (б) вы обнаружили баг в SBCL: вообще говоря, политика компилятора не должна изменять поведение кода, за исключением нескольких допускаемых ANSI случаев при использовании SAFETY 3, или же когда высокий уровень отладки препятствует оптимизации хвостовой рекурсии. Знающие Scheme объяснят вам, что это может иметь значение.

   3.7 DISASSEMBLE не есть штатное средство отладки, но иногда может быть полезным. Это зависит от сути имеющейся проблемы.

4 Расширения отладочной печати. Часто это простейшее средство, которым можно воспользоваться. Не стыдитесь этого.

   4.1 Пользуйтесь специальными переменными:

  (LET ((*DEBUG* :MAGIC)) ...) и, где-либо в коде,

  (WHEN (EQ :MAGIC *DEBUG*) (PRINT (LIST :FOO FOO)))

Будучи ленивым, я обычно использую глобальные переменные для управления отладочной печатью, как в примере выше. Это позволяет использовать ее только при необходимости, определять, на каком участве кода происходит вызов и т.д.

   4.2 Не бойтесь вставлять в отлаживаемый код (break "foo=~S" foo) или подобные вызовы.

   4.3 SB-DEBUG:BACKTRACE все же можно использовать.