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发展(如果需要,实现)
- 注意供应商特定的扩展
外部函数接口
大型程序经常需要与用其他语言编写的其他程序进行交互。不幸的是,这方面没有标准。
- 了解供应商的外部接口
- 尽量减少数据交换
- 注意可能产生问题的地方:
内存管理 信号处理