程序员修炼之道——第二章 注重实效的哲学(一)

注重实效

  1. 重复的危害: 别做重复的事
  2. 正交性:
    – 按模块进行开发,每个模块负责该模块的部分,不要将其扩展到其他模块。
    – 将对代码有侵略的第三方接口统一封装成统一格式提高复用性。
  3. 可撤销性
  4. 曳光弹
  5. 原型与便笺(jian)
  6. 领域语言
  7. 估算

7.重复的危害

DRY- Dont Repeat Yourself
强加的重复:开发者觉得他们无可选择——环境似乎要求重复。
无意的重复:开发者没有意识到他们在重复信息。
无耐性的重复:开发者偷懒,他们重复,因为那样做更容易
开发者之间的重复:同意团队或者不同团队的几个人重复了同样的信息。

强加的重复

有时,重复似乎是强加给我们的,项目标准可能要求建立含有重复信息的文档。或者重复代码中的信息的文档,多个目标平台各自需要自己的编程语言、库以及开发环境,这会使我们重复共有的定义和过程,编程语言自身要求某些重复信息的结构、我们都在我们觉得无力避免重复的情形下工作过,然而也有一些方法,可用于把一项知识存放在一处,以遵守DRY原则,同时也让我们的生活更容易一点, 这里有一些这样的技术:
信息的多种表示:在编码一级,我们常常需要以不同的形式表示同一信息,我们也许在编写客户-服务器应用,在客户和服务器端使用了不同的语言,并且需要在两端都表示某种共有的结构,我们或许需要一个类,其属性是某个数据库表的schema(模型、方案)的镜像 你也许在撰写一本数,其中包括的程序片段,也正式你要编译并测试的程序。
发挥一点聪明才智,你通常能够消除重复的需要 答案常常是编写简单的过滤器或者大妈生成器 可以在每次构建(build)软件时,使用简单的代码生成器,根据公共的元数据表示构建多种语言下的结构,可以根据在线数据库schema、或是最初用于构建schema的元数据,自动生成类定义,本书种摘录的代码,由预处理在我们每次对文本进行格式化时插入,诀窍是让该过程成为主动的。
这不能是一次性转换,否则我们就会退回到重复数据的情况。
代码中的文档:程序员被教导说,要给代码加上注释:好代码由许多注释,遗憾的是,没有人教给他们,代码为什么需要注释:糟糕的代码才需要许多注释。
DRY法则教导我们,要把低级的知识放在代码中,它属于那里;把注释保留给其他高级的说明,否则,我们就是在重复知识,而每一次改变都意味着既要改变代码,也要改变注释,注释将不可避免地过时,而不可信任地注释比没有注释更糟糕。
文档与代码。你撰写文档。然后编写代码,有些东西变了,你修订文档,更新代码。文档和代码都含有同一个知识地表示,而我们都知道,在最紧张地时候——最后期限在逼近,重要地客户在呐喊——我们往往会推迟文档地更新
Dave曾经参与过一个国际电报交换机项目的开发,很容易理解、客户要求提供详尽的测试规范,并要求软件在每次交付时都通过所有测试,为了确保测试准确地反映规范,开发团队用程序方式、根据文档本身生成这些测试 ,当客户修订他们地规范时,测试套件会自动改变有一次团队向客户证明了,该过程很健全,生成验收测试在典型情况下只需要几秒钟。

无意的重复

有时,重复来自设计中的错误。
让我们看一个来自配送行业的例子、假定我们的分析揭示

 class Line{
	public:
	Point start;
	Point end;
	double length;
 };

第一眼看上去,这个类似乎是合理的,线段显然有起点和终点、并总是有长度(即使长度为0),但这里有重复,长度是由起点和终点决定的:改变其中一个,长度就会变化,最好是让长度成为计算字段

 class Line{
	public:
	Point start;
	Point end;
	double length(){return start.distanceTo(end)};
 };

