把记录作为主要的数据结构。记录是Erlang 4.3 时才引入的一种带标记的元组(参看 EPK/NP 95:034)。它类似于 C 语言中的 struct
或 Pascal 中的 record
。
如果记录将用于多个模块,它的定义应放在这些模块所包括的一个头文件中(带有 .hrl
后缀)。如果记录只用在一个模块中,则它的定义应位于定义模块的文件的最前面位置处。
Erlang 的记录特性可以保证数据结构跨模块的一致性,因此在模块间传递数据结构时,接口函数应使用记录。
使用记录特性所提供的选择器(selector)和构造函数(constructor)来管理记录实例。不要使用明显假定记录是一个元组的匹配。范例如下:
demo() -> P = #person{name = "Joe", age = 29}, #person{name = Name1} = P,% 使用匹配,或者...... Name2 = P#person.name. % 像这样使用 selector不要像下面这样编程:
demo() -> P = #person{name = "Joe", age = 29}, {person, Name, _Age, _Phone, _Misc} = P. % 不要这样做6.3 使用带标记的返回值
使用带标记的返回值。
不要像下面这样编程:
keysearch(Key, [{Key, Value}|_Tail]) -> Value; %% Don't return untagged values! keysearch(Key, [{_WrongKey, _WrongValue} | Tail]) -> keysearch(Key, Tail); keysearch(Key, []) -> false.
{Key, Value}
不含有 false 值。下面是正确的方法。
keysearch(Key, [{Key, Value}|_Tail]) -> {value, Value}; %% Correct. Return a tagged value. keysearch(Key, [{_WrongKey, _WrongValue} | Tail]) -> keysearch(Key, Tail); keysearch(Key, []) -> false.6.4 慎重使用
catch
和 throw
千万不要在搞不清用法的情况下使用 catch
和 throw
!要尽可能少地使用它们。
复杂与非信任输入(从外部环境中所输入的内容,而非来自于可信的程序时)可能会导致代码很多深层次位置的错误,当程序处理这些类型的输入时,catch
与 throw
就显得非常有用了。关于这一点,典型的例子就是编译器。
千万不要在搞不清用法的情况下使用 get
和 put
等函数!尽量少用它们。
引入一个新的参数,就可以重写使用进程字典的函数。
范例如下:
不要像下面这样编程:
tokenize([H|T]) -> ...; tokenize([]) -> case get_characters_from_device(get(device)) of % Don't use get/1! eof -> []; {value, Chars} -> tokenize(Chars) end.正确的方法是:
tokenize(_Device, [H|T]) -> ...; tokenize(Device, []) -> case get_characters_from_device(Device) of % This is better eof -> []; {value, Chars} -> tokenize(Device, Chars) end.使用
get
和 put
会容易导致,在同样输入下函数的结果却随情况不同而改变。这使得代码难以阅读,因为代码已经失去了确定性。调试也变得很复杂,因为使用 get
和 put
的函数不仅是自身参数的函数,而且还是进程字典的函数。Erlang 中的很多运行时错误(比如 bad_match
)就包括函数的参数,但从来不包括进程字典。
不要使用 -import
,使用它会让代码变得难以阅读,这是因为无法弄清函数定义所在的模块。使用 exref
(交叉引用工具)查找模块依赖。
一定要搞清楚某个函数之所以导出的原因。这种原因可能包括以下几种:
apply
、spawn
调用,但只限于本模块内部调用。
使用不同的 -export
进行分组,并相应对它们进行注释。范例如下:
%% 用户接口 -export([help/0, start/0, stop/0, info/1]). %% 模块间导出 -export([make_pid/1, make_pid/3]). -export([process_abbrevs/0, print_info/5]). %% 导出,只限于模块内部使用 -export([init/1, info_log_impl/1]).