数据结构论坛

注册

 

发新话题 回复该主题

dgiot编程规范 [复制链接]

1#
北京治白癜风症最好的医院 http://baidianfeng.39.net/index.html

#编程规范

##摘要

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

注意

本文档是一份初步且不完全的文档。本文档不包含对于EBC的“BaseSystem”的需求,然而,如果需要使用"BaseSystem",在设计初期必须遵循此类需求。这些需求包含在文档"1/-ANDUen“MAP-StartandErrorRecovery”"中。

##目标

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

##结构和Erlang术语

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

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

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

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

发送一条消息

接收一条消息

调用exit

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

警告:本文包含了一些badcode的例子,这些例子用这种"Uglyfont"书写。

##SW工程原则

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

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

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

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

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

###尽量减少模块间的依赖

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

而不是/p>

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

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

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

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

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

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

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

```

do_something(Args)-

casecheck_args(Args)of

      ok-

        {ok,do_it(Args)};

      {error,What}-

String=format_the_error(What),

ioormat("*errors\n",[String]),%%Don’tdothis

error

  end.

正确的写法如下/p>

do_something(Args)-

casecheck_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.

```

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

```

NewQ=[],%Don’tdothis

Queue1=queue:add(joe,NewQ),

Queue2=queue:add(mike,Queue1),....

```

这不是一种好的做法,由于用户——

需要知道queue代表一个列表

实现者不能改变队列内部所指代的含义(这里他们可能需要在后面提供该模块一个更好的版本)。更好地实现应该是:

```

-module(queue).

-export([new/0,add/2,fetch/1]).

new()-

[].

add(Item,Q)-

  lists:append(Q,[Item]).

fetch([H

T])-

  {ok,H,T};

fetch([])-

  empty.

```

现在我们可以这样写:

```

NewQ=queue:new(),

Queue1=queue:add(joe,NewQ),

Queue2=queue:add(mike,Queue1),...

```

这是一个更好的实现并且修复了之前的问题。现在假设用户需要知道队列的长度,他们将会尝试这样写:[source,]

Len=length(Queue)%Don’tdothis

所以他们知道queue代表一个列表。同上,这也不是一个好的实现并且会导致代码难于维护和理解。如果需要知道queue的长度,就必须在模块中添加一个length函数,这样:

```

-module(queue).

-export([new/0,add/2,fetch/1,len/1]).

new()-[].

add(Item,Q)-

  lists:append(Q,[Item]).

fetch([H

T])-

  {ok,H,T};

fetch([])-

  empty.

len(Q)-

  length(Q).

```

现在,用户可以调用queue:len(Queue)来代替。这里我们说我们已经抽象出了queue所有的细节(queue实际上是所谓的抽象数据结构)。

为何我们会遇到这些问题?抽象出内部细节的实践使得我们可以无须修改调用这些函数的模块而仅仅改变其实现细节。所以,一个更好的实现如下:

```

-module(queue).

-export([new/0,add/2,fetch/1,len/1]).

new()-

  {[],[]}.

add(Item,{X,Y})-%Fasteradditionofelements

  {[Item

X],Y}.

fetch({X,[H

T]})-

  {ok,H,{X,T}};

fetch({[],[])-

  empty;

fetch({X,[])-

  %Performthisheavy

分享 转发
TOP
发新话题 回复该主题