在以后的开发过程中,你可以因为性能原因而选择违反DRY原则,这经常会发生在你需要缓存数据,以避免重复昂贵的操作时、其诀窍时使影响局部化 对DRY原则的违反没有暴漏给外界:只有类中的方法需要保持注意“保持行为良好”

class Line{
	private:
	bool changed;
	double length;
	Point start;
	Point end;
	public:
	void setStart(Point p) {start = p;changed = true;}
	void setEnd(Point p) {end = p; changed=true;}
	Point getStart(void) {return start;}
	Point getEnd(void) {return end;}
	double getLength(){
		if(changed){
		length = start.distanceTo(end);
		changed = false;
		}
		return length;
	}
	double length(){return start.distanceTo(end)};
 };

这个例子还说明了像Java和C++这样的面向对象语言的一个重要问题,再可能的情况下,应该总是用访问器(accessor)函数读对象的属性,这将使未来增加功能(比如缓存)变得更容易。

无耐性的重复

每个项目都有时间压力——这是能够驱使我们中间最优秀的人走捷径的力量。 需要与你写过的一个例相似的例程?你会受到诱惑,去拷贝原来的代码,并作出一些改动,需要一个表示最大点数的值?如果我改动头文件,整个项目就得重新构建 也许我应该再这里使用直接的数字。这里,还有这里,需要一个与Javaruntime中的某个类相似的类?源码再那里(你有使用许可)。那么为什么不拷贝它、并作出你所需要的改动呢?
如果你觉得受到诱惑,想一想古老的格言:“欲速则不达”你现在也许可以省几秒钟,但以后却可能损失几小时,想一想围绕着Y2K惨败的种种问题 ,其中许多问题是由开发者的懒惰造成的:他们没有参数化日期字段的尺寸,或是实现集中的日期服务库。
无耐性的重复是一种容易检测和处理的重复形式,但那需要你接受训练,并愿意为避免以后的痛苦而与i西安花一些时间。

开发者之间的重复

另一方面,或许是最难检测和处理的重复发生在项目的不同开发者之间,整个功能集都可能在无意中被重复,而这些重复可能几年里都不会被发现,从而导致各种维护的问题,我们亲耳听说过,美国某个州在对政府的计算机系统进行Y2K问题检查时,审计者发现超过10000个程序,每一个都有自己的社会保障号的验证代码。
在高层,可以通过清晰的设计,强有力的技术项目领导、以及在设计中进行得到了重复理解的责任划分,对这个问题加以处理,但是、在模块层,问题更加隐蔽。不能划入某个明显的责任区域的常用功能和数据可能会被实现许多次。
我们觉得、处理这个问题的最佳方式时鼓励开发者之间相互的进行主动的交流,设置论坛,用以讨论常见的问题。

8. 正交性

在计算机技术中,该术语用于表示某种不相依赖性或是解耦性 如果两个或者更多事务中的一个发生变化,不会影响其他事物,这些事物就是正交的 在设计良好的系统中,数据库代码于用户界面是正交的:你可以改动界面,而不影响数据库;更换

非正交系统

更改一部分会对系统造成较大的影响。

正交系统的好处

提高生产率和降低风险。

扫描二维码关注公众号,回复: 8918492 查看本文章

提高生产力

  • 改变得以局部化,所以开发时间和测试时间得以降低。与编写单个大块代码相比,编写多个相对较小的、自足的组件更为容易。你可以设计、编写简单的组件,对其进行单元测试,然后把他们忘记——当你编写新代码的时候不需要改动已有的代码。
  • 正交的途径还能够促进复用。如果组件具有明确而具体的、良好定义的责任,就可以用其最初的实现者未层想过的方式,把它们与新组件组合在一起。
  • 如果你对正交的软件进行组合。你会发现你的生产率会提高。假定某个组件叫做M件事情,而另一组件在做N件事情。如果他们是正交的,你把他们组合在一起你就可以做M*N件事情。但是如果他们是非正交的软件,他们就会重叠。结果能做的事情就会变少。

