Ответ 1
Общий Lisp - это язык, предназначенный для разработки больших и сложных приложений. Что в 80-х годах считалось крупными приложениями. Но из производственных систем у него появилось несколько средств для устранения ошибок и даже некоторая поддержка для проверки времени компиляции. Все еще много кода написано для прототипа программного обеспечения, исследовательских систем и/или личных целей. Вы не всегда находите высокий уровень качества. Также имейте в виду, что иногда очень строгая проверка может сделать код слишком строгим (например: много HTTP-клиентов отправят несоответствующие запросы, но это как есть, и нельзя легко отказаться от них, не теряя при этом большого количества возможных пользователей).
Посмотрите на некоторые примеры того, как Common Lisp помогает вам написать надежное программное обеспечение:
проверка сильной типизации и проверки времени выполнения
Мы ожидаем, что обычная система Lisp проведет проверку времени выполнения для каждой операции. Избегайте Lisp систем, которые этого не делают.
Если у вас есть числовая функция:
(defun foo (n x)
....
(bar ...))
(defun bar (a b)
(+ a b))
Если FOO
не проверяет аргументы, мы ожидаем, что в конце концов операция +
проверит аргументы. Во время выполнения появится ошибка, и будет запущен обработчик ошибок, который по умолчанию вызовет отладчик.
Подумайте об этом: все (большинство) операций будут проверяться во время выполнения. Все объекты имеют примитивный тег типа (целое число, строка, массив, бит-вектор, символ, поток,...), и во время выполнения тип в конечном итоге будет проверен.
Но мы ожидаем большего от времени выполнения Lisp:
- проверяет границы массива
- проверка типов слотов
- согласованность кучи в случае ошибок
- различные проверки против вредоносных операций, таких как переопределение стандартных функций, удаление общего пакета Lisp, арифметических ошибок и т.д.
Использование системы Lisp, которая не выполняет проверки типа времени выполнения, представляет собой огромную боль. Теперь Common Lisp позволяет объявлять части кода не выполнять проверки времени выполнения. Лучшая стратегия: найдите наименьший объем кода, где это можно сделать, не создавая риск (см. LOCALLY
).
Списки аргументов
Общий Lisp позволяет проверять список аргументов во время компиляции. Используйте его.
(defun foo (&key (n 1) (x 1.0))
...)
Теперь типичный компилятор поймает вызов типа (foo :y 2 :x 2.0)
с ошибкой: неверный аргумент ключевого слова :y
.
Пусть компилятор проверяет, что список аргументов имеет правильное количество аргументов и что используются правильные аргументы ключевых слов.
CLOS, Общая Lisp Система объектов
Используйте CLOS.
(defmethod foo ((n integer) (x float)) ...)
Если вы определяете метод, описанный выше, во время выполнения в теле метода n
будет целое число, а x
будет поплавка. Если вы вызываете FOO
с другими типами аргументов и не применяете никаких методов, тогда мы получаем ошибку времени выполнения.
Аналогичные слоты для примера: вы можете объявлять типы.
(defclass bar ()
((x :type float)
(n :type integer)))
Используйте общую реализацию Lisp, которая фактически проверяет эти объявления или записывает собственные проверки.
Также: не создавайте структуры данных на основе списков. Всегда упаковывайте их в классы и методы CLOS. Таким образом, вы получаете правильное количество проверок и возможностей самоанализа.
Проверить типы во время выполнения
Общие Lisp предоставляет макрос для проверки типа времени выполнения: CHECK-TYPE.
(defun foo (n x)
(check-type n integer)
(check-type x float)
(* (isqrt n) (sqrt x)))
Макрос CHECK-TYPE
позволяет проверять шрифт и даже исправлять ошибку.
CL-USER 27 > (foo 2000 5)
Error: The value 5 of X is not of type FLOAT.
1 (continue) Supply a new value of X.
2 (abort) Return to level 0.
3 Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 28 : 1 > :c 1
Enter a form to be evaluated: 5.0
Обратите внимание, что вы можете использовать типы, чтобы указать такие вещи, как интервал для чисел, размеры массива или аналогичные.
Например, это проверяет, что объект, привязанный к переменной a1
, представляет собой двумерный массив с размерами 3 на 3:
(check-type a1 (array * (3 3)))
Обратите внимание, что вы можете определить свои собственные типы с помощью DEFTYPE
с предикатами произвольного типа.
Используйте конструкторы Lisp, которые сигнализируют об ошибках
Например ecase
vs. case
:
CL-USER 37 > (let ((code 10))
(ecase code
(1 'fine)))
Error: 10 fell through ECASE expression.
Wanted one of (1).
ecase
автоматически сигнализирует об ошибке, если не указано условие.
Макрос ASSERT
позволяет нам проверять произвольные утверждения.
Общие Lisp предоставляет встроенный макрос ASSERT.
(defun foo (n x)
(assert (and (integerp n) (evenp n)) (n))
(assert (floatp x) (x))
(* (isqrt n) (sqrt x)))
Опять же, доступно определенное количество времени выполнения:
CL-USER 33 > (foo 2001 5.0)
Error: The assertion (AND (INTEGERP N) (EVENP N)) failed.
1 (continue) Retry assertion with new value for N.
2 (abort) Return to level 0.
3 Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 34 : 1 > :c 1
Enter a form to be evaluated:
2000
98.38699
Используйте CLOS для простого проектирования по контракту
(defclass bar ()
((n :type integer)
(x :type float)))
(defmethod setup-bar ((b bar) (n1 integer) (x1 float))
(with-slots (n x) b
(setf n n1 x x1))
b))
Теперь мы можем написать дополнительный метод, чтобы проверить, например, что n
больше, чем x
:
(defmethod setup-bar :before ((b bar) (n1 integer) (x1 float))
(assert (> n x) (n x)))
Метод: before всегда будет работать до основного метода.
Добавить проект по контрактной системе в CLOS
Для этого есть библиотеки. Quid Pro Quo - пример. Существует также более простая и более старая реализация DBC от Matthias Hölzl: Дизайн по контракту.
Расширенная обработка ошибок с системой условий
Введите типы условий:
(define-condition mailer-incomplete-delivery-error
(mailer-error)
((recipient-and-status-list :initarg :recipient-and-status-list
:reader mailer-error-recipient-and-status-list)))
Выше - новое условие, основанное на условии mailer-error
. Во время выполнения мы можем поймать код ответа SMTP и сигнализировать о таком состоянии.
Напишите обработчики и перезагрузите, чтобы справиться с ошибками. Это продвинулось. Обширное использование системы условий обычно указывает на лучший код.
Записать и проверить тесты
Во многих случаях надежный код нуждается в наборе тестов. Общий Lisp не является исключением.
Пусть пользователь сообщает об ошибках
Во многих реализациях Common Lisp можно получить объект условия ошибки, обратную трассировку и некоторые данные среды. Напишите их в журнал ошибок. Сообщите пользователю об этом. Например, LispWorks имеет команду :bug-form
в отладчике.