优秀的Lisp编程风格教程:第五章(译文)

原文链接:https://norvig.com/luv-slides.ps

5. 大型程序设计

了解软件开发的各个阶段:

  • 收集需求
  • 架构
  • 组件设计
  • 实现
  • 调试
  • 微调

它们可以重叠。探索性编程的要点是最小化组件设计时间,快速实现以确定体系结构和需求是否正确。

了解如何组织一个大型程序:

  • 使用包
  • 使用defsystem
  • 把源代码拆解到多个文件
  • 大型文档
  • 可移植性
  • 错误处理
  • 与非lisp程序的接口

把源代码拆解到多个文件

以下因素影响如何将代码分解到多个文件:

  • 语言强加的依赖性
    宏、内链函数、CLOS类在使用之前
  • 分层设计
    隔离可重用组件
  • 功能分解
    将相关组件分组
  • 和工具兼容
    为编辑器、compile-file选择良好大小的文件
  • 分离OS/机器/供应商特定的实现

高效地使用注释

使用注释:

  • 解释基本原理。 不要只是记录细节;还要记录为理解代码的整体结构提供框架的基本原理、动机和隐喻。
  • 提供示例。 有时候一个示例比一堆文档更有价值
  • 与其他开发者进行交流! 在协作项目中,您有时可以通过将问题放入源代码中来提出问题。你可能会回来发现它已经回答了。把这个问题和答案留给以后也会疑惑的人吧。
  • 维护你的“TODO”列表。 在以后要回来做的注释上放一个特殊的标记:???!!!; 可以使用!!!!来表示高优先级。一些项目保留待办事项列表和更改日志,这些日志与源代码分开。
(defun factorial (n)
  ;; !!! What about negative numbers? --Joe 03-Aug-93
  ;; !!! And what about non-numbers?? -Bill 08-Aug-93
  (if (= n 0) 1
      (* n (factorial (- n 1)))))

文档:说出你想表达的含义(Say what you mean)

Q:你写代码的时候用过注释吗?

“很少,除了在开始的时候,然后我只对数据结构进行注释。我不会对代码本身进行注释,因为我觉得正确编写的代码是非常自我文档化的。”-- Gary Kildall

“我认为有两种类型的注释:一种是解释显而易见的东西,这些东西比没有价值更糟糕;另一种是当你解释真正复杂、令人费解的代码时。这是好的。我总是尽量避免使用复杂的代码。我尽量写出真正强大、清晰、干净的代码,即使它多出了5行代码。我几乎认为,你需要的注释越多,你的程序就越糟糕,它就会出问题。”-- Wayne Ratliff

“不要注释糟糕的代码,重写它。”-- Kernighan & Plauger

  • 描述系统的目的和结构
  • 描述每个文件
  • 描述每个包
  • 为所有函数添加文档字符串
  • 考虑自动化工具(manual
  • 编写代码,而不是注释

文档:过度注释

这32行必须记录一个主要系统:

; ====================================================================
;
; describe
; --------
;
; arguments : snepsul-exp - <snepsul-exp>
;
; returns : <node set>
;
; description : This calls "sneval" to evaluate "snepsul-exp" to
;               get the desired <node set>.
;               It prints the description of each <node> in the
;               <node set> that has not yet been described during
;               the process; the description includes the
;               description of all <node>s dominated by the <node>.
;               It returns the <node set>.
;
; implementation: Stores the <node>s which have already been describe
;                 in "describe-nodes".
;                 Before tracing the description of a <node>, it
;                 checks whether the <node> was already been describe
;                 to avoid describing the same <node> repeatedly.
;                 The variable "describe-nodes" is updated by "des1".
;
; side-effects : Prints the <node>'s descriptions.
;
;                                    written: CCC 07/28/83
;                                    modified: CCC 09/26/83
;                                              ejm 10/10/83
;                                              njm 09/28/88
;                                              njm 4/27/89

(defmacro describe (&rest snepsul-exp)
  `(let* ((crntct (processcontextdescr ',snepsul-exp))
          (ns (in-context.ns (nseval (getsndescr
                                      ',snepsul-exp))
                             crntct))
          (described-nodes (new.ns))
          (full nil))
     (declare (special crntct described-nodes full))
     (terpri)
     (mapc #'(lambda (n)
               (if (not (ismemb.ns n described-nodes))
                   (PP-nodetree (des1 n))))
           ns)
     (terpri)
     (values ns crntct)))

问题:

  • 文档太长;失去大局观
  • 文档有误:describe(d)-nodes.
  • 文档是低效的:没有文档字符串
  • 文档是冗余的(参数列表)
  • 遮蔽Lisp的describe函数是个坏主意
  • 需要从宏中分离出函数
  • 缩写是模糊的

文档:注释

较好的

这不会处理crntct(不管它是什么)


(defmacro desc (&rest snepsul-exp)
  "Describe the node referred to by this expression.
  This macro is intended as an interactive debugging tool;
  use the function describe-node-set from a program."
  `(describe-node-set (exp->node-set ',snepsul-exp)))

(defun describe-node-set (node-set)
  "Print all the nodes in this node set."
  ;; Accumulate described-nodes to weed out duplicates.
  (let ((described-nodes (new-node-set)))
    (terpri)
    (dolist (node node-set)
      (unless (is-member-node-set node described-nodes)
        ;; des1 adds nodes to described-nodes
        (pp-nodetree (des1 node described-nodes))))
    (terpri)
    node-set))

移植性

使您的程序在您使用的环境中可以良好运行。

但请注意,您或其他人可能有一天会在其他环境中使用它。

  • 使用#+feature#-feature
  • 隔离与具体实现相关的部分
  • 维护一份源代码和多个二进制文件
  • 向dpANS CL发展(如果需要,实现)
  • 注意供应商特定的扩展

外部函数接口

大型程序经常需要与用其他语言编写的其他程序进行交互。不幸的是,这方面没有标准。

  • 了解供应商的外部接口
  • 尽量减少数据交换
  • 注意可能产生问题的地方:
    内存管理
    信号处理

猜你喜欢

转载自blog.csdn.net/zssrxt/article/details/134226644
今日推荐