降低风险

正交的途径能够降低任何开发中固有的风险。

  • 有问题的代码区域会被隔离开来。如果某个模块有毛病,它不可能把问题扩散到系统的任何部分,要把它切除,换成健康的新模块也更容易。
  • 如果系统更健壮,对特定区域做出小改动与修正,你所造成的影响应该就局限在此区域。
  • 正交系统很可能得到更好的测试,因为设计测试,并针对其组件运行测试更容易。
  • 你不会与特定的开发商、产品、或者平台紧绑在一起,因为与这些第三方组件的接口仅绑定在开发的很小的一部分。
    遵循正交原则的几种方式

项目团队

你是否注意到,有些项目团队很有效率,每个人知道自己要做什么,并全力做出贡献,而另一些团队整天都在争吵,而且好像无法避免互相妨碍?
这常常是一个正交性问题。如果团队的组织有许多重叠,各个成员对责任感感到困惑,每一个改动都需要整个团队开一次会,因为他们中的任何一个人都会受到影响。
怎么把团队划分为责任得到良好定义的小组,并使重叠降到最低呢?没有简单的答案。这取决于项目本身,以及你可能变动的区域的分析。这还取决于你可以得到的人员。我们的偏好是从使基础设施与应用分离开始。每个主要的基础设施组件(数据库、通讯接口、中间件、等等)有自己的的子团队。如果应用划分显而易见,那就照此划分。然后我们考察我们现有的人员,并对分组进行相应的调整。
你可以对项目团队的正交性进行非正式的衡量。只要看一看,在讨论每个修改需要涉及到多少人。人数越多正交性就越差。

设计

大多数开发者都熟知需要设计正交的系统,尽管他们会使用模块化、基于组件、或是分层这样的术语描述该过程。系统应该由一组相互协作的模块组成,每个模块的实现都应该不依赖于其他模块的功能。有时,这些组件被组织为多个层次,每层提供一层抽象,在改动底层实现、而又不影响其他代码方面,你拥有极大的灵活性。分层也降低了模块间的依赖关系失控的风险。
在这里插入图片描述
对于正交设计,有一种简单的测试的方法,设计好后问问你自己显著的改变某个特定的功能背后的需求,有多少的模块被影响?在正交系统中你的回答应该是只有一个,修改GUI就不应该要求更改schema的数据库。
让我们考虑一个用于监视和控制供暖设备复杂系统。原来的需求要求提供图形用户界面但后来要求改为要增加语音应答系统,用按键来控制设备。在正交系统中,你只需要改变那些与用户界面有关联的模块,让他们对此加以处理:控制设备的底层逻辑不变。事实上,如果你仔细设计你的系统结构,你应该用同一个底层结构支持两套用户界面。(因此分层不是绝对的,看项目的需求)。
不要依赖你无法控制的事务属性。

工具箱与库

在你引入第三方库的时候,要注意保持系统的正交性。要明智的选择技术。
我们曾接参加过一个项目,在其中的一段Java代码,既需要运行在本地服务器上,同时又要运行到客户机器上,要把类似这样的方式分布,可以选用RMI或者CORBA。如果RMI实现类的远地访问,对类的每一次调用都有可能发生异常;
这意味着一个幼稚的实现可能会要求我们,无论何时使用远地类,都要对异常进行处理。在这里RMI显然不是正交的(应该是远程端自己把异常处理后,统一返回调用端的格式):调用远地类,都要对异常进行处理。调用远地类的代码应该不用知道这些类的位置。另一种CORBA的实现就没有施加这样的限制:我们可以不知道我们类的位置的代码。
在引入某个工具箱(或是引用来自你团队其他成员的库),问问你自己,它是否会迫使你对你的代码进行不必要的改动。如果对象持久模型是透明的,那么他就是正交的。如果它要求你以一种特殊的方式创建和访问对象就不是正交的。让这样的细节与代码隔离具有额外的好处:它让你以后更好更换供应商。(比如你的代码中使用到了OKHTTP3,以后不想用他了,换成了RestTemplate,这中间就对代码侵入性比较强了,因此最好把OKHTTP3或者RestTemplate 抽离出代码,做成统一的一层,对外部http、第三方接口调用的时候就可以直接使用你的抽离出来那层,底层无论使用OKHTTP3或者RestTemplate 还是其他都可以了。)

