-module(counter).
-export([run/0, counter/1]).
run() ->
S = spawn(counter, counter, [0]),
send_msgs(S, 100000),
S.
counter(Sum) ->
receive
value -> io:fwrite("Value is ~w~n", [Sum]);
{inc, Amount} -> counter(Sum+Amount)
end.
send_msgs(_, 0) -> true;
send_msgs(S, Count) ->
S ! {inc, 1},
send_msgs(S, Count-1).
% Usage:
% 1> c(counter).
% 2> S = counter:run().
% ... Wait a bit until all children have run ...
% 3> S ! value.
% Value is 100000
public class HelloWorld extends AbstractBehavior<HelloWorld.Greet> {
public static final class Greet {
public final String whom;
public final ActorRef<Greeted> replyTo;
public Greet(String whom, ActorRef<Greeted> replyTo) {
this.whom = whom;
this.replyTo = replyTo;
}
}
public static final class Greeted {
public final String whom;
public final ActorRef<Greet> from;
public Greeted(String whom, ActorRef<Greet> from) {
this.whom = whom;
this.from = from;
}
}
public static Behavior<Greet> create() {
return Behaviors.setup(HelloWorld::new);
}
private HelloWorld(ActorContext<Greet> context) {
super(context);
}
@Override
public Receive<Greet> createReceive() {
return newReceiveBuilder().onMessage(Greet.class, this::onGreet).build();
}
private Behavior<Greet> onGreet(Greet command) {
getContext().getLog().info("Hello {}!", command.whom);
command.replyTo.tell(new Greeted(command.whom, getContext().getSelf()));
return this;
}
}
class WindowFactory {
public Window createWindow(String title, int x, int y, int width, int height) {
...
}
public Window createWindow(String title, Rect rect) {
...
}
}
采用上述模块操作符可以模拟任何过去、现在和未来所发生的设计变化。例如可以抽取设计演化历史中的连续片段,然后用模块操作符描述每一步的变化。对于进行中乃至未来的设计来说,模块演化则是非确定的,采用公式(j6 X 2) - 1即可计算模块演化的所有可能路径,例如当系统中包含6个模块时,就有93311种演化可能。
假设DSM的元素数量为n,其中传播成本(Propagation cost,Pc)忽略元素所在的位置,假设直接依赖和间接依赖具有同等成本,然后计算所有元素的扇入或扇出数M,则Pc = M / n2。对于整个系统而言,扇入和扇出数是相等的,因此M可以任选其中一种进行计算。聚集成本(Clustered cost)把模块内和模块间的依赖进行区别计算,首先指定一个依赖阈值(通常是10%~100%间的数),并将DSM中被依赖次数超过该阈值的元素计入主控元素,然后根据以下条件计算每项依赖所包含的成本:
DependencyCost(i -> j | j is a vertical bus) = d
DependencyCost(i -> j | in same cluster) = d * n^λ
DependencyCost(i -> j | not in same cluster) = d * N^λ
其中d是表示是否存在i -> j依赖的二进制值,n指模块规模,N指DSM规模。λ是自定义参数。
除了通过依赖成本计算模块度,另一类方法是直接计算模块度。根据模块从内及外且依赖由强变弱的定义,[GG04]提出了一种通用的模块度计算方法,该方法的前提是DSM中已经包含了精确的模块化信息。当DSM中不包含模块化信息,或者需要直接计算系统的实际依赖复杂度时,可采用奇异值模块度指数(Singular Value Modularity Index,SMI)[KO11]。该方法通过对DSM进行奇异值分解,然后计算奇异值的下降率从而表示系统模块度。以下面三种典型的结构模式为例:
关注点分离(Separation of concerns, SoC),即把注意力集中在某个方面,而非与其它无关方面相混淆。该原则最初来源于Dijkstra对计算领域中科学性思维属性的探索[EWD74],后来被引入软件设计领域,用于强调软件模块之间应具有尽可能少的特性重叠。
一次且仅一次(Once and only once),也称Don’t repeat yourself,DRY。指任何知识都应在系统中有唯一、清晰和权威的表示。该原则适用于许多软件设计领域。例如单一数据源(Single source of truth, SSOT),指系统中的任何数据元素都只有一份,任何其它具有相同定义的数据都是该唯一元素的引用,目的是保证数据的完整性和规范性。
保持简洁(Keep it simple stupid, KISS),简洁意味着易于理解、维护和扩展。KISS旨在强调简洁性对于系统设计的重要性。实际上简洁性还普遍适用于设计、建筑和哲学等其它领域,例如Simplicity is the ultimate sophistication,Brevity is the soul of wit,Less is more,Make simple tasks simple以及Simplify, then add lightness等。
懒元素(Lazy element),指某些设计元素(例如类、实体)只有非常简单的功能,甚至使用一、两行代码就能清楚表现该元素的特性,那么他们就没有单独存在的必要。与之表现形式相反,但具有统一思想的夸夸其谈未来性(Speculative generality),或者称作大设计先行(Big design up front, BDUF),则表示发生了过度设计。
过高的圈复杂度(High cyclomatic complexity),指代码中存在过度复杂的控制流图。圈复杂度通常用代码中的线性逻辑路径数进行表示。假设用N表示代码中的基本区块(指不包含任何控制分支的连续代码片段)数,E表示连接基本区块的边数,P表示连通子图数,那么圈复杂度M可用公式M = E - N + 2P进行计算。过高的圈复杂度可能意味着过度复杂的逻辑或缺少结构性的代码。
尽管前述大部分的代码味道都有“程度”的概念,使其具体的应用仍然依赖实际经验,但仍然有一些可以遵循的规则。例如,针对重复代码的事不过三规则(Rule of Three),这里虽然中文成语中的数量是虚指,但具体应用时可以作为实际阈值,也就是说当重复代码出现三次时,就应考虑采取相应解决方案了。当然这种规则只能作为初步判断条件,进一步仍然需要结合设计原则进行恰当分析。
public final class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
public final class Singleton {
private Singleton() {}
private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
OO的重要特性之一是通过继承实现增量式扩展,这种特性能帮助我们有效管理系统复杂性。但是正如面向对象——概念与建模一文中提到的,继承除了提供模块能力外还兼有类型概念,这种概念兼有利弊,特别是令继承的适用场景受到一定限制,不合适的继承非但不能有效管理复杂性,反而会带来更多问题,此时就需要考虑用组合替代继承实现模块扩展,这就是组合优于继承(Composition over inheritance)长期占据主流观点的原因。接下来要讨论的设计模式或采用继承或采用组合的方法来解决增量扩展的问题。
观察与测量模式(Observations and measurements),该模式用于表示真实世界对象的信息。以数值类属性为例,体重是人的基本属性之一,一般用数值类型存储。然而如果领域要求更精确一些,数值类属性还需要额外的单位信息,这时就需要构建抽象数据类型,这里称为Quantity。几乎一定会用到Quantity类型的领域包括财务(货币单位)、医疗(剂量单位)等。
库存与会计(Inventory and accounting),一种跟踪企业资金流动的模式,主要用于财务会计、库存和资源管理等领域。通常用账户表示具有实际价值及其历史记录的实体,例如银行账户、库存账户等。对于账户中的每两条历史记录(指包含存和取的“两腿交易”),都能够对应一个交易,后者用于记录价值流动的细节。如果问题域存在多腿交易,例如一次交易超过了两个参与方,则允许一个交易引用多条历史记录。从模型的角度看,两腿交易属于多腿交易的一种特殊情况,因此后者的灵活性更好。如下图所示:
任何金融市场的交易都有报价(Quote),多数情况下“报价”其实包含两个值——买入价和卖出价,这被称为双向价格(Two way pricing)。相应地,在某些问题域中也存在单向价格(One way pricing),后者可以被看作是双向价格的一种特殊情况。为了同时支持这两种报价类型,需要构建一个更高抽象层次的报价类型,如下图所示:
在复杂的问题域中,为了识别出所有概念类,需要对领域进行实体分解。一般方法是从用例描述中的名词短语直接提取概念类,但是受限于自然语言的随意性,把任何提取出的词汇都映射为概念类极有可能出错。除了进一步澄清所提取的词汇外,还可以借助概念类分类表(Conceptual class category list),后者是对特定领域中常用概念类的一种分类形式,可用于对照用例描述中的词汇获取预设的概念类。例如在商场中,一次交易可以包含Sale或者Payment等概念,而在机场可能还涉及Reservation等概念。通过上述方法可以获取候选的概念类列表,例如在表示交易的领域模型中,我们可以提取出Store、Register和Sale等作为候选概念类列表。需知候选概念类列表并非是唯一确定的,其具体的覆盖范围应结合上下文综合考虑。
由前文编程范式我们知道,对象作为最基础的编程概念广泛存在于各类编程范式中,采用这种范式的语言被称为基于对象语言(Object based language)。本文讨论的面向对象(Object oriented)的概念则首次出现在1967年诞生的Simula 67,其中除了对象概念本身外,还进一步提出了继承和多态这两个重要特性,成为此后长期影响学术界的语言之一。而面向对象在工业界的兴起则始于上世纪80年代,以Smalltalk和C++的相继发明为标志,深刻影响了此后近四十年的软件工程。
用例图(Use case diagrams),表示系统的功能需求。用例(Use case)是一种对用户与系统或系统自身交互的描述。用例更注重用户的一般目标,与用户场景(User scenario)不同,后者详细描述了用户与系统的每一步交互(这里的用户不仅指人类),如果交互过程中发生分支则产生新的场景。也就是说,一个用例包含了许多用户场景。下图是一个用例图的例子:
Robert W. Floyd在1978年图灵奖颁奖礼上如是说[RWF79],这里所说的“方法论”即编程范式(Programming Paradigms)。范式一词源自Thomas S. Kuhn的《科学革命的结构》,Kuhn认为过去数个世纪的科学变革,实质上是主宰范式的更替,而这些范式却都曾在一段时期内常自认为能够独立揭示科学的内涵[TSK62]。具体到程序设计领域,范式表示为编程活动中存在的公共风格或方法论。例如,结构化编程可看作是上世纪70年代的主宰范式,但并非唯一。即使是忠实的拥趸也必须承认,结构化编程在解决某些问题时并不理想,于是持续有诸如分支限定(branch-and-bound)、分治(divide-and-conquer)、状态机(state machine)等其他更高层级范式的提出。或许有人认为使用较为底层的编程范式照样可以完成绝大部分任务,但却低估了软件的运行效率和经济效益等重要因素。因此Floyd认为,编程技术得以持续进步的重要前提即是新范式的发明、阐释和交流。
编程范式的概念组成(Conceptual Composition of Programming Paradigms)
非确定性和时间(Nondeterminism and time)。当程序具有并发和状态等概念时,问题就会变得更加复杂,这是因为并发所引起的线程时序是非确定的,而状态的改变也会因此变得不稳定。这里需要强调,非确定性本身不会带来问题,只有当程序中的非确定性具有可观测性时,进而引发的竞争条件才会导致潜在问题。
12345678
declare
C={NewCell 0}
thread
C:=1
end
thread
C:=2
end