当前位置:  开发笔记 > 编程语言 > 正文

用于参数检查和其他偏执狂的常见Lisp习语?

如何解决《用于参数检查和其他偏执狂的常见Lisp习语?》经验,为你挑选了1个好方法。

这个问题涉及编码约定,最佳实践和生产风格,关键任务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似乎并不容易找到这个问题的答案.



1> Rainer Joswi..:

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在调试器中有命令.


我可能会补充:CHECK-TYPE断言还可以帮助编译器优化代码.许多CL编译器都"聪明",足以推断出,如果`x`已通过`(check-type x(整数0 10))`,那么他们可以优化该函数中的后续代码,仅用于从0到10的整数通常,你从这些声明中获胜两次.
推荐阅读
个性2402852463
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有