Enterprise Java Beans(EJB)系统是正交性的一个有趣的例子。 在大多数面向事务的系统中,应用代码必须描述每个事务的开始和结束。在EJB中,该信息作为元数据,在任何代码之外,以声明的方式表示的。同一应用代码不用修改,就可以运行到不同事物环境中。
正交的另一个例子(AOP)AOP让你在一个地方表达本来会分布在源码各处的某种行为。例如日志常常分布在源码各处。

编码

每次你编写代码,都有降低应用正交性的风险。除非你不仅时刻监视你正在做的事情,也时刻监视应用的更大的环境。否则你就有可能重复模仿其他模块的内容,或者两次表示已有知识。

  • 让你的代码保持解耦性:编写羞涩的代码——也就是不会没有必要地向其他模块暴漏任何事情,也不依赖其他模块的实现的模块。如果你需要改变对象的状态,让这个对象替你去做(让该模块自己去改变状态,不会让其他去影响他)。这样,你的代码就会与其他的代码保持隔离。
  • 避免使用全局数据:每当你的代码引用全局数据的时候,他都把自己与共享该数据的其他组件绑在一起。即使你只想对全局数据进行读取,也可能带来麻烦(改为多线程)。一般而言,如果你把所需要的任何语境(context)显示的传入模块,你的代码就会更易于理解和维护。在面向对象应用中,语境常常作为参数显示的传参给对象的构造器。换句话说你可以创建含有语境的结构,并传递指向这些结构的引用。
    设计模式 一书中的singleton(单体)模式是确保特定类的对象只有一个实例的一种途径。许多人把这些singleton对象用作某种全局变量(特别是在除此而外不支持全局概念的语言Java)。使用singleton要小心他们可能造成不必要的关联。
  • 避免编写相似的函数。 你常常会遇到看起来全都很像的一组函数——他们也许在开始和结束处共享代码,中间的算法却各有不同,重复的代码是结构问题的一种症状。要了解更好的实现,参见strategy(策略)模式。
    养成不断地批判自己的代码的习惯。寻找任何重新进行组织、以改善其结构和正交性的机会,这个过程叫做重构。

测试

正交的设计和实现的系统也更易于测试,因为系统的各组件间的交互是形式化的和有限的,更多的系统测试可以在单个模块上测试。因为与集成测试相比,模块级测试容易测试和合进行的多。事实上我们建议每个模块都拥有自己的,内建在代码中的单元测试,并让这些测试作为常规构建过程中的一部分自动运行。
构造单元测试的本身就是对正交性的一次有趣的测试。要构建和链接某个单元测试,都需要做什么?只是为了编译或链接某个测试,你是否就必须把系统中的很大一部分拽进来?如果你已经发现一个没有很好地解除与系统其余模块耦合的模块。
修改bug 也是检验正交的好时机,评估修正的局部程度。

文档

正交性也适用于文档。其坐标是内容和表现形式。对于真正的正交文档,你应该能显著的改变外观,而不用改变内容。现代的字处理器提供了样式表和宏。

认同正交性

正交性与DRY原则紧密相关。运用正交原则,你是在寻求系统中的重复降到最低;运用正交性原则,你可以降低各组件间的相互依赖。

发布了144 篇原创文章 · 获赞 3 · 访问量 9515

猜你喜欢

转载自blog.csdn.net/NumberOneStudent/article/details/103684722
今日推荐