这个问题涉及编码约定,最佳实践和生产风格,关键任务Common-Lisp代码.我仔细阅读了Google的Common-Lisp样式指南(http://tinyurl.com/qfvnqcx),但没有找到任何明确解决我特定问题的内容,我通过示例表达,与C/C++,Java等形成对比.我还快速浏览了Github上的Common-Lisp代码库,我没有看到很多参数检查和中间值检查,我在C/C++,Java等中看到过.
在我的商店里,我们非常习惯于检查论点和其他价值观,并在论证不符合合同/先决条件等时提前退出.例如,考虑以下(设计,不完美,典型,但请 - 不要' t-waste-time-criticizing,micro-example,它预示着CL的例子):
ErrorCode o_symb_to_g_symb (char * symb, uint len) { if (len < 2) { return ERROR_LENGTH; } if (symb[0] != 'O' || symb[1] != '!') { return ERROR_SYNTAX; } char * result = (char *) malloc (len + 1); if (NULL == result) { return ERROR_MALLOC; } if (result != strncpy (result, symb, len + 1)) { return ERROR_STRNCPY; } result[0] = 'G'; return result; }
这与Doug Hoyte的第67页"Let Over Lambda"的代码大致相同,只是在整个过程中尽可能地检查(http://letoverlambda.com/).
(defun o!-symbol-to-g!-symbol (s) (symb "G!" (subseq (symbol-name s) 2))))
问题是Common Lisp中的实际生产代码是否进行了更多检查.例如,编写显式代码以检查s实际上是一个字符串并且实际上足够长并且实际上具有"O!"可能是合理的.作为前两个字符.
这段代码是否因为教学方法而绕过所有的偏执狂?在任务关键型生产部署中,相同的代码是否更有可能进行偏执检查(我对Github的CL代码的轻量级代表会建议"不")?如果现实世界的CL代码不倾向于偏执狂,为什么不呢?角落案例或详尽测试的做法是否比看上去更广泛?
简而言之,我对风格的差异感到非常困惑.真实世界的任务关键型C代码往往是超级偏执狂.我在CL中看不到相同的内容.也许我不是在寻找合适的代码库?也许我没看过合适的书?Googling似乎并不容易找到这个问题的答案.
Common Lisp是一种用于开发大型和复杂应用程序的语言.80年代被认为是大型应用.但它从生产系统中获得了几个处理错误的工具,甚至还有一些支持编译时检查的工具.仍然有很多代码是为原型软件,研究系统和/或个人目的而编写的.你总是找不到高质量的产品.还要记住,有时非常严格的检查会使代码过于严格(例如:许多HTTP客户端会发送不符合要求的请求,但就是这样,人们不能轻易拒绝它们而不会丢失大量可能的用户).
让我们看一下Common Lisp如何帮助您编写健壮的软件:
强类型和运行时类型检查
我们希望通常的Lisp系统会对每个操作进行运行时检查.避免使用没有的Lisp系统.
如果你有一个数字函数:
(defun foo (n x) .... (bar ...)) (defun bar (a b) (+ a b))
如果FOO
没有参数检查,我们期望最终+
操作将检查参数.在运行时会出现错误并运行错误处理程序,默认情况下会调用调试程序.
想一想:所有(大多数)操作都将在运行时进行检查.所有对象都有一个基本类型标记(整数,字符串,数组,位向量,字符,流,...),并且在运行时最终将检查该类型.
但是我们期望从Lisp运行时获得更多:
数组边界检查
插槽类型检查
错误时的堆一致性
针对有害操作的各种检查,例如重新定义标准函数,删除Common Lisp包,算术错误等.
使用不执行运行时类型检查的Lisp系统是一个巨大的痛苦.现在,Common Lisp允许我们声明代码的一部分不进行运行时检查.最佳策略:找到可以在不产生风险的情况下完成最少量的代码(请参阅参考资料LOCALLY
).
参数列表
Common Lisp允许在编译时进行一些参数列表检查.用它.
(defun foo (&key (n 1) (x 1.0)) ...)
现在,典型的编译器将捕获类似于(foo :y 2 :x 2.0)
错误的调用:错误的关键字参数:y
.
让编译器检查参数列表是否具有正确数量的参数,以及正在使用正确的关键字参数.
CLOS,Common Lisp对象系统
使用CLOS.
(defmethod foo ((n integer) (x float)) ...)
如果你定义一个像上面这样的方法,那么在运行时,方法体n
中将是一个整数,并且x
将是一个浮点数.如果FOO
使用其他参数类型调用并且没有应用任何方法,则会出现运行时错误.
类似于插槽:您可以声明类型.
(defclass bar () ((x :type float) (n :type integer)))
使用Common Lisp实现,它实际检查这些声明或编写您自己的检查.
另外:不要基于列表创建原始数据结构.始终将它们打包到CLOS类和方法中.这样您就可以获得适当数量的运行时检查和内省功能.
在运行时检查类型
Common 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
请注意,您可以使用类型来指定数字间隔,数组维度或类似内容.
例如,这将检查绑定到变量的对象a1
是尺寸为3乘3的二维数组:
(check-type a1 (array * (3 3)))
请注意,您可以DEFTYPE
使用任意类型谓词定义自己的类型.
使用Lisp构造信号错误
例如ecase
主场迎战case
:
CL-USER 37 > (let ((code 10)) (ecase code (1 'fine))) Error: 10 fell through ECASE expression. Wanted one of (1).
ecase
当没有子句匹配时,自动发出错误信号.
该ASSERT
宏允许我们检查任意断言.
Common 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
使用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)))
的:以前方法将总是运行前的主要方法.
将按合同设计的系统添加到CLOS
有图书馆.Quid Pro Quo就是一个例子.MatthiasHölzl还有一个更简单,更老的DBC实现:契约式设计.
条件系统的高级错误处理
写条件类型:
(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响应代码并发出这样的信号.
编写处理程序并重新启动以处理错误.那是先进的.条件系统的广泛使用通常表示更好的代码.
编写并检查测试
在许多情况下,健壮的代码需要测试套件.Common Lisp也不例外.
让用户报告错误
在许多Common Lisp实现中,可以获得错误条件对象,回溯和一些环境数据.将它们写入错误日志.让用户报告这些.例如,LispWorks :bug-form
在调试器中有命令.