走过的世界无论多辽阔,心中的思念 仍是相同的地方 - 《回家》html
就在那个山顶听听来天堂的声音,就在那个村庄平息难以安静的灵魂 - 《带我到山顶》数据库
在VB中,方法分为函数和过程(Function/Sub),至关于C系语言有返回值方法和void方法。沿用单元测试的叫法,方法称其为单元。不少人会疑问,这个有什么好说的呢?实际上很有一些研究价值,请看分析。编程
老外有篇文章,《When A Method Can Do Nothing》。不少人表示看不懂,若一个方法什么也不作,要它干吗?其实若是细品文章中的一段话:c#
当你告诉一个对象作XX时,有时候它并不去作——发送执行XX的消息,意味着有时XX事件并不必定会发生。这要由这个对象决定。安全
若是说多态有任何意义,它至少在说对象是本身管理本身的。咱们给它发送一个消息,这是由它来决定应该去作什么。这是面向对象的核心,也是 Alan Kay(设计了SmallTalk,得到过图灵奖) 最初的对对象的认识之一——消息只是它们的信息传递。这种观点在现在并不占主导地位。架构
在面向过程/函数语言中,天然一切都围绕方法,每一个方法都有明确的职责,调用某个方法有明确的目的。app
到了面向对象编程,方法的业务逻辑则要服从对象的设计,方法的职责和目的要与服务于对象。这很天然,然而却不易深入理解。框架
When A Method Can Do Nothing正是揭示了这种的改变,并且是面向对象的核心之一。若是文章标题改成 Don’t Expect A Method To Do Anything,或许更好理解。不要期望你调用的方法会作什么,除了返回值。编程语言
脱离外部单元的依赖,外部单元,还有外部单元的外部单元,也须要单元测试,造成级联,每一级均可以明确职责,实现隔离的测试。直至上溯到咱们项目代码边界,操做持久层或UI或框架代码。函数
方法的业务逻辑不该该依赖于它调用的方法,或者说调用的其余方法改变了什么,方法的业务逻辑都能处理,这是真正面向对象的方法。这其实是SOA(面向服务)思想在方法级别的应用,方法间只有消息(参数)传递的松耦合。
面向对象方法很容易被扩展、测试、重构,是IoC的很好补充。通常用于API上,尤为是UI和持久层。虽然这种方法只占所有方法的一小部分,但对整个系统就是点晴之笔。
方法是最小粒度的模块,全部业务处理步骤,均可以分为四类,参见下图:
一个方法存在的意义就在于最后的组装,将前四部分编织起来,融入数以千计甚至万计的组成一个复杂的业务逻辑网中。
目前C#等语言方法概念,仍然是沿袭面向过程语言。方法签名只能单向规定被调用的方式,除非看源代码,不然彻底不清楚其内部的运做方式。这就像一个黑盒子,虽然输入输出一目了然,但却可能有许多看不见的线,与外部状态关联。
从低耦合角度,应该把方法尽可能设计成让人放心的黑盒子,尽量避免对外部状态的依赖,系统的测试性/维护性/扩展性都会大大提升。然而在整个系统中,必须有模块要与外部状态打交道,除非是一个理想化的模型。好的设计只是封装得更好,限制得更少。即便一个方法看上去好像不操做全局上下文,可它一旦调用其余单元,就可能间接改变外部状态,致使意料以外的结果。
或许现实中真有那种建立时根本无须和外界打交道的对象(业务越复杂可能性越小)。多是我我的经验不够,还想象不到。
方法还与标准黑盒子有点不一样,只须包含加工处理逻辑,并不必定要有输出返回值或“产品”。用函数仍是过程,要根据对现实对象连系,以及业务逻辑的抽象程度。反映了系统模块间的连结方式,连结方式反映了业务逻辑的组织方式。何时用全局状态,何时用函数返回值,这是模块设计中必须不断权衡的问题。
这里的“产品”其实指编程语言能够描述的对象,若是一个处理过程不出“产品”,并不表明没有成果,好比修改全局状态,或者后面提到的操做Database/UI,调用第三方API等等,这些事不能也没必要描述为“产品”对象。由于咱们的项目和代码,是对现实世界的抽象,现实场景的一个角度和片段。只能尽量地模拟,而没法彻底描述。
上面提到了方法中各类元素,只有内部计算和封装每一个步骤的流程,是没有反作用的,这是方法真正的业务逻辑。
业务逻辑自己就是个很是抽象的词儿,咱们最常提到,也最习惯的业务逻辑概念,是从领域模型角度看的,对此阿彬同窗有一篇很是独到而精僻阐述其本质。他指出,业务逻辑不是数据库也不是UI,而是Domain(Business Logic) Layer的代码。
对于这些业务逻辑,充分的单元测试代码,既能说明需求,又能检查逻辑,一举多得,对于如今主流的敏捷开发更是不可或缺。
领域角度,相似于像数据库三级模式中的概念模式。DBMS自己也是一个软件系统,其内模式和外模式一样能够套用在通常系统上。
领域视角的系统概念层,对应领域模型的设计,代码上直接反映在BL层。而领域的内模式,则将UI层/持久层/Service等,全部与底层和外部交互的架构层都包括其中。
从方法角度看,业务逻辑的概念能够扩展到BL之外的层,这些架构层也有其自身的处理逻辑。但BL层逻辑仍然是核心,其余层的业务逻辑都是服务于BL层服,只是手段而不是目的。这些层的模块/对象/方法,都是能够隔离,能够模拟,能够替代成不一样框架实现的。
好比系统中日志处理模块,能够将日志保存到Server的Event Logger,也能够存到数据库,两种方式虽然数据相同,但读写策略不一样。 这些策略虽然不是标准的Business Logic,但能够称之为扩展或广义的业务逻辑。
BL层的方法和其余层方法,结构上没有特别之处。只有一种特殊状况,除BL外的其余层,都应该有一个边界,边界内是咱们的代码,“外面的世界”是无限的第三方框架和服务,处于边界的方法,是沟通咱们代码,与底层框架/操做系统/第三方服务的桥梁。
应用程序的边界方法会调用许多外部API,平台服务的边界方法会提供API供外部调用。
你们都知道,代码逻辑中处理边界的状况要特别当心注意。对于边界方法也是,要处理可靠性(好比网断了)、安全性、性能方面的要求,这些一样也能够看做扩展的业务逻辑。采用优秀的框架,如WCF,每每能封装并下降实现这些要求的代码负担。
通常而言,代码越容易被测试,也很容易被修改,也越健壮。总之软件各项标准很大程序上都是统一的。模块/类/方法,各个粒度的代码都适用。
过去的DB和UI框架,不少难以封装抽象,如Asp.Net的控件绑定。现在随着MVC、MVVM、Respostory架构的推广,状况已经大为改观,出现了许多Mock框架。将领域业务和实现业务隔离,避免常常边界操做致使性能时间损失,更有助于推广Unit Test。
现代每一个平台,每一个团队都提倡,敏捷开发更离不开单元测试,但是为何你们仍是时常很迷茫,无从下手呢。根本缘由在于:单元测试验证机制彻底靠手工,没法准确反映单元(方法)间的联系和约束,并且随业务逻辑变化须要维护。
举个例子,有下面一个ER图,也可做为领域模型,描述学生/讲师/课程间的关系。
假设有个BL层方法,计算学生选的课,生成一个课程表,方法会调用DAL层返回学生和课程信息。如今需求提出要课程表要加上讲师信息,DAL层采用了EntityFramework,因此讲师信息做为课程的外键属性,不会自动加载。很多人包括我,常常会忘掉加Include方法。这种状况下单元测试若是不更新,增长讲师Null断言,也就没法检测出这种逻辑错误。
将来的编程语言或框架,必需要能支持检测这些约束。还有个问题是,对于课程的讲师信息,其实应该是非空外键属性,只是因为性能考虑,对现实抽象妥协而留空。这种状况的Null很容易与真正业务上的Null混淆。
Spec#语言是C#扩展,提供了方法约束和不可空对象的支持。看下一个求平方根的方法:
int ISqrt(int x){ requires 0 <= x; //参数非负 ensures //检查结果偏差 result*result <= x && x < (result+1)*(result+1); { int r = 0; while ((r+1)*(r+1) <= x) invariant r*r <= x; { r++; } return r; }
多是语法有点繁琐,Spec#没流行起来,但这个探索应该是编程语言将来的方向。而非空对象的特性,我以为面向对象语言都应该引入,这会使《WhenA Method Can Do Nothing》提到的Null-Object模式变得流行起来,将带来更接近真实,更容易理解的业务逻辑。
理想的完美系统,每一个方法职责绝不含糊,每一个方法都清晰可测的,方法的外部依赖都应该封装成接口或其余形式的抽象。对一个单元测试,除了BL层那些不会产生反作用的方法,应该Mock全部外部调用。再单独对每一个外部调用进行单元测试。由于一个API可能被多个业务逻辑调用,而只须要一次就能够验证其正确性。
预计单元测试将来的发展方向,应该也分红不一样的架构层,会和项目业务部分同样重要的地位。
胡扯了很多,该结尾了。其实本文的大部分东西,都是写以前未想到的。能思考出这些东西,仍是很开心的。本文和以前一篇框架和应用的文章,做为下一篇随笔的铺垫。最后祝全部人新春快乐,马到成功!