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

(本文为草稿,校对时间待定)

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

6. 其他

说话算数
  • 不要误导读者
    预料到读者的误解
  • 使用适当程度的特异性
  • 小心那些声明
    不正确的声明会破坏代码
  • 一一对应

坏的声明:只是一个虚构的例子

lisp 复制代码
(defun lookup (name)
  (declare (type string name))
  (if (null name)
    nil
    (or (gethash name *symbol-table*)
        (make-symbol-entry name))))

应该为(declare (type (or string null) name))

命名规范:保持一致

在命名中保持一致:

  • 保持大小写一致:
    大部分更倾向like-this,而非LikeThis
  • *special-variable*
  • +constant+(或某种惯例)
  • Dylan使用<class>
  • 考虑structure.slot
  • -p?; !n; ->-to-
  • 动宾式:delete-file
    对象属性:integer-length
    比较name-filefile-name
    不要使用宾语-动词或定语-宾语结构
  • 参数顺序一致
  • 区分内部和外部功能
    不要混合&optional&key;小心使用1或2个&optional参数
    使用关键字保持一致(keytestend
命名规范:明智地选择名字

明智地选择名字:

  • 最小化的缩写
    大部分的单词有多种可能的缩写,但只有一个正确的拼写。把名字拼出来,这样更容易阅读、记忆和发现。
    一些可能的例外:char、demo、intro和paren。这些单词在英语中越来越像真正的单词了。一个很好的测试(针对以英语为母语的人)是:你会在对话中大声说这个单词吗?我们之前的示例crntctprocesscontextdescr就无法通过这个测试。
  • 不要用另一个局部变量来遮蔽一个局部变量。
  • 清楚地显示已更新的变量。
  • 避免有歧义的名字:使用previousfinal而不是last
符号技巧:第0列中的括号

大多数文本编辑器将第0列中的左圆括号作为顶级表达式的开始。除非你提供反斜杠,否则在第0列的字符串内的左圆括号可能会混淆编辑器:

lisp 复制代码
(defun factorial (n)
  "Compute the factorial of an integer.
\(don't worry about non-integer args)."
  (if (= n 0) 1
      (* n (factorial (- n 1)))))

许多文本编辑器将第0列中的(def视为定义,但不会将其他列中的(def视为定义。所以你可能需要这样做:

lisp 复制代码
(progn
(defun foo ...)
(defun bar ...)
)
多行字符串

在多行字符串作为文字常量的情况下,例如:

lisp 复制代码
(defun find-subject-line (message-header-string)
  (search "
Subject:" message-header-string))

考虑使用读取时求值和对format的调用:

lisp 复制代码
(defun find-subject-line (message-header-string)
  (search #.(format nil "~%Subject:") message-header-string))

当同一个字符串被多次使用时,考虑使用全局变量或已命名常量:

lisp 复制代码
(defparameter *subject-marker* (format nil "~%Subject:"))

(defun find-subject-line (message-header-string)
  (search *subject-marker* message-header-string))

对于较长的格式化字符串,你可以使用<Return>@<Return>来缩进后续行。下面两种形式做同样的事情:

lisp 复制代码
(format t "~&This is a long string.~@
             This is more of that string.")
This is a long string.
This is more of that string.

(format t "~&This is a long string.~
           ~%This is more of that string.")
This is a long string.
This is more of that string.

后一种语法允许您轻松缩进固定数量:

lisp 复制代码
(format t "~&This is a long string.~
           ~% This is more of that string, indented by one)
This is a long string.
This is more of that string, indented by one.
符号技巧:多行注释

避免在字符串中使用#||#,因为这会在以后尝试去注释掉这样一个字符串时产生混淆。反斜杠再一次起到帮助:

好的

lisp 复制代码
(defun begin-comment () (write-string "#\|"))
(defun end-comment () (write-string "|\#"))

这意味着您可以稍后注释掉包含这些字符串的部分,而无需编辑字符串本身。

如果您的编辑器提供支持(注释区域和取消注释区域命令),那么最好使用显式的";;"注释。这样读者就不会对哪些部分被注释掉了感到困惑。

一些危险信号

以下情况是"危险信号"。它们通常是问题的症状,尽管从技术上讲,它们中的大多数确实发生在完全合法的情况下。如果你看到这些危险信号之一,你的代码并不一定有问题,但你仍然应该谨慎地进行:

  • 任何对eval的使用
  • 任何对gentemp的使用 (没有已知的好用途)
  • 任何对append的使用
  • 在一个使用了setf或调用了mentmacroexpand的宏里缺失&environment参数。
  • error类型编写状况处理器(包括对ignore-errors的使用)。
  • 任何对c...r函数的使用,除了caarcad...r以外。(这里的"..."全是d
避免常见错误
  • 总是提示输入(否则用户不知道发生了什么)
  • 理解defvardefparameter
  • 理解fletlabels
  • 理解多值
  • 理解宏(如上所示)
  • 更改宏或内联函数后重新编译
  • 使用#'(lambda ...),而不是 ``(lambda ...)`
  • 记住#'f只是(function f)
  • 根据需要使用:test #'equal
  • 确保声明是有效的
  • 制定破坏性函数的原则
破坏性函数

对破坏性函数有以下原则:

  • 大多数程序在可以证明其他地方不需要参数时(例如函数 nconc 的部分结果时),都会使用破坏性更新。
  • 否则,就假定那些参数不能被修改
  • 假定结果会被修改
  • 为了安全起见,主要接口通常会复制它们发出的结果。
  • 请注意,破坏性更新可能会减慢分代回收 GC 的速度。
小错误

坏的

lisp 复制代码
(defun combine-indep-lambdas (arc-exp)
  (apply #'*
         (mapcar #'eval-arc-exp (cdr arc-exp))))
  • apply超出调用参数限制
  • mapcar产生垃圾
  • cdr违反数据抽象

好的

lisp 复制代码
(reduce #'* (in-arcs arc-exp) :key #'eval-arc-exp)

学会使用累加器:

lisp 复制代码
(defun product (numbers &optional (key #'identity)
                (accum 1))
  "Like (reduce #'* numbers), but bails out early
   when a zero is found."
  (if (null numbers)
    accum
    (let ((term (funcall key (first numbers))))
      (if (= term 0)
          0
          (product (rest numbers) key (* accum term))))))

考虑下:

lisp 复制代码
(collect-fn 'number (constantly 1) #'* numbers)
多任务和多进程
多任务(时间片)

花时间构建代码以使其能够在多任务处理下正常工作是合理的。

尽管还没有可移植的标准,但许多商业 Lisp 实现都具有此功能。它与现有的语言语义非常吻合。

  • 注意全局状态,如 setq 和属性列表。
  • 使用without-interruptswithout-abortswithout-preemption等工具同步进程
多进程(真正的并行性)

考虑真正的并行性,但如果事情突然变得并行化,请不要浪费大量时间来构建程序以使其正常运行。

将串行程序变成并行程序是一项不平凡的改变,它不会偶然发生(例如,由于 Common Lisp 语义的一些一夜之间的变化)。 需要一种全新的语言来支持这一点; 你会有时间做准备。

意想不到的事情

墨菲定律:"如果事情可能出错,那就一定会出错。"

不要因为你很确定某件事永远不会发生而忽略了对这件事情的检查。即便如此,也不要忽略它们。从系统中得到"这不可能发生"的错误是很常见的,很明显,人们并不总是像他们想象的那样聪明。

阅读他人的代码

"你需要研究别人的作品。他们解决问题的方法以及他们使用的工具会让你以一种全新的方式看待自己的工作。" -- Gary Kildall

"我从看别人的节目中学到了很多"-- Jonathan Sachs

"我仍然认为,对编程能力的测试之一就是给程序员30页左右的代码,看他能多快地通读和理解。" -- Bill Gates

"准备(成为一名程序员)的最好方法是编写程序,并学习其他人编写的优秀程序。在我的例子中,我去了计算机科学中心的垃圾桶,捞出他们的操作系统清单。"-- Bill Gates

"你必须愿意阅读别人的代码,然后编写自己的代码,然后让其他人审查你的代码。" -- Bill Gates

  • Lisp Machine 操作系统
  • 互联网FTP网站(comp.lang.lisp 常见问题)
  • CMU CL 编译器和工具
  • 苹果电脑 Common Lisp 示例
示例:deftable

任务:使定义和使用表结构变得容易。

  • defstruct一样
  • 应该很快:内联函数
  • 应该可以处理一个或多个表
  • CLOS?
  • 操作?参数和返回值?
  • 默认值?变异还是返回?

分离用户代码和实现者代码,同时支持两者。

  • 定义新表实现的方法
  • 命名;包?
  • 记录的局限性?
  • 插桩?
  • 自动选择?

课程学习:

  • 捕获常见的抽象:表,其他?
  • 可以稍微小心地设计复杂的宏
  • 思考扩展的可能性
原型

Lisp允许您轻松地开发原型。

"做好舍弃第一个原型的准备,你往往需要这种舍弃。" -- Fred Brooks

"我在做任何事情之前都会考虑很多,一旦我做了一件事,我就不怕把它扔掉。程序员回头看一段代码,就像看一本书中糟糕的章节一样,然后毫不回头地把它删掉,这一点非常重要。" -- John Warnock

"不要太早决定;千万不要过早地做决定。保持一个比你认为你需要的更普遍的数量级,因为你最终会长期需要它。让某些东西快速工作,然后能够将其丢弃。" -- John Warnock

"所以我倾向于一次写几行然后试一试,让它工作,然后再写几行。我尝试在每次迭代中做最少的工作来进行真正的实质性更改。" -- Wayne Ratliff

"1-2-3从一个可工作的程序开始,并在其整个发展过程中一直是一个可工作的程序。" -- Jonathan Sachs

其他想法

学会打字。如果你每分钟打不到60个字,你就在阻碍自己。

此外,当你在一个复杂的项目上努力工作时,练习是很重要的。缺乏体育锻炼使大多数程序员疲惫不堪。它会导致精神敏锐度的丧失。 -- John Page

问:怎样才能成为一名优秀的程序员?

"怎样才能擅长任何事情?怎样才能成为一名优秀的作家?优秀的人是两个因素的结合:一种偶然的符合学科需要的心理,加上一种不愚蠢的心理能力。这是一种罕见的组合,但它一点也不神秘。一个好的程序员必须喜欢编程并对它感兴趣,所以他会努力学习更多。一名优秀的程序员还需要审美意识,外加负罪感,以及对何时违反这种审美意识的敏锐意识。负罪感迫使他更加努力地改进节目,使其更符合审美。" -- Bob Frankston

推荐书籍

介绍Common Lisp:

  • Robert Wilensky 的 《Common LISPcraft》
  • Deborah G. Tatar 的 《A Programmer's Guide to Common Lisp》
  • Rodney A. Brooks 的 《Programming in Common Lisp》

推荐和必备品:

  • Guy L. Steele 的 《Common Lisp: The Language, 2nd Edition》
  • ANSI通用Lisp标准草案
  • Harold Abelson 和 Gerald Jay Sussman 还有 Julie Sussman 的 《Structure and Interpretation of Computer Programs (Scheme)》

更进一步:

  • Patrick H. Winston 和 Berthold K. P. Horn 的 《LISP, 3rd edition》
  • Wade L. Hennessey 的 《Common Lisp》
  • Sonya E. Keene 的 《Object-Oriented Programming in Common Lisp: A Programmer's Guide to CLOS》
  • Eugene Charniak、Christopher K. Riesbeck、Drew V. McDermott 和 James R. Meehan 的 《Artificial Intelligence Programming, 2nd edition.》
  • Peter Norvig 的 《Paradigms of AI Programming: Case Studies in Common Lisp》

期刊:

  • 《LISP Pointers》。ACM-SIGPLAN 从 1987 年开始
  • 《LISP and Symbolic Computation》。从 1989 年开始
  • 《 Proceedings of the biannual ACM Lisp and Functional Programming Conference》。从 1980 年开始
相关推荐
程序媛小果4 分钟前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林10 分钟前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨24 分钟前
El表达式和JSTL
java·el
q5673152325 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java