DGIoT编程规范

编程规范

摘要

开源代码下载:https://gitee.com/dgiiot/dgiot
有问反馈给我们 https://gitee.com/dgiiot/dgiot/issues
商务合作vx:dgiotcs
QQ支持一群:346566935

本文档提供了如何使用Erlang开发软件系统的规则和建议。

注意
本文档是一份初步且不完全的文档。 本文档不包含对于EBC的“Base System”的需求,然而,如果需要使用"Base System", 在设计初期必须遵循此类需求。这些需求包含在文档"1/10268-AND 10406 Uen “MAP - Start and Error Recovery”"中。

目标

本文列出了使用Erlang编写软件系统需要考虑到的一些方面的内容。这里不会给出与使用Erlang无关的通用细节和设计活动的完整描述。

结构和Erlang术语

Erlang系统被划分为不同的模块(module)。模块由函数和属性组成。仅当函数在模块内或者被导出时才对调用该函数的其他函数所在模块可见。属性以"-"开头并且放在模块的起始位置。

在使用Erlang设计的系统中,具体工作由进程(processes)②完成。进程是一个可以使用不同模块中函数的任务。不同进程间通过消息传递进行通信。进程可以接收发送给它的消息,一个进程可以决定哪些消息已经准备好被接收。其他消息会被放入队列中,直到该进程已做好接收此消息的准备。

通过设置一个链接(link),一个进程可以监视另一个进程。当一个进程挂起时,它将自动给其被链接的其他进程发送退出信号(exit signals)。进程接收到退出信号的默认行为是挂起该进程,同时传递该信号给其链接的其他进程。通过捕捉信号,进程可以改变上述默认行为,这将使所有发送给某进程的退出信号转换成消息。

纯函数(pure function)是指无论调用该函数的上下文如何,给定相同参数都返回相同结果的函数。这与我们通常对数学函数的期待一致。一般说来,非纯函数有副作用(side effect)。 副作用通常发生在一个函数

发送一条消息

接收一条消息

调用exit

调用任何改变进程环境或操作方式的内置函数(BIF)(例如:get/1, put/2, erase/1, process_flag/2等)

警告:本文包含了一些bad code的例子,这些例子用这种"Ugly font"书写。

SW工程原则

从一个模块尽可能少地导出函数

模块是Erlang中的基本代码结构实体。一个模块可包含大量函数,然而只有包含在导出列表中的函数才能在该模块外调用。

从外部观察一个模块的复杂度取决于从该模块导出函数的数量。一个仅导出一两个函数的模块通常比导出大量函数的模块更易于理解。

导出/未导出函数比率低的模块正是其使用者仅需要理解从该模块导出函数功能时所需要的。

另外,代码的作者或维护者在模块中可以以任意合适的方式改变其内部结构,而其提供给外部的接口保持不变。

尽量减少模块间的依赖

调用更多其他模块的模块更加难以维护。 这是由于每当我们改变了一个模块的接口时,必须检查代码中所有调用该模块的地方。减少模块间的依赖可以简化维护这些模块时产生的问题。 我们可以通过减少被某一模块调用的其他模块的数量来简化系统结构。 同时要注意我们更希望模块间互相调用的依赖关系采用树状结构而非回环图。例如:

而不是:

将常用函数放入库中 常用的函数应该放在库中。库是相关函数的集合。应尽量保证库中包含的函数类型相同。所以像lists这样的库中仅包含操作列表的函数是一个好的选择,反之,一个库名为lists_and_maths包括操作列表和数学函数的集合则相反。 最好的库应该没有副作用。包含有副作用函数的库限制了其可重用性。

将复杂的(tricky)和脏代码(dirty)分离在不同的模块中 通常混合使用简洁和肮脏的代码可以解决问题。但请将二者分别放在不同模块中。

脏代码是做了一些使代码变脏事情的代码。例如: 使用了进程字典③

出于奇怪的目的使用了erlang:process_info/1 做了任何你不想(却不得不做)的事情

尽量集中精力使简洁的代码数量最大化同时使脏代码数量最小化。 分离脏代码并注释它们或者在文档中清晰描述这些代码的副作用和相关的问题。

不要假设调用者会对函数返回结果会如何处理

不要对函数被调用的原因或调用者将对返回结果的操作作出假设。 例如,假设我们调用某函数(原文 run time)期间传入了非法参数,函数实现者不应假设传入非法参数后其调用者所做的事情。 所以我们不应编写这样的代码:

do_something(Args) ->
case check_args(Args) of
			ok ->
				{ok, do_it(Args)};
			{error, What} ->
String = format_the_error(What),
io:format("* error:~s\n", [String]), %% Don’t do this
error
	end.
正确的写法如下:

do_something(Args) ->
case check_args(Args) of
	ok ->
		{ok, do_it(Args)};
	{error, What} ->
		{error, What}
end.
	error_report({error, What}) ->
format_the_error(What).

前一种情况下总是使用标准输出打印错误信息,而后一种则将错误类型返回给应用程序。应用程序此时可以根据错误信息决定下一步做什么。 在必要时,应用程序可以通过调用error_report/1将错误信息描述转换成可打印字符串并打印。但这也许不是我们所需要的 —— 在任何情况下,对结果处理的决定权应交由调用者。④

抽象出代码常用模式或行为

无论何时,当代码中有两处或更多相同代码形式,尽量把这些相同的部分单独放在一个函数中并调用它,而不是在不同的地方放置相同的代码段。

如果在代码中两个或更多地方有类似形式(甚至是相同)的代码,有必要花时间看看是否其中一个问题可以稍作修改使两处相同,然后写一些更小的代码(函数)来描述两者的差异。

编程时避免“复制”、“粘贴”,使用函数(代替他们)。

自顶向下

采用自顶向下的方法编程,而不是自底向上(从细节开始)。通过定义函数原型,自顶向下是一种成功处理实现细节的好方法。由于设计上层代码好时下层代码所代表的内容是未知的,所以代码应独立于其所代表的内容。

不要优化代码

不要在开始时优化代码。首先保证其正确,其次(必要时)使它运行的更快(同时保证代码正确)。

采用 “最小惊讶(astonishment)定律”

对用户而言,系统响应时应保证产生最少的意外 —— 例如,当用户做了某种操作后,应能够对系统反应作出预测且不会对产生的结果感到惊讶。

这与一致性有关,不同模块以相似方式处理问题的一致的系统要比各模块以不同的方式处理问题的系统更易于理解。

如果你对函数运行结果感到惊讶,那么不是函数处理了错误的问题就是其命名有误。

尽量减小副作用

Erlang包含一些有副作用的原始设计。由于这些函数会导致环境永久的改变所以它们不容易被重用,同时在使用它们之前你必须清楚地了解进程的状态。

尽可能写不包含副作用的代码。

使纯函数的数量最大化。

将有副作用的代码整合在一起并清晰地在文档中说明其副作用。

稍稍注意一下,大部分代码可以避免产生副作用。这将使系统易于维护、测试和理解。

不要使模块的私有数据结构泄露

通过一个简单的例子可以最好的说明这一点。我们定义一个简单的模块并命名为queue —— 用来实现队列。

-module(Queue).
-export([add/2, fetch/1]).
add(Item, Q) ->
lists:append(Q, [Item]).
fetch([H|T]) ->
	{ok, H, T};
fetch([]) ->
	empty.

这里使用列表来实现队列,不幸的是这样做用户必须知道队列代表一个列表。一个使用以上代码典型的程序代码段如下:

NewQ = [], % Don’t do th
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值