代码是核心
面向对象发展到今天,已经出现了许许多多优秀的实践、方法和技术。很多的技术都能够有效的提高软件质量,而要用好这些技术,我们需要从过程和管理的角度来看待它们,而不是为了使用技术而使用技术。此系列文章包括:代码是核心、 案例实战(上)、 案例实战(下)、 重用、 优化代码的组织、 针对契约设计、 业务建模。
在一个有效的组织中,必定拥有杰出的一线人才。软件设计也是一样的,一线人才的素质决定了软件的质量。从敏捷的观点来看,代码是检验软件过程是否有效的最终标准。目前为止,以及在短时间的未来,我们都不太可能xx脱离代码进行软件设计。所以,软件过程中的任何一个活动都是为了能够产出优秀的代码。所以,代码才是核心。
1. 代码是软件开发的基础
编码是软件开发过程中最基本、{zd2}层的技艺,然而也是最重要的技艺。任何一个领域的专家都需要花费大量的时间来进行基本技艺的锻炼,木匠需要花费大量的时间来锻炼他们对各种工具的掌握,厨师则需要练习刀工和火候。程序员也是一样的,对我们来说,语言的各种特性必须要了然于胸。而对软件的管理也需要从代码做起。
从2000年到现在,国内兴起了一股软件工程热,需求管理、配置管理、甚至CMM。面对纷至沓来的各种方法学、UML、OOA,大家似乎已经热衷于这些概念本身了,却往往忽略了软件开发中最基本的元素-代码。在和很多软件组织的接触过程中,我们认为大多数组织急切需要的并不是这些工程理论,不是说这些理论不重要,而是这些组织的症结不在于此。很多的组织连代码的质量都管理不好,又何谈其它呢?代码管理是基础的基础,从管理的角度上来看,任何一个组织的管理都需要一个从上至下的管理过程,有基层的管理人员,也有高层的管理人员。对代码的管理就是软件开发中的基层管理,它起到的作用就是能够把需求、设计的思路贯彻到最终的代码中。
"管理无大事"。对软件的管理也是一样,大部分的问题都是由于很小的原因引起的。例如,一个产品如果后期在debug上花费了大量的时间,那么,这种现象是由于什么原因引起的?一种可能的原因是前期的代码设计中对代码质量的把握不严。每一次代码功能的演化并不会产生太多的问题,但是当代码累积越来越多的时候,问题也就慢慢出现了。那么如何解决呢?可以加强QA的力量,也可以引入复审,还可以引入单元测试。总之,要有一种方法对代码进行控制。
软件的开发过程就象是一部精密的机器,任何一个环节的变化,都会对其它的环节产生影响。把软件过程按照瀑布的形式进行划分是一种分解的处理思路,但同时我们还应该看到不同活动之间的相互影响。软件开发中的生命周期模型也是一个层次模型,从业务建模一直到软件实现,需要跨越数个层次,同样会出现执行不力的情况,例如,代码设计偏离需求、偏离设计的情况比比皆是。
如何避免这种情况呢?这就需要我们从源代码的角度,反思其上游的实践活动,是否足以约束代码设计?就拿XP来说,他解决这个问题的方式是尽快的进入代码开发阶段,从代码开发中发现问题,并在下一轮的开发中解决。这种思路是正确的,但XP毕竟是方法论,他不会告诉你过于细节的东西,尽管XP已经提供了大量面向代码的实践。因为方法论的抽象级别比较高,使得他必须舍弃部分的细节。而这篇文章告诉你的,就是这些细节。就像我们在下一节中讨论的例子,需要在代码中加入对异常的处理,那么,异常的源头在哪里呢?是需求,在需求中,我们发现了一些业务的非正常的处理序列,发现了一些业务实体的限制性的要求,所以在代码实现中,就需要有相应的异常处理。在例如,一个优秀的异常处理,还需要让客户端程序员了解可能发生的异常,以保证不同代码间正确的集成。
2. 面向对象的代码
面向对象的代码已经在现在的软件开发中占据了主流的位置,面向对象的思路也有其优势所在,就像后文所讨论的,面向对象代码有着非面向对象代码的很多优势,而软件业中很多新的思潮的产生,也都是基于面向对象语言的,所以我们关注的代码将是面向对象代码。
面向对象的思想来自于抽象数据类型。对于面向对象来说,它最重要的改进就是把世间万物都描述为对象,而类则描述了同一种对象的特征,而不是像传统的开发方法那样,按照机器指令的执行顺序来进行设计。当然,面向对象代码最终仍然是要按照时序来执行的,但是从程序员的角度看来,面向对象代码更侧重于对象之间的交互,多个对象各司其职,相互协作以完成目标。而面向对象技术的发展,也是朝着更加贴近我们世界观的方向发展。从这点来看,有人说xx没有程序设计经验的人学习面向对象可能会更加的容易,因为他不需要从原先的时序程序的桎梏中摆脱出来,但这未必是事实。面向对象决不是一种简单的程序设计思路。这是我们的观点,也会在下文中反复的论证。
和所有的职业一样,程序员,或者是面向对象程序员,始终坚持的一点就是严谨。你会看到各种各样优秀的代码,但那决不是一次能够写成的,要不断的尝试,不断的改进。为什么重构和测试优先是敏捷方法中很重要的一项实践?因为程序员不是神,他们需要慢慢改进他们的代码。虽然罗马不是{yt}能够建成的,但是在编写面向对象代码的过程中,有一些实践是需要坚持的,它体现了我们所说的严谨。
3. 编写并管理面向对象的代码
编写优秀的面向对象代码并不是一件容易的事情,优秀的OO代码如行云流水,糟糕的OO代码让人觉得浑身起鸡皮疙瘩。编写优秀的OO代码要求程序员有一定的自我修养,能够以抽象的思路看待问题,找到问题的核心并对问题域进行分解。它强调的是一种解题的思路,但这个解不是{wy}的。
典型的例子是设计模式,设计模式确实给了我们以很大的启发,通过它,我们能够了解到优秀的代码是如何用于解决实际问题的。但是是不是你必须在软件中照搬设计模式呢?如果你这么做,那么你对设计模式的理解仍然不够。我曾和在建筑行业的朋友聊起Christopher Alexander的建筑的永恒之道。他很兴奋的告诉我,那确实是一本很好的书,能够引发人很深的思考,但是现在也有另外的一种观点,认为美仍然是无形的,应该发自建筑师的内心。对这句话我思考了很久,其实建筑是给人使用的,因此最重要的是它能都给人带来的价值,隐含在其中的那种活生生的气质,这是建筑师文化底蕴的外在表露。所以,Christopher Alexander在那本书中的目的,也是为了找到一种总结自己观点的方法,来总结自己对人文的认识。至于现在大家对他的思路提出了质疑,那也是一件好事,这说明大家对建筑之道的认识到了新的高度。建筑是这样,软件中的模式也是一样的,我也曾热衷于研究模式的使用,直到某{yt}我猛然惊醒,与其沉迷于模式的表面形式,为什么不去研究隐藏在它背后的文化底蕴呢?武侠小说中常说无招胜有招,模式的应用也应当到达这个境界,你如果可以在不经意间应用模式的思想,那又何必拘泥于模式的形式呢?
编写优秀OO代码虽难,但还有更难的事情,就是让整个开发团队都产出优秀的OO代码。我们刚才说了,OO对问题的解不是{wy}的,但各个不同的优秀解汇集到一起,可能就是一个糟糕的解,这是风格和架构的问题。你如何在团队中制定制度,营造氛围,让优秀OO代码成为团队最终的成果?这些问题,在我看来,要比CMM难得多,这个问题并不是靠花钱就能够解决的。如果能够解决这个问题,这个团队的创造力一定是惊人的。
4. 面向对象软件开发过程
普通的软件开发过程和面向对象开发过程有着很大的不同。回想我们在非面向对象中开发过程中,最经常采用的任务分配方法就是以软件模块为单位,这样的好处是分配简单,不同任务之间耦合程度低,容易操作。坏处是几乎无法做到重用,也缺乏整体性的设计。而面向对象软件开发则不同,它是以类、类集合作为基本单位的。类之间关系错综复杂(虽然我们提倡低耦合的设计,但类之间的关系仍然是相对复杂的)。这种情况下程序员之间相互协作的要求就非常之高,这种关系如果处理恰当,则能够xx体现出面向对象的威力,否则,那将会是一场大灾难,面向对象的软件开发过程要养成一些好的习惯:
4. 1 尽量简化和稳定客户端。
个人编程可以是一种享受,但团队开发始终是一项严谨的职业活动,因此多考虑别人,不要设计复杂的接口,虽然你省事了,但这会给理解和使用你的接口和人造成障碍。
4.2 准备一份简洁的文档,并保持更新。
随便一种形式的稳定,可以是代码,可以是UML图,也可以是纯粹的文字(估计没几个程序员喜欢这种形式)。只要它能够传达你的代码的目的,那就足够。记住,更新代码后,同时更新你的文档。过期的文档不仅是废纸这么简单,它会给其它人造成麻烦。切记!
4. 3 尽可能多的考虑异常和错误的情况。
写出一个功能并没有什么,但是要把这个功能写的非常的完善那就很难了,因为你需要考虑各种各样的情况,正常的、非正常的。所以,写完一个类的定义应该是,完成编码和稳定,并通过原定的测试。
5. 基于面向对象代码的分析框架
在一个开发过程中,往往有着多种复杂的因素:过程、技能、工具、规范、组织、个性。所有的这些,都会对最终的代码产生影响,对代码的成本和质量产生影响。软件最有价值的部分是代码,根据敏捷方法和精益编程的思路,除了代码之外的产出物,都不具有价值,或者说对最终用户没有价值。但它们都需要成本的投入,而我们应该考虑如何节省这些成本。
要考虑如何节约成本,关键的因素就是需要分析两点:
首先,哪些活动对代码的成本和质量有正面的帮助。如果一个活动对代码没有帮助,那么它就没有存在的意义。有一些软件组织实施了UML,但是开发人员画好了UML图之后,就把它仍在一边,仍然按照老的方式开发。这种的活动就没有任何意义,只是徒增成本。我们称之为有效原则。
其次,如果活动对代码的成本和质量有正面的帮助,那么,这种帮助的价值足够大吗?是否存在其他的活动,其价值能够超出现有的活动呢?软件需求规约(SRS)对代码产生当然有帮助,因为它对软件要干什么进行了定义。问题是,SRS往往需要很大的功夫去制作、维护。有没有成本更低、效果更好的方法来替代它呢?用例技术是一种考虑方向,CRC卡片也是一种敏捷的处理思路。我们称之为更优原则。
有了这两个概念,我们在后文中的分析将会以此为中心展开,讨论代码和过程、技能、工具、规范、组织、个性之间的关系。我们把它们之间的关系称为基于代码的分析框架。在下一篇中,我们选择一个实际的案例进行演练,然后我们从四个方面 -- 重用、优化代码组织、针对契约设计、业务建模 -- 来深入的分析该框架中的一些共通的特性。我们再次重申:软件开发过程是一个复杂的生态环境,我们没有办法对其进行机械的划分,我们能够做的就是把握平衡-成本和质量的平衡。
案例实战
BPR的思路认为,组织并不是天生就存在的,它只是一种工具,企业盈利的工具。从代码来反向的思考开发过程,听起来有些奇怪。但是过程、工具、技能等等因素和企业组织有什么区别呢?它们都是工具,都是为了产出高质量代码的工具。所以我们从代码回望过程,正是为了更有效的整理我们的过程。本文通过一个实例,来分析代码对过程中种种因素的影响。
1. 案例分析-对异常的管理
在本文中,我们通过一个实例,来分析代码对过程中种种因素的影响。由于本文讨论的是面向对象代码,因此我们选择了面向对象的一个特性来进行分析。我们从案例的基本情况开始介绍,分析异常管理的基本思路,以及我们为什么需要引入对异常的管理。然后我们根据前文定义的分析框架来分析引入异常管理需要哪些方面的考虑,以及如何实施。
2. 案例的简单描述
需求分析阶段开始,软件开发就需要处理各种各样的异常序列,在用例设计中,除了正常的执行序列之外,还需要对各种各样的异常序列进行处理。编写代码也是这样,处理实现主要的功能之外,还需要对各种错误和异常进行处理。例如,编写一个处理Email的功能模块,处理能够正常的收发邮件之外,还需要能够处理服务端返回的错误,以及处理一些异常的情况,例如网络阻塞。
在传统的软件开发中,对错误的处理是基于返回码的,这种方式我们非常的熟悉,为了能够xx的定位错误,我们需要对返回码进行结构化的设计和分析,MFC框架就是此类的代表。我们举一个小例子:
public sealed class Painful{
...
private static char[] ReadSource(string filename)
{
FileInfo file = new FileInfo(filename);
if (errorCode == 2342) goto handler;
int length = (int)file.Length;
char[] source = new char[length];
if (errorCode == -734) goto handler;
TextReader reader = file.OpenText();
if (errorCode == 2664) goto handler;
reader.Read(source, 0, length);
if (errorCode == -5227) goto handler;
reader.Close();
Process(filename, source);
return source;
handler:
...
}
}
如果返回码简单的话,xx没有问题,但是如果返回值复杂的话,象上例这样,就显得非常的复杂和难懂了。在敏捷方法中,我们始终提倡自文档的代码写作风格,但是如果代码中充斥着各种各样的错误处理代码,那么会给代码造成很大的阅读难度。这是从代码风格的角度上说的,从设计的角度上看,错误码的本质是一个数值,是一个原生类型。而面向对象的威力就是在于能够准确的描述一个类型,将各种各样的错误情况都描述为数值不是面向对象提倡的风格。
因此,异常机制在过去的一段时间中,逐渐显现出其威力,慢慢的替换了陈旧的返回码机制。这里不打算对异常进行解释和举例,这种例子有很多。在Java语言中,异常的根是Throwable,在Throwable的层次中,异常大致可以分为三类:checked exception、runtime exception和error。根据JLS,使用的基本规则是在希望处理并恢复程序执行的情况下使用checked exception,对于error来说,往往意味着JVM内部处理非法的状态,程序已经不能够再执行了,代码不需要对这种情况进行处理。runtime exception一般用来指明程序错误,例如,用在指明前提条件违例的情况。
典型的异常的处理过程如下:
...
public sealed class PainLess{
public static int Main(string[] args)
{
try
{
string filename = args[0];
char[] source = ReadSource(filename);
Process(filename, source);
return 0;
}
catch (SecurityException caught) {
...
}
catch (IOException caught) { ... }
catch (OutOfMemoryException caught) { ... }
...
}
private static char[] ReadSource(string filename) {
FileInfo file = new FileInfo(filename);
int length = (int)file.Length;
char[] source = new char[length];
TextReader reader = file.OpenText();
reader.Read(source, 0, length);
reader.Close();
return source;
}
}
在将异常处理的代码集中一个地方之后,代码的流程就清楚了很多。
这样,看起来,在开发中规范异常机制,是有利于代码质量的改进的。这符合我们的{dy}个原则-有效原则。而从目前的技术来看,能够替代异常的机制尚未出现,而由于本案例假定环境的限制,我们无法选择其它更有效的提高代码质量的机制,所以我们认为这也符合第二个原则-更优原则。
那么,在下一章中我们将开始使用分析框架来分析问题。需要注意的一点是,我们并不按照分析框架定义的顺序来进行分析,因为顺序是无关紧要的。任何一个问题,可能有些要素容易分析,有些则比较难,我们xx可以先分析简单的,再考虑复杂的。而实施的时候,也基本上是按照这个思路来处理。
3. 规范
规范应当是从简单到复杂的,我们首先制定的规范并不复杂,只是对如何使用异常机制的一些定义。要获得这些规范并不困难,大部分介绍异常的技术资料中都给出了很多的建议。理解并使用它们,xx而已。
1、 对不正常的条件使用异常,尽可能准确的使用预定义的异常。这一目标来自于Effective Java中的条款39和条款42。其目的是为了能够正确的使用异常。使用系统提供的异常能够减少代码,提高代码的可读性(特别是新人不需要了解自定义的异常结构)。大多数情况下,系统提供的异常已经足够用了。
2、 尽可能多的收集异常发生时的上下文信息。异常之所以比返回码优秀的一个原因就是它能够将错误类型化,提供比错误代码多得多的信息。因此,我们实在没理由不使用这一功能。
3、 正确的使用异常转义,并保留原异常信息。异常转义的目的是为了让客户端能够得到易于理解的类型。我们想象一个用户登录的情境,假设用户数据保存在一个文件中,当文件中找不到用户名的相关记录的时候抛出一个RecordNotFoundException异常,系统截获了这个异常,并将其发布给用户,问题在于,用户会觉得非常的奇怪,为什么会是记录没有找到呢?因此,建立一个IllegalUserException异常会更适合于这种情况。
4、 针对不同的抽象层次定义不同的异常。正如我们在第三点中提到的,RecordNotFoundException异常并不适合于用户这个层次。但是,这个异常对于程序员调试代码就很有意义了。
5、 将异常发布到合适的地方。有计算机的地方就一定有输入和输出。如果把异常发生时的信息收集看作输入,那么异常的输出是什么呢?可能是错误的提示信息,可能是一个显示错误信息的网页,可能是日志中的记录,可能是一条短信,也可能是一封EMail。这些就是异常的输出形式。此外,异常的输出还需要正确的确定对象,对用户来说,异常只要有一个友好的提醒方式就够了,但对于管理者来说,异常需要记录下来,或是通过异步消息进行通知。
这就是规范,你也可以把它称为{zj0}实践、建议等名词。当然,它还可以更加的细化,但事情总有个过程,一开始把问题弄得过于复杂未必是一件好事,你说呢?
4.技能
有了规范是一回事,能否把规范运用起来则取决于人员的技能。在有一部描述清末的电影中,有这样一个情节,留学归来的知识分子为了提高民众的知识水平,不惜花费巨资免费发放报纸,这一举措大受欢迎,可惜大部分的民众都不识字,他们要报纸的原因只是这东西烧火很方便。
所以其次要解决的问题就是,大部分的程序员没有足够的异常处理经方面的技能。如果程序员没有这方面的概念,你把一本异常管理{zj0}实践放在他的面前会有用吗?
学会使用异常并不困难,困难的是如何让程序员正确的使用异常。什么时候使用系统定义的异常。什么时候使用自定义的异常,自定义异常又该如何设计。这些都是程序员的技能问题。
基于这种思路,首先做的是培训,而培训的目标是让程序员理解异常的机制,让程序员能够把异常运用到工作中。培训不等于上课,因为我们的目标是能够影响程序员的行为,单靠上课是无法达成目标的,因此我们把几种方式综合使用。一般来说,程序员对未知的技术总是有兴趣的,并且会尝试着运用,然后就会慢慢失去兴趣。这种对新技术的兴趣是可以利用的,但是要在兴趣过去之前将行为稳定下来。所以我们首先做的是挑选合适的程序员,他们要么就是有使用异常的经验,要么就是热衷于研究新技术,这些人是比较容易说服的。这些人的作用并不是限于学习,他们的主要职责是将这项技术传播到整个团队中。管理的本质其实是透过人来做事,而不是自己做事,所以传播知识的职责应该是由一个团队来共同承担,{jd1}不是一个技术主管上窜下跳的。我见过很多过于忙碌的技术主管,他们都犯了这个错误。
当我们在讨论会议上取得了对使用异常机制的共识之后。开始使用第二种方式-训练。训练分为两个部分,前半部分是讲述异常的机理、如何使用。这时候还不讲异常的{zj0}实践,因为大家还没能够熟练的使用异常,讲述{zj0}实践的效果不好。后半部分是让原先挑选的程序员辅导其他程序员练习异常的使用。练习的时间大约维持了3天,我们观察到项目开发的速度并没有受到很大的影响,因为训练并没有占用很多的时间,辅导的时间也是分布到项目开发中,但是我们知道,如果要在项目引入异常,一开始速度是一定会下降的。在首次训练完毕之后,是第二次的训练,这次主要是介绍异常管理的{zj0}实践。有了一定的使用经验之后,团队顺利的接受了这部分的知识。团队已经开始拥有了这方面的技能了。
5.组织
由于是团队协作,因此我们考虑问题必须在团队环境中考虑,这就象多线程,在多线程环境中考虑问题的方式和单线程是截然不同的,任何问题都有基本的解决思路,团队协作中主要的问题包括沟通、信息传递、协作、计划等问题。所以我们解决问题的思路也是先定义出问题和要达到的目标,然后再来考虑方法。
我们认为,异常在组织中的{zd0}作用是它清晰的定义了类开发者和类调用者之间的契约关系。所以,我们希望类的开发者清楚的说明一个方法在什么条件下会出现什么样的异常,类调用者则需要保证他的调用对异常有明确、完整的处理。
这个时候,我们需要过程的帮助了。
6.过程
为了实现组织一节中定义的目标,我们对过程进行修订:
1) 在设计类时,需要同时设计异常类,并编写异常类的说明文档,通过复审。
2) 在给出类和方法的说明时,需要给出异常的定义和触发的条件。
3) 在代码中使用异常,而不是返回值。
4) 对输入参数进行判定,使用参数不正确异常和空指针异常。
5) 测试所有异常。
6) 方法调用时,如果方法中有异常,必须使用try块进行异常处理。
7) 异常需要通过通用的框架发布。
1、2、5三点属于设计的内容,3、4、6、7四点属于编码的内容。
注意到了吗?这种过程的定义是非常具体的,准确的说这类似于作业流程。很多软件组织都没有做这方面的工作。抽象的制度是没有意义的,比如这么一条,严谨的做好软件测试。什么叫严谨?软件测试的基本原理就是你不可能测试所有的情况,那么,严谨到底是测试到什么地步,没人知道。而我们这里的定义则不一样,方法中抛出的异常都需要测试。非常的明确,如果你的代码中抛出了3个异常,那么你就需要至少三个测试方法来测试它们。
当然,其实这个过程制度还是不完整的,我们拿第4条来说,它就有几个问题:首没有指明两种异常的使用环境;没有规定异常如何收集上下文环境。但是,我们认为,制度性的规章不必要定义的如此复杂,你见过法律规定拿xxxx定什么罪,拿刀子xx定什么罪吗?你可能会问,刚才你说制度要定义的细一些,现在又说不要定义的这么细,这不是自相矛盾吗。不,任何问题的解决方法都是在不同的因素间进行权衡,而我们是在制度的可操作性和抽象性之间权衡。这也是敏捷思维的一部分。团队中知识的传递决不是靠一纸规章就能够完成的。那么应该怎么做?两种方法,一个是靠口头讲述使用方法,另一个是通过我们原先指定的"辅导员们"。
但在这时候,新的问题又产生了。类设计者如何把异常告诉类使用者呢,他们如何做到第2条呢?,第7条中的通用的框架又该如何设计呢?这时候我们必须求助于工具。
7.工具
我们先解决{dy}个问题。这个问题的根本在于,类设计者如何编写异常文档。在Java语言中,我们可以很容易的使用语言本身的功能和JavaDoc工具来解决这个问题。但是在其它的语言中,我们可能需要第三方的工具,甚至自己编写工具。
第二个问题在Java中同样有比较好的解决思路。Log4J是Apache组织下的一个优秀的处理日志的开放式框架。使用它能够很方便的将异常发布到各种途径中,例如Console、Windows日志、邮件、文本。无疑这正是我们需要的。至于其它的语言,那就只能靠各位看官发挥自己的能力了。
8.个性
我不得不说这是最神秘,最难处理的一点。更糟糕的是,我发现很难把这个问题描述清楚。所幸的是,有一件事情让我印象深刻:
一次程序员A和程序员B为了一件事情争论。B编写了用户注册的后端控件,A负责调用。A的代码大致上是这样的:
try
{
doUserLogon();
} catch (LogonException le)
{
//异常处理代码
}
这样的代码看起来似乎没有什么问题,但是实际上却出错了,问题的现象是在测试重复用户的时候,没有任何现象发生。在debug的时候,B告诉A,其实对于重复用户的设计是通过返回值的,返回值非0表示有重复用户。所以正确的调用代码应该是这样的:
try
{
if doUserLogon()
{ … }
else
{ … };
} catch (LogonException le)
{
//异常处理代码
}
你认为是哪一位程序员犯错了呢?A有错吗?他的调用方式其实并没有错误,而且,他也并不知道B的设计思路。那么是B的错吗?B的异常处理思路也没有什么问题,他把异常用在非常的条件中,例如数据库连接失败。对于用户重复这样的分支,应该看作是一种正常的返回。那么是过程的问题了,至少B应该提供完整的类使用文档。话虽如此,但是一个软件组织的资源毕竟是有限的,Sun公司能够为他的Java API提供详尽的文档,你认为有多少公司可以做到呢?而且在团队内部仍然采用这种高成本的沟通方式,其实是不太好的。
我想来想去,这件事情应该属于个性的范畴。任何一个人都有自己的背景和性格。你永远无法指望大家能够写出一样的代码。作为类的编写者B,他无法知道A将会以什么样的方式来调用他的代码,与此对应的,类的使用者A也不清楚B编写的代码的细节,因为根据类的设计原则,类的实现细节不应该透露给外部。这中间出现错配也是正常的。
这个问题还是有办法解决的-为代码定义统一的风格,也就是固定一种编码风格。在技术实现上,我比较倾向于程序员A,因为第二种代码要比{dy}种代码复杂和难读。一个优秀的类设计人员应该在简化客户端调用上下功夫。在代码中包含了过多的路径导致类的测试和使用变得困难。当然,我在很多资料上也看到了很多不同的见解,但不管是哪一种见解,{zj0}的做法是在组织内统一做法。所以在我们的组织中,都只使用两种序列,要么正常返回,要么发生有类型异常。
但这是否解决了问题呢?这个问题可能是解决了,但还会有很多类似的问题。所幸的是,这个错误最终是被发现了,可能软件中还存在大量未被发现的错误,这些才是最可怕的。其实这个问题的根源在于,程序员之间的沟通不够。而要解决这个问题,我们认为,只能从文化上去想办法,提倡沟通,提倡协作。而我们的分析框架中并没有文化这一项。理由是,文化的主题超出了我们讨论的范畴。《创建软件工程文化》一书是一本讨论文化的优秀书籍,如果大家有兴趣,可以进一步的阅读。
所以,这里我们可以知道一个道理,软件工程、项目管理其实并没有什么非常难的地方,我们看很多书把很多问题都提到纯理论的高度,反而给读者造成了困扰。很多读者的来信中,问到一个同样的问题,我希望在我的团队中引入软件工程,有些人说从需求管理开始比较好,有些人说从配置管理开始比较好,到底应该从哪里开始呢?就拿这个例子来说,为什么我们对一个异常管理如此的关心呢?为什么不做需求管理,或是上CMM呢?那该多酷啊!问题就在这里,软件开发是为了给客户带来价值,就像UMLChina的名言,软件以用为本,而不是以酷来衡量的。异常管理的最终目的是为了提高软件质量,这是立竿见影的效果,能够给老板、给同事以信心。需求管理和CMM当然也可以达到这种效果,问题是,异常管理几乎不需要多少的成本,却能够达到直接的效果,这种好事,为什么不做?
事情到了这一步,程序员的行为已经开始慢慢规范了。注意,我们先激发大家的兴趣,再使用制度来规范行为。但是,这里还有一个问题,我们之前提到,使用新技术一定会在短期造成生产力的降低。好的,现在我们确实发现生产力降低了,因为程序员必须思考什么时候使用异常,如何使用,并编写文档和测试。所以我们在之前已经做了一件事,就是取得老板的支持,这一点至关重要。这也许也是属于文化的范畴吧。
好了,软件过程是需要不断的改进的,我们的案例讨论也体现了这一点,这个案例还可以讨论很多,但是处理的思路基本一致。找出可能存在的问题,思考相应的对策来规范行为,再发现问题,再规范行为。这样形成了一个不断循环不断进步的过程。