附录A 致谢
Erlang系统、OTP库集、还有Erlang内置的所有应用都是许多人进行了大量的工作后的集体成果。
致谢部分总是很难写的,因为我不想弄丢任何对系统有贡献的人,也不想出任何差错——我将力图做到准确,但是如果还有任何疏忽或差错,我先在这里表示歉意。
首先,我要感谢当初的“管理者”们,他们使得一切成为可能。我在Ericsson时候的领导Bjarne Däcker大力支持我们的工作,为了维护我们而积极地斗争——谢谢你,Bjarne。Torbjörn Jonsson(Bjarne的领导)也为了维护Bjarne而积极地斗争,谢谢你,Torbjörn,我从你身上学到了许多东西。Jane Walerud(即Open Source Erlang的幕后策划者),Bluetail的常务懂事,是她教会我如何经营一家小公司。
现在轮到Erlang的开发者的一个大名单了。Erlang团队的最初成员有我、Robert Virding和Mike Williams。当初我编写了Erlang的编译器,Robert编写了库,Mike编写了JAM仿真器。Robert和我写过多个版本的Erlang仿真器,大部分是用Prolog写的,后来Mike用C重写了一遍。两年后,Claes Wikström(我们叫他Klacke)加入了进来,为Erlang增加了分布式的能力,Bogumil Hausman这时候发明了改进版的Erlang虚拟机,即BEAM1。
还有CSLab的许多成员先后加入到我们的项目中,用Erlang编写了许多程序,后来又去做其它的事情去了。Carl Wilhelm Wellin写了yecc,Klacke和Hans Nilsson写了mnesia和mnemosyne。Tony Rogvall和Klacke给Erlang语言增加了二进制语法,使得Erlang可以如此漂亮地支持网络编程。Per Hedeland的耐心相当好,回答了我所有Unix方面的愚蠢的问题,确保了我们的系统能够一直工作的很好,并且他主动解决了Erlang仿真器中的一些顽疾。Magnus Fröberg编写了调试器。Torbjörn Törnkvist编写了接口生成器,使得Erlang可以与C交互。
当Erlang从CSLab搬出来, OTP诞生的时候,我们的团队扩大了,并且进
1 Bogdans Erlang Abstract Machine,即Bogdans Erlang虚拟机。
181
行了改组。Magnus Fröberg,Martin Björklund和我设计了OTP的库结构和behaviour的结构,OTP的behaviour库是在我们实验室多年的思想积累之上编写的。Klacke之前就编写过一个类似于gen_server的“通用服务器”,Peter Högfelt之前也写过另一个通用服务器,是监督树模型的早期的版本。关于进程监督的许多思想来源于Peter在移动服务器项目中的工作。
我离开Ericsson之后,系统的日常维护和开发交给了新一带的程序员们。Björn Gustavsson维护着仿真器模块,Lars Thorsén、Kenneth Lundin、Kent Boortz、Raimo Niskanen、Patrik Nyblom、Hans Bolinder、Richard Green、Håkan Mattsson和Dan Gudmundsson维护着OTP库集。
而现在呈现在我们的用户面前的Erlang/OTP系统,则是从与我们的忠实的用户们的互动中,已经得到了非常显著的提高。
我们的第一个用户群,即用Eralng构建第一个正式产品的团队是:Mats Persson、Kerstin Ödling、Peter Högfeld、Åke Rosberg、Håkan Karlsson和Håkan Larsson。
在AXD301项目中,Ulf Wiger和Staffan Blau在首次用Erlang实现运营商级的应用做了许多意义非凡的开创性工作。
无论是Ericsson内部的用户,还是Ericsson之外的用户,都做得非常棒。英国的“one-2-one”公司(现在是T-mobile公司)的Sean Hinde简直是“一个人的Erlang工厂”。
最后要说,Erlang的mailing list也是我们的灵感和勇气的源泉。今天无论是任何人想要了解Erlang的任何事,他们只需要“问一下Erlang的mailing list”,就可以很快得到一个准确而全面的回答。感谢mailing list中的所有人,特别是那些素未谋面,确有过很长很长的有意思的email来往的人。
最后,感谢我在SICS的朋友和同事们,感谢Seif Haridi教授,是他审阅了这篇论文。感谢Per Brand,是他鼓励我写这篇论文的。感谢分布式系统实验室的所有成员们,与他们的讨论给我留下了深刻的印象。
谢谢大家!
182 183
附录B 编程规范和约定
用Erlang进行程序开发的
编程规范和约定1
K Eriksson, M Williams, J Armstrong
1996 年 3 月 13 日
摘要
本文描述了用Erlang编写系统的一些编程规范和建议。
注释
本文档只是一个初步文档,并不完整。
对EBC的“Base System”的使用在这里并没有做要求,如果要使用“Base System”,那么就应该在设计的很早的阶段就遵守它。这些要求已经写入了1/10268-AND 10406 Uen“MAP-Start and Error Recovery”一文。
1 这里发表的是对Ericsson内部文档(EPK/NP 95:035)重新整理后的版本——原文已经作为Open Source Erlang的配套资料的一部分公开发布。
184
目录
1 目的.................................................................................................................................187
2 结构和Erlang术语............................................................................................................187
3 软件工程原则...................................................................................................................188
3.1 从一个模块导出的函数越少越好........................................................................188
3.2 尽量降低模块间的依赖........................................................................................188
3.3 将公用的代码放入库中........................................................................................189
3.4 将“复杂的”或“脏的”代码隔离到单独的模块中........................................189
3.5 不要对调用者如何使用函数调用的结果作出任何假设....................................190
3.6 抽象出代码的共用样式或行为............................................................................191
3.7 自顶向下................................................................................................................191
3.8 不要优化代码........................................................................................................191
3.9 牢记“最小惊诧”原则..........................................................................................191
3.10 尽力消灭副作用..................................................................................................192
3.11 不要让私有数据结构从模块中“泄漏”出来..................................................192
3.12 使代码达到最大确定性(deterministic).........................................................195
3.13 不要“防御式”编程..........................................................................................196
3.14 用设备驱动隔离硬件接口..................................................................................196
3.15 do与undo都在一个函数里做...............................................................................196
4 错误处理...........................................................................................................................198
4.1 将错误处理代码和正常情况代码分离................................................................198
4.2 标明错误内核........................................................................................................198
5 进程、服务器和消息.......................................................................................................198
5.1 将一个进程的实现放在一个模块中....................................................................198
5.2 利用进程来组织系统............................................................................................199
5.3 注册进程................................................................................................................199
5.4 给系统中的每个真正的并发活动注册唯一一个并行进程................................199
5.5 每个进程应该只有一个“角色”........................................................................199
5.6 在服务器和协议处理器中尽可能地使用通用函数............................................200
5.7 给消息打上标签....................................................................................................200
5.8 清掉未知消息........................................................................................................201
5.9 编写尾递归的进程处理函数................................................................................202
5.10 接口函数..............................................................................................................203
5.11 超时......................................................................................................................204
5.12 捕获退出信号(trapping exits)........................................................................204
6 一些Erlang的特别约定....................................................................................................204
6.1 使用record作为标准数据结构..............................................................................204
6.2 使用选择器(selector)和构造器(constructor)..............................................204
6.3 给返回值打标签....................................................................................................205
6.4 使用catch和throw时要极其小心..........................................................................205
6.5 使用进程字典(process dictionary)时要极其小心...........................................206
6.6 不要使用import.....................................................................................................207
6.7 导出(export)函数..............................................................................................207
185
7 特别的词汇和风格约定...................................................................................................208
7.1 不要编写深度嵌套的代码....................................................................................208
7.2 不要编写很大的模块............................................................................................208
7.3 不要编写很长的函数............................................................................................208
7.4 不要编写很长的代码行........................................................................................208
7.5 变量名....................................................................................................................208
7.6 函数名....................................................................................................................209
7.7 模块名....................................................................................................................209
7.8 程序格式保持一致风格........................................................................................210
8 代码文档化.......................................................................................................................210
8.1 注明代码归属........................................................................................................210
8.2 提供代码中对规格说明的引用............................................................................211
8.3 将所有错误文档化................................................................................................211
8.4 将消息中的所有的标准数据结构文档化............................................................211
8.5 注释........................................................................................................................211
8.6 注释每个函数........................................................................................................212
8.7 数据结构................................................................................................................213
8.8 文件头,版权........................................................................................................213
8.9 文件头,修订历史................................................................................................213
8.10 文件头,描述......................................................................................................213
8.11 不要注释过时代码——删除它..........................................................................214
8.12 使用一个源代码控制系统..................................................................................214
9 最普遍的错误...................................................................................................................214
10 必备文档.........................................................................................................................215
10.1 模块描述..............................................................................................................215
10.2 模块描述..............................................................................................................215
10.3 进程......................................................................................................................216
10.4 错误消息..............................................................................................................216
186
1 目的
本文罗列了在使用Erlang说明和编写软件系统时应该考虑的一些方面。本文并不尝试对与Erlang的使用没有太大关系的一般的需求和设计活动给出一个完整的描述。
2 结构和Erlang术语
Erlang系统被划分成模块(module)。模块由函数(function)和属性(attribute)组成。函数要么只能在模块内部可访问,要么被导出(exported),即能够被其它模块中的函数调用。属性由“-”开头,被放在模块的开头处。
用Erlang编写的系统,所有的工作(work)都是由进程(process)来完成的。一个进程是一个可以使用其他模块中函数的作业(job)。进程之间通过发送消息(sending message)来进行通信,一个进程可以决定准备接收什么消息,而其它的消息就排队等待,直到进程准备好接收它们。
一个进程可以通过与其它进程建立连接(link)来监控其它进程的存在。当一个进程终结时,会自动向与之相连的进程发送一个退出信号(exit signal),收到一个退出信号以后,一个进程的默认行为就是终结掉自己,并向与之相连的所有进程传播该退出信号。一个进程可以通过捕获退出信号(trap exits)来改变这种默认行为,这样可以把发送给一个进程的所有退出信号转换成正常消息。
纯函数(pure function)是一个不管处于什么样的上下文中,只要使用的参数相同,就会返回相同结果的函数。这也是我们平常期望的一个数学函数的行为。一个非纯函数的函数我们说它具有副作用(side effects)。
如果一个函数做了下面的这些事情,一般就会产生副作用:a)发送一个消息;b)接收一个消息;c)调用exit;d)调用任何会改变一个进程的环境或操作模式的BIF(例如get/1、put/2、erase/1、process_flag/1等等)。
警告:这篇文档中就包含有坏代码的例子。
187
3 软件工程原则
3.1 从一个模块导出的函数越少越好
模块是Erlang的基本代码结构实体。一个模块可以包含有大量的函数,但是只有包含在导出列表中的函数才能够在模块外部被调用。从一个模块的外部看,模块的复杂性决定与它所导出的函数的个数。一个只导出一、两个函数的模块肯定比导出几十个函数的模块更容易理解。
用户希望一个模块的导出函数/非导出函数的比率越低越好,这样他只需要理解导出函数的功能就足够了。
还有,只要模块的外部接口保持不变,模块代码的编写者和维护者可以任意改变模块的内部结构。
3.2 尽量降低模块间的依赖
一个调用了许多不同模块中的函数的模块要比只调用了很少的几个模块中的函数的模块要难以理解得多。
这是因为每次我们修改一个模块的接口时,我们必须要检查所有调用了该模块的地方。降低这种模块间的相互依赖将会简化这些模块的维护工作。
我们可以通过减少一个特定模块调用的不同模块的数量来简化我们的系统结构。
还要注意,模块间的调用依赖关系最好形成的是树型结构而不是环状结构。例如:
188
而不要是:
3.3 将公用的代码放入库中
公用的代码应该放入库中。这个库应该是相关的函数的集合,应该努力使得库中包含的是同类型的函数。因此,像一个叫lists的库只包含对列表操作的函数,这是一个好的决策;而一个叫lists_and_maths的库既包含对列表的操作的函数,也包含有数学运算的函数,就不是一个好的决策。
最好的库函数是没有副作用的。含有带副作用的函数的函数的库限制了其可复用性。
3.4 将“复杂的”或“脏的”代码隔离到单独的模块中
一个问题的解决通常需要综合用到净代码和脏代码,那么就把净代码和脏代码放入彼此隔离的模块中。
脏代码是指做了一些脏事情的代码。如: