0%

【书摘】简单之美:软件开发实践者的思考

知识需要积累,对知识的热爱是知识积累的基础。


我们始终相信,最简单的也就是最有效的,一个好的idea往往都能通过一些简单的技术实现,从最早的IM的成功,到SNS的火爆;从AJAX旧瓶装新酒,到jQuery改变JavaScript的书写习惯,莫不是如此。事实证明,简单才是王道!本书用简单的原则、富于想象的精神引领我们悟透软件开发的本质和奥义。

前言

领悟到软件开发中的简单之美是解决软件开发问题的钥匙。在我看来,思想是一种坚定不移的信仰,并且是在经过了足够的实践检验后形成的。在软件开发过程中,稳定的思想会使所有的力量汇聚到一个有效的方向。

总之,构建一套完整的思想体系是软件开发能力成熟的标志。只有思想成熟,才会使你的一言一行、举手投足、细节决策等在复杂的实践中收放自如。

什么样的信息是有效的呢?

  • 简洁明确的思想表述;
  • 层次清晰的分类信息;
  • 令人信服的论证过程。

本书想要传递的思想是,用简单的原则、富于想象的精神、文化的视角来认识软件开发。请在阅读时始终关注这个核心思想。

1.1 创造的根源

众所周知,编程语言经过多年的发展,已经从面向机器的低级语言“进化为”面向领域和对象的高级语言。越是高级的语言,抽象程度就越高,人类想象力和创造精神的特征也越明显。

我有一个有趣的想法:如果人们愿意定心敛神,细细体会熟视无睹的事物,一定可以得到超出想象的收获。

在汇编语言中,数据结构的概念是很淡的,更不要说围绕数据结构展开的算法了。数据结构和算法的繁荣发生在比汇编语言高一级的编程语言中,例如C语言。

C语言在一定程度上解决了人类在数学计算和逻辑推理上的抽象。从此,软件的主要关注点转移到数据结构(线性表、栈、队列、串、数组、二叉树、图等)和各种算法(对特定问题的求解过程)上,经过多年的发展,数据结构和算法已经成了计算机专业的一门基础课程,而几乎所有的教科书,都是用规范的数学语言来描述数据结构的。

算法就是数学问题的求解过程。在使用C语言来实现一个软件系统时,逻辑思维占了绝对的比重。

当代的企业级信息系统与Java语言结合得非常完美,这不是偶然的。我们知道,企业级信息系统需要解决的是领域问题,而不单单是数学运算问题。领域问题最终会转化为一个个数学运算,但是在求解领域问题的过程中,仅仅依靠数学知识是远远不够的。例如,保险公司的承保业务,不仅需要大量的信息数据,还需要复杂的业务逻辑。要描述这些业务逻辑,必须站在领域的高度上进行。此外,复杂多变的用户需求、数不胜数的方案模型、参差不齐的开发团队、成年累月的项目周期,这些各方面的因素都会对信息系统的开发产生影响。

Java语言培养了我在软件开发中的想象能力,这是其他任何一种编程语言都没有给过我的。

多年前,我曾经阅读了Tomcat(一个开源的servlet引擎,用Java语言实现,它也提供Web服务)的源码。我看到一个HTTP的请求被Tomcat监听到以后,它根据请求数据中携带的特征,开启了不同的阀门。开启的阀门引导数据流进入相应的管道。管道中有各种解释器对数据进行分析。分析后再转发给相应的处理器。简直不可思议,我就像阅读一个故事,第一次从软件中体验到了一个想象的世界! 这次阅读使我开始反思自己的软件开发生涯。反思的结果是:多年来一直在编写程序,而且翻阅了不少软件产品或技术的操作手册; 已经比较善于理解各种需求了,而且可以直接地给予实现;可以从软件的运行中得到乐趣了,可是还没有开始关注软件本身;学到了一些知识,但是这些知识总显得杂乱零散; 我的软件开发工作几乎没有任何创造性。

反思中的最后一条使我很震惊,我也因此领悟了一些道理。总结起来只有一句话:软件的美和价值在于创造,创造的根源在于想象。

能够充分展开想象,是创造能力的集中体现。

事实上,在我所经历的一些糟糕的软件开发案例中,最大的问题,往往源于缺乏想象或想法混乱造成的软件架构问题,以及毫无想象力的、僵化的项目管理思想。只有较小的一些问题,来自具体的工作细节,例如,糟糕的算法和糟糕的逻辑思维。这也从另一个角度证明了想象和创造的价值。

1.2 本质的把握

思想体系的建立是很有用的。我们不妨以学习为例。你知道吗?最有效的学习方法就是用自己的思想体系来接纳外部知识。这也可以说明一个问题,你看,在计算机书店,有很多大师级的作品,他们的知识向所有人开放,可是很多人从中得到的收获总是非常有限的,为什么?因为思想体系还没有建立,很多人还无法对书中的知识进行有意识地提炼和抽象,所以总是会有很多疑惑和不解。

1.3 简单的追求

软件开发,尤其是企业应用软件开发,最根本的原则是简单化。简单可以使表达准确、思路清晰,可以实质性地、最大限度地降低软件开发成本。简单化不是天方夜谭,它是可以实现的。

一般来说,人们认识事物从简单开始,经过简单认识的不断堆积,然后在混乱复杂中摸索,最后又回归简单的认识。这样的认识过程同样适用于软件开发领域。 因此,在软件开发实践中,我们应该有意识地向认识的最高形式——简单化——靠拢。事实上,简单与否,常常可以成为衡量我们对事物认识程度的一个标准。我们不妨按照简单的标准来认识一下企业应用软件。 在我看来,企业应用软件很简单。它主要包括三个部分:一个领域模型,一组基于领域模型的计算,以及用来和用户交互的界面。这是一个基本的思路。

任何时候都不要轻易丢弃一个非常简单的原则性想法,同时,在任何时候都不要固执于一个具体的想法细节。

具有讽刺意味的是,在软件开发实践中,很多设计人员提出的针对性能的解决方案往往是性能的最大瓶颈。

本质的就是简单的。所以,我们的目标是要让事情都变得简单。这有很多的工作要做,比方说如何让软件如何与平台框架的耦合性降到最低,如何让软件的可移植性和可扩展性最好等。事实上,这些让事情变得简单的工作,才是我们思考和努力的方向。相反,在简单的事物上,寻求局部的、复杂的解决方案是一种风险极高的做法。

相关背景知识的积累需要一个长期的过程。在这个过程中,主要的工作就是收集信息。

收集信息是一项艰巨复杂的任务。但是,对软件开发来说,这项任务是过程性的任务,而不是我们的目标任务。 可是,我总是很遗憾地发现,很多软件开发人员都被这个过程性的任务蒙蔽了双眼。他们中的一部分,始终徘徊在信息收集的门外(因为无法掌握足够的信息而无力解决问题),他们中的另一部分,在积累了丰富的信息之后,尽管已经不自觉地站到了一个更高的层次(可以用思想来指导行动)上,但是主观上,他们仍然把信息收集的过程当作了软件开发的本质内容。

其实,信息量的多寡,根本不能反映软件开发能力的本质差异。我们说必要的知识积累是软件开发的起点,但是软件开发的本质在于创造,所以运用信息的能力才是关键。 从这个角度来看,很多软件开发组织对软件开发技能的认识其实都是错误的。这些组织看重信息量,而忽视信息的使用方法。这也是我们为什么总是遇到复杂混乱的软件架构的原因之一。

越简单越准确。用例的撰写就是一个明证:不需要修饰语,不需要副词,不需要语气词和助动词。有效的用例只保留主谓宾,没有任何内涵和外延干扰意思的表达。用例简单到只有常识和术语,几乎不可能带来歧义。

越简单思路越清晰。我曾经看过这样的系统,它包含了很多概念和分层。一次调用会涉及数不清的接口,跨越数不清的框架、模块、公共组件和“小发明”。一定需要这样的结构吗?不需要。这样的结构只会使后期的演化和维护变得异常困难,由此带来了巨大的隐形开发成本。虽然我们不差资源和智慧,但能简单为啥不简单呢?

理想中的简单化是仅保留所有必须保留的,使通过简单化建立的一切,最有利于知识共享和持续演化。这两个目标看似容易,其实不容易。它需要成熟的简单化思想,需要持之以
恒地恪守原则,需要简单化技能的训练。

第2章 关于软件开发方法论的思考

文化是方法论的基础,也是方法论的运行环境。割裂文化来实施方法论,就像建造空中楼阁。

2.2 CMM的精髓

CMM(Capability Maturity Model,能力成熟度模型)对软件开发过程的关注是其最重要的贡献之一。能力成熟度越高,说明软件开发过程越成熟。成熟的软件开发过程,是一个软件开发组织进行团队开发的基础,而如何运用软件开发过程,则是保证团队项目持续成功的关键。

当人们需要共同完成一件事情时,常常需要一些契约来规范人们的行为。在软件开发中,这些契约以过程的形式存在着。 为了使契约更加合理,软件开发组织需要对过程进行系统地思考和总结。这是一种持续的行为。另外,在软件开发实践中,过程会不断地得到优化。那些更合理的过程,会被保存下来并重复使用,最终成为知识资产的一部分。

在软件开发实践中,我们经常会遇到很多模糊的反对意见。在不成熟阶段产生的判断,被固定成一些错误的想法,而这些错误想法,又会阻止一切迈向成熟的尝试。糟糕的是,这些反对意见往往来自掌握话语权的软件开发人员。

我一直认为,排斥CMM是错误的。CMM中包含了很多有价值的想法,尽管它很少提及人的因素(在敏捷软件开发中,人的因素得到了充分的关注,甚至有被夸大的嫌疑)。

开源软件的开发人员,常常以一种松散的形式组合在一起,兴趣爱好是他们最主要的驱动力。对于这样的组织来说,过程可以最简化,只需要保留减少混乱、提升效率的活动,而剔除在缺乏主动性的情况下进行约束的所有内容。

首先,CMM对于软件开发过程的关注是系统性的。在CMM中,定义了质量保证、配置管理、需求管理、项目管理、软件开发管理、同行评审、项目资源协调、人员培训等概念,涉及软件开发过程中方方面面的活动。

其次,CMM推荐了大量被证明有效的活动,并把它们纳入一个模型中。在这个模型中,CMM定义了这些活动的类型,以及先后次序和依赖关系。

最后,CMM鼓励软件开发组织基于模型和推荐的活动来定义自己的软件开发过程。CMM会依据规范来评估自定义的软件开发过程,换句话说,CMM是一个参考模型,它不要求软件开发组织严格遵守。CMM只是建议,在软件开发组织自定义的活动和CMM规范推荐的活动之间做一些映射。这些映射可以非常灵活。

同行评审是我非常推崇的一项活动。通过同行评审可以及早发现软件开发工作中的一些失误,可以及早为一些问题找到解决方案。

CMM建议,有价值的过程应该被记录下来,并在实践活动中完整地验证。相比于那些没有任何记录、重复讨论、人走茶凉的垃圾会议,CMM的做法不值得赞许吗?

2.2.2 成熟之路

只有成熟的软件开发组织,才能快速适应组织规模的变化,才能持续不断地递交成功的项目成果,才能最大限度地降低团队开发的成本。CMM提供了成熟度评估模型。这个模型,可以帮助软件开发组织,围绕着软件开发过程这条主线,通过各种有价值的活动,最终达到软件开发能力成熟的目标。

不经历CMM(或类似的工作模式),很难体会到敏捷开发的意义。CMM仿佛是软件开发组织成长中的一个阶段。只有经历过这个阶段,软件开发思想才能更快地成熟。

是的,任何一个软件开发组织的软件开发过程,都是不相同的。当我们的智慧经过积淀和结晶,当这些积淀和结晶被记录在案,当我们拥有了知识基础,当我们的软件开发思想形成体系,我们将可以轻松面对任何变化。

2.3.1 人与实践

本质上,软件开发是人类的一种智力活动,是一种艺术性和科学性相结合的工作。不关注人的因素,软件开发就会失去控制。要关注人的因素,最实际的办法就是注重以人为本的实践。我认为,敏捷软件开发思想的精髓就在于人与实践。

为了提高软件开发人员的主动性,通常有两种方法。一种方法是,传播信仰,使团队成员成为同志;另一种方法是,建立一系列以人为本的实践方法集,用习惯和文化融合组织成员。敏捷软件开发致力于第二种方法。

意外的想法仍然产生了。Alistair CockBurn指出了一些敏捷方法实践者的误区:

  • 迭代必须简短;
  • 敏捷团队必须驻扎在一起;
  • 敏捷团队不需要计划;
  • 架构已死,重构是你全部所需要的;
  • 我们不需要什么经理;
  • 敏捷开发在纪律上要求很低;
  • 敏捷只适合最优秀的开发人员。

有趣的是,尽管没有外部的指导,很多人却不约而同地陷入相似的“误区”。看上去,人们过于期待那些可以把自己从约束中解脱出来的方法了,所以,人们往往走向了约束的反面。这些误区的存在,说明很多人还没有真正理解敏捷方法的本质。

在我看来,项目经理最重要的工作,应该是为软件开发提供服务。他是那个扫清路障的人、积极进言者、精神鼓舞者,而不是那个拿着时间表、冲着软件开发人员发火的人。要保证软件开发的进度,项目经理的频繁干预,不是一件好事。组织必须培养有责任、有追求的团队。这类团队,应该围绕着一位主刀医生角色的软件开发人员展开工作。

2.3.2 海岸灯塔

方法论的研究者和实践者,总是希望能够找到一种最好的方式,来轻松地开发出高质量的软件。而敏捷方法的支持者,是其中最灵活、最富有反思精神的一群人。

敏捷方法的支持者在理论上是理想主义者,在实践中是实用主义者。他们似乎指出了一个激动人心的方向,但是真正的终点其实还很远。

在习惯的培养上,CMM更加系统。它似乎更明确地希望,通过制度和约束来造就好的工作习惯。而敏捷方法,则更多地依赖个人,它希望通过一些简单、灵活、人性化的最佳实践,来培育一种良好的文化。这或许是二者之间的本质差异。

对于敏捷方法来说,没有个人技能的支撑,实践通常是失败的。

最后,我想强调,这一节的内容不是在批判敏捷方法。事实上,敏捷方法非常适合于那些做好准备(顿悟)的人。它是我所见到的最高效的方法。

2.4.1 中庸

企业文化包含哪些内容呢?我认为,最重要的有两个:一个是企业经营者和管理者的价值观;一个是组织成员的结构和背景。这两个都与人有关,而前者对企业文化的形成更是起着决定性的影响。

价值观的狭隘,会造成不同价值观之间的频繁冲突,会造成大量人力资源的浪费。我提倡的是兼容并蓄、博采众长的价值观。这种价值观,在软件开发实践中是很少见的,在东方人的实践中更少见。 要形成兼容并蓄、博采众长的价值观,需要站在一个更高的层次上,需要具备一种为敌人辩护的精神。

企业经营者和管理者,应该清醒地认识到,企业需要的是人的技能,而不是技能发挥的形式。如何兼容并蓄、博采众长,应该是我们最应该关注的问题。

中庸,不是僵硬地居中于两个极端,而是一种非常动态和灵活的平衡。就像秤砣:秤砣在秤杆上来回移动,最终使重物与秤砣保持了平衡。换句话说,目标和能力是客观存在的两端,中庸之道就是这两者之间的平衡点。一旦找到了这个点,目标就容易实现了。

纵观历史,文化的形成,往往源自于严酷的制度。与之类似,在组织的软件开发方法形成之初,求助于制度也不失为一种办法。

从个人的角度,粗犷分工不利于软件开发人员自身知识的积累。如果没有知识的积累,生产效率会变得极为低下。

2.4.2 聚焦

项目经理可以制定计划,并根据实际情况调整计划;需求分析人员可以编写需求分析文档,并根据具体情况追加解释;架构人员可以使用保守不变的方案;设计人员可以因为项目紧张的原因,提交不完善的设计书;测试人员可以只负责发现问题。只有一线的程序员必须完成实际可用的产品。当项目失败的时候,没有人去追究各个环节的问题,只有程序员是永远的替罪羔羊。

2.5.1 关于执行

执行,在西方企业界不算是头等重要的事情。这或许是因为,西方人很早就适应了严密的制度体系,他们掌握了一整套在这种体系下沟通和协作的方法。而中国人刚刚从家长制思想中解放出来,还不能完全适应新的游戏规则。这种不适应体现在无数的细节上,并最终导致了企业文化上的差异。

2.5.2 约束与习惯

我们都接触过很多管理者。有些管理者希望以德服人,他们注重自身的品德修养——关心下属、宽厚待人、信守承诺。个人品德提升了这些管理者的执行力。有些管理者喜欢辩论交战的方式,他们思维缜密,表达能力出众,在执行中毫不畏惧地面对各种挑战,直到对方臣服。

有些管理者采用世俗手段,拉拢舆论,打压反对派,用胡萝卜加大棒的政策确保执行的顺利进行。以上的三种执行方式是软件开发实践中最常见的,但是它们的效果都不大好。事实上,仅仅关注执行力是错误的。这就像头痛医头、脚痛医脚,缺乏解决问题的全局眼光。

在执行力上关注越多,偏离执行的本质就越远。

软件开发方法不是解决执行问题的银弹。从约束到习惯的演变过程才是关键。你看,软件开发过程带来约束,长期的约束形成习惯,丰富的习惯促生文化,文化制造氛围,氛围产生最佳的执行效果。神奇的逻辑,约束最终将转变成自然!如果我们没有经历从约束到习惯、习惯到文化、文化到氛围的演变过程,就不可能在本质上提升执行效果。这就是我的解答。

3.2.1 准确表达

对于企业信息系统来说,通常有两种类型的项目:一种是开拓型,另一种是持续型。开拓型项目面对的是全新的领域,组织内部缺乏相关领域的知识;持续型项目是在以往项目的基础上进行的,组织内部往往已经积累了大量的经验。 在实践中,这两种类型的项目碰到的需求问题有一些差别:在开拓型项目中,人们经常被客户的需求所淹没;在持续型项目中,人们经常因为领域问题上的理解差异,产生大量隐性的开发成本。

无论哪种类型的项目,都有一个共性。那就是,客户往往不能提出准确的需求(更不用说有系统地提出需求)。我认为,期望通过一两位专家就能把所有的需求传递给软件开发人员是不现实的。 不现实的原因有三点:首先,客户是一个群体,提出需求的人只是客户中的一小部分人;其次,提出需求的客户并不能完整地考虑到所有的场景;最后(也是最重要的),客户往往不能准确表达出自己的需求。

要想解决需求问题,关键有两点。这两点都需要需求分析人员的努力。 第一,需求分析人员应该使用客户提供的常识和素材,为客户提供他们可以理解的完整故事。事实上,单纯限制需求变化,只会演变为一场两败俱伤的战争;第二,需求分析人员要积累领域知识,提升对需求的预判能力,并把预判应用到软件实现中去。

3.2.2 信息传递

从技术角度来看,要保证信息的准确传递必须借助于术语。术语,是领域常识、是领域知识的浓缩。我们知道,需求分析时需要用到术语(领域知识)。其实,在软件开发的各个环节(架构设计、编程、软件测试、产品维护等),都需要用到各自领域的术语。

一种理想的做法是,我们应该在需求分析阶段准备两份文档。一份是使用客户的术语来表达客户的故事,另一份是使用软件术语表达软件实现的故事。前者类似于“设计”工作,后者类似于“编程”工作。

掌握术语不是一件复杂的事。我们唯一需要做的,就是长期的学习和持续的关注。

为了深入掌握和灵活运用术语,我们必须长期走在正确的积累之路上。为了保证交流中的正确表达,我们需要统一领域方面的认识,并用统一的认识来准确交流。尽管这种统一认识的过程不会为企业带来直接的创收,但是统一认识的结果,却可以极大地降低交流的成本以及后续的实现风险。

很多软件开发组织都迷信培训的力量。他们认为,只要经过短时间的集中学习,就可以让软件开发人员快速进入客户的领域。 这有点像我们儿时常玩的打仗游戏。小朋友被想象中的子弹击中,自觉倒地,另一个小朋友大喊给你吃药,于是这个受伤的小朋友迅速站起来投入战斗。这个过程没有错。可是现实中不是这么玩的。拨出培训经费、安排短期培训课程,不是解决问题的方法。

术语是准确表达的基础,而讲故事则是对需求分析人员的基本技能要求。每个人都会讲故事,但是需求分析要讲述的故事有其特殊性。需求故事的结构和风格,对于需求的准确表达非常重要。

解释会导致歧义:解释越多,歧义性越大。

要提高自然语言的准确性,有两个办法:一个办法是,使用常识性的自然语言(包含领域术语)来减少二次解释;另一个办法是,调整故事的讲述方式(简单句式、层次递进)。

3.3.2 应对之道

限制需求的变化,不是解决问题的正确方向。这就像用“堵”而不是用“疏”的办法来治理洪水。正确的方向是,站在客户的立场,努力去发现和解决客户真正的需求。

需求分析的目的是使客户明白自己的需要,而不是单纯地限定需求的范围。我们需要考虑商务上的因素,但是很多人也许并不清楚,商务上,或者说软件开发上的成本,到底产生在何处?如果我说,成本与需求的范围关系不大,你同意吗?

基于这种认识,我们说,对于需求分析人员来说,撰写需求文档不是目标,说服客户在需求确认书上签字也不是目标。为不是目标的任务付出大量的努力,不是一件值得称道的事情,因为你走在一条错误的道路上。 让客户明白自己的需要,离不开需求分析人员的主动性。几乎所有的软件开发方法,都没有强调需求分析人员在需求变化过程中的主动性。这使我感到非常不解。需求分析人员有责任主动地帮助客户完成自己的“愿望”。

需求变化永远是客观存在的。我们必须尊重客观规律(该来的变化迟早要来到),积极参与需求的变化之中,并尝试领导这种变化的发生、发展和消亡。主动出击,一切都会尽在掌控之中。

4.2 软件架构的本质

中医对人体的解释,使用了一种特殊的方式。它不是直接解释人体的结构,而是通过长期积累的经验知识,建立了另外一套理论模型。中医在验证和调整理论模型的过程中,注重的是外部现象,而不是内部结构。用软件术语来说,是关注接口,不关注内部实现。在人类对生命认识不足的今天,这无疑也是解决问题的一种方式。

4.3.2 软件框架

框架的本质是约束。框架的设计者正是通过约束来传递软件构架思想的。在软件开发过程中,采用一个框架就意味着接受一种构架软件的思想以及随之而来的约束,所以从某种程度上来说,框架担负着传承知识资产的重任。

4.3.3 隐喻的价值

不是所有的隐喻都直接有助于软件开发,只有越简单的隐喻才越有价值。在软件开发中,抽象的特征,是简化对事物的主观认识,隐喻的目标是抽象,因此,隐喻也肩负着简化认识的使命。

4.3.5 软件架构师的素质

软件架构师应该具备哪些素质呢?在我看来,应该主要包括以下几点。

  1. 诚实 知道的,就是知道的;不知道的,就是不知道的;知道不多,或者不知道,学习之后就知道了。这就是软件开发中的诚实。
  2. 想象力 软件架构师应该具备很强的形象思维能力。 软件设计中的创意灵感基本上来自想象(而不是逻辑推理)。在现实生活中,有很多循规蹈矩的人,他们不愿意想象,他们喜欢一成不变的架构,他们喜欢夸大未知的风险,他们对任何改变都会做出排斥的反应。
  3. 生活经验 生活经验,可以提供想象的素材;想象,可以建立隐喻;隐喻,可以帮助我们对现实世界进行抽象。 在抽象的过程中,生活经验发挥着巨大的作用。的确,有一些具有天赋的人,可以在较少的生活体验下设计出漂亮的架构。但是,在大多数的情况下,丰富的生活经验是想象的源泉和创造的基础。
  4. 逻辑思维 尽管软件架构设计中的创造大多来源于形象思维,但是在对细节的想象过程中,逻辑思维仍然起着重要的作用。软件架构师应该是个思路清晰的人。当他们向团队成员(或涉众)推广设计成果时,如果没有严格推理的表达,听众是无法理解和接受的。
  5. 理想主义者 技术领域不是职场。职场上的成功,不能代表技术上的成就。软件架构师应该是个理想主义者。在以自己为主刀医生的架构设计过程中要追求自己设计思想上的完整,不向非技术性的建议妥协。
  6. 兼容并蓄 软件架构师要坦率地面对自己的知识局限,勇于聆听和采纳任何有价值的建议。兼容并蓄的前提是,透彻理解外来贡献的内容,并确保外来贡献与自己设计思想没有冲突。
  7. 反思精神 软件架构师,应该具备反思精神。对于软件系统来说,通常没有所谓最好的架构。我们应该深入反思、寻找缺陷、酝酿改变、追求完美,这样才能实现技术能力上的提升。其中,反思,是提升能力的根本动力。

5.2.1 领域模型

软件是用来解决领域问题的,准确地说,是用来解决领域内的信息问题的。要通过软件处理信息,对领域问题建模是首要的步骤。建模的目标,是把对客观事物的认识转化为对数学模型的认识。一般认为,领域模型是软件实现的基础。

5.3 软件开发

对于一些经常使用到的知识,我尝试用自己的理解来给出解释。我发现提升技能的方式大致有两种:一种是涉猎数量惊人的细节,一种是不停地总结。前者眼界宽阔,但不善于系统表达和重用自己的思想;后者则相反。

首先,团队成员要有共同的工作目标。上级下达工作目标是很简单的,而人们内心中的工作目标往往不大容易统一。在一个团队中,有些人希望学习新的知识和技能,有些人希望验证或维护自己的权威,有些人希望获得升职的机会,有些人希望一鸣惊人。这些内心中的工作目标客观存在,无可厚非。可是,当个人的工作目标变得非常强势的时候,会对团队的工作目标造成恶劣的影响。

其次,团队成员的价值观应该比较接近。比较接近的价值观可以避免工作中大的分歧。如果在一个团队中,有人信奉激进创新,有人信奉稳健保守,那么,冲突往往就会在所难免。我认为,任何一种价值观都可以解决问题,可是当团队成员由于价值观差异而产生激烈冲突时,团队的力量就会荡然无存。

最后,团队成员间应该沟通顺畅,相互促进。在我看来,团队的精髓在于共享和互助。这种共享和互助基于共同的工作目标和相互认可的价值观。在共享和互助的气氛下进行深入沟通,是一种好的团队文化。

在现实中,团队就像是一个小社会,充斥着各种利益关系。我们会在以后的章节中讨论这个话题。但是,对于想提高编程技能的新手来说,保持一颗简单的心,从主观上去除团队的社会性,才是一条快速成长的捷径。

现实往往不会因为个人的意志而改变。对于编程新手来说,应该像个理想主义者一样来思考,什么是团队?什么是值得学习的知识?什么是必须舍弃的杂念?你的希望在未来,而不是现在

在软件开发实践中,保持一种快乐的心态是很重要的。快乐,意味着对工作的享受,也意味着站在人生的高度来认识生命。

5.3.2 掌握编程

我们来看看一些编程中常见的反面模式:

  • 意外的复杂度(Accidental Complexity):向一个方案中引入不必要的复杂度。
  • 积累再开火(Accumulate and fire):通过一系列全局变量设置函数的参数。
  • 在远处行动(Action at distance):意料之外的在系统分离的部分之间迭代。
  • 盲目信任(Blind faith):缺乏对bugfix或子函数返回值的正确性检查。
  • 船锚(Boat anchor):在系统中保留无用的部分。
  • Bug磁铁(Bug magnet):指很少被调用以致最容易引起错误的代码,或易出错的构造或实践。
  • 忙循环(Busy spin):在等待的时候不断占用CPU,通常是因为采用了重复检查而不是适当的消息机制。
  • 缓存失败(Caching Failure):错误被修正后忘记把错误标志复位。
  • 货运崇拜编程(Cargo Cult Programming):在不理解的情况下就使用模式和方法。
  • 检查类型而不是接口(Checking type instead of interface):仅是需要接口符合条件的情况下,却检查对象是否为某个特定类型,可能导致空子类失败。
  • 代码动力(Code momentum):过于限制系统的一些部分,因为在其他部分反复对这部分的内容做出假设。
  • 靠异常编程(Coding by exception):当有特例被发现时才添加新代码去解决。
  • 隐藏错误(Error hiding):在显示给用户之前捕捉到出错信息,要么什么都不显示,要么显示无意义的信息。
  • 异常处理(Exception handling):使用编程语言的错误处理系统实现平常的编程逻辑。
  • 硬编码(Hard code):在实现过程中对系统环境作假设。
  • 熔岩流(Lava flow):保留不想要的(冗余的或是低质量的)代码,仅因为除去这些代码的代价太高或是会带来不可预期的结果。
  • loop-switch序列(Loop-switch sequence)使用循环结构来编写连续步骤而不是switch语句。
  • 魔幻数字(Magic numbers):在算法里直接使用数字,而不解释含义。
  • 魔幻字符串(Magic strings):直接在代码里使用常量字符串,例如用来比较,或是作为事件代码。
  • 猴子工作(Monkey work):指在一些代码复用性或设计上很差的项目中的反复出现的支持性代码。通常会被避免或是匆忙完成,因此易于出错,有可能会很快变为Bug磁铁。
  • Packratting:由于长时间不及时释放动态分配的内存而消耗了过量的内存。
  • 类似保护(Parallelprotectionism):随着代码变得复杂和脆弱,很容易就克隆出一个类似的结构,而不是直接往现有的结构中添加一些琐碎的属性。
  • 巧合编程(Programming by accident):尝试用试验或出错的方式解决问题,有时是因为很烂的文档或一开始就没把东西搞清楚。
  • 馄饨代码(Ravioli code):系统中有许多对象都是松散连接的。 软代码(Soft code):在配置文件里保存业务逻辑而不是在代码中。
  • 面条代码(Spaghetti code):指那些结构上完全不可理解的系统,尤其是因为误用代码结构。
  • 棉花里放羊毛(Wrapping wool in cotton):常见的情况是某些类的方法只有一行,就是调用框架的代码,而没有任何其他有用的抽象。 还有一些项目管理上的反面模式,也很有趣。
  • 死亡征途(Death March):除了CEO,每个人都知道这个项目会完蛋。但是真相却被隐瞒下来,直到大限来临。
  • 拖后腿的无知(Heel Dragging Blindness):项目经理的无知拖了后腿。出于某些动机,员工趋向于减少努力来延长项目时限。例如,他们是按时间(而非结果)付费,又没有能顺利转移过去的后续项目。

5.4 负责制度

负责制度,是软件开发中最严肃的话题。尽管在本质上我是一个理想主义者,尽管我追求个人的内驱力,但是我不是一个不问收获的人。与个人的责任感不同,负责制度是一种外部的约束。在这种约束下,负责人的权利与义务应该是对等的。对软件开发来说,如果没有负责制度,就不可能在组织内部形成公平竞争的文化,也不可能为个人的创造力提供尽情发挥的空间。

建立负责制度的目的,不是为了惩罚某人,也不是为了永久取消某人的职业发展权利,它只是通过责任人利益损失的形式,来表明这样一个事实:没有金刚钻,别揽瓷器活。负责制度使人在享受权利带来的利益之前,认真地评估自己真实的能力,从而避免因为不称职而给企业带来的损失。

建议企业经营者(或者企业文化的倡导者),在文化养成阶段,要关注软件开发团队中的最小单位,这并不会丢掉“身份”(就连邓小平也会去关注南方的一个小渔村)。你可以尝试在这个小小的单位中建立负责制度,推动负责文化;你可以树立负责文化的典范;你可以在这个单位中培养堪以重任的责任人;你可以用这一团星星之火,点燃整个草原(整个团队)。另外,这个小小的单位不会给你带来商业上的任何风险。

5.6 质量保证

我们也可以注意到,在敏捷方法中没有过多地强调质量保证活动,这不代表它忽视软件质量的重要性。相反,敏捷方法要求开发人员不断地追求软件的质量。这种追求,体现在软件开发各个环节上的实践活动。例如,测试驱动、结对编程、持续重构等。这也是为什么我在前面说敏捷方法更加实际的原因。

6.2 软件测试的本质

软件测试的定义中有四个关键词:规定的条件、发现错误、衡量品质、是否满足设计要求。事实上,任何需要进行测试的产品都必须工作在这四个关键词之下。

在很多软件开发组织中,软件测试技术的应用能力是比较低的。更糟糕的是,这些软件开发组织即便经过了较长期的实践,这种能力也没有得到多大的提升。这体现在低效的软件测试过程和极低的自动化测试能力上。

7.3.1 思想准备

一个成功的团队,其成员在思想上是高度兼容(相互认同)的。对于软件开发来说,更准确地,对于协作紧密程度很高的“作战单元”(软件开发团队)来说,保持思想上(价值观和软件开发思想)的兼容性是成功的关键。

具有开放心态的人往往具备两种典型的特质。第一,是勇于承认错误;第二,是懂得感激。我想象不出,和具有这两种特质的人进行交流会有什么困难和勉强的地方,而这种开放的心态也是所有化学反应的催化剂。

7.3.2 如何组建团队

团队建设是一个长期的过程。在这个过程中,要注意避免下面几个误区(参考智库百科之团队建设条目)。

误区一:团队利益高于一切 过分推崇这一论调,会导致两个弊端。一个是容易滋生帮派主义,一个是容易导致个体的利益被忽视和践踏。

误区二:团队内部不能有竞争 没有竞争机制,容易导致以团队精神为名义的大锅饭,从而使团队成员的创造性得不到充分发挥,使团队失去活力。

误区三:团队内部皆兄弟 过于追求亲和力和人情味,会导致管理制度的失效。

误区四:牺牲“小我”换“大我” 过分强调团队精神,容易导致团队成员的个性创造和个性发挥被扭曲和湮没。

7.3.3 现实与理想

在我看来,要想提高员工的忠诚度,必须具备三个条件:企业的价值观与员工的价值观相吻合(或包容),企业的发展前景不错,合理的利益分配机制。

7.4.2 稳定的价值

稳定的团队会为企业的生产目标做出更多的贡献,这就是所谓的——“稳定的价值”。 一个稳定的团队,内部成员间的化学反应趋于稳定,来自团队外部的影响与团队成员对此产生的思想反应趋于平衡,更重要的是,团队成员的自我价值得到了认同(包括对于利益分配的认同)。 稳定可以使团队聚焦在生产任务上,而使技术之外的干扰降至最低。

8.2 项目管理的本质

我一直认为,好的项目管理,对于项目的成功并没有多少关键性的推动作用(有一定的推动作用),但是差的项目管理,对于项目的失败却要负一种关键性的责任(会输掉有赢面的牌局)。这种想法和传统的对于项目管理的认识不大一样。 在传统的认识中,项目管理仿佛是解决问题的银弹(这也可以解释为什么项目经理总是拿着更多的薪水)。但实际上,一手好牌才是你最应该追逐的。它会使你的获胜概率有实质性的提高,而不用每一局都玩心跳。

9.3 组件化开发

设计思想应该足够简单,可以用一个词或一句话表述清楚,例如,组件化、插件结构、微内核等。简单可以保证目标明确,而且有利于思想的统一。

工作成果应该长期有效。我们知道,应用平台、业界标准、技术框架、编程模型、业务逻辑等总是处在不断的变化之中。这些变化有些来自它们自身,有些来自市场的选择。从企业应用程序开发商的角度来看,可以保证长期有效的工作成果只有设计思想,具体来说,体现在我们对工作内容(代码)的组织方式上。长期有效的工作成果将成为软件资产的一部分。

工作成果应该保持独立性。独立性是保证工作成果长期有效的一个必要条件,而且更有利于工作成果的移植。可以移植的工作成果具有可重用的价值。

工作成果在演进时应该使影响面最小化,换句话说,工作成果应该具有良好的可扩展性。糟糕的扩展能力往往表现为牵一发而动全身,这会大大增加工作成本。

工作内容(代码)的组织方式应该有利于软件开发团队工作在一种高效的模式下。什么是高效的工作模式?很简单,就是一个最小的工作单元(个人或小组)可以工作在依赖关系最少的环境下。

9.5 妥协的方案

关于软件维护,我有两个思路可以与读者分享:第一,要坚定地展开重构;第二,在故事讲清楚后再开始重构。

坚定地展开重构,是对“历史遗留问题”者的挑战。所谓“坚定”,是指不向非技术因素妥协、不向夸大的未知风险妥协。

10.2 知识积累

几乎所有关于软件开发方法论(或者软件工程)的论著,似乎都遗漏了一个非常重要的话题——知识积累。

知识的种类有很多(从不同的侧面有不同的说法),需要积累知识的主体也有很多,我们在本节中讨论的是软件开发组织内的知识积累。 对软件开发组织来说,需要积累的知识是相当广泛的。从大的方面来说,除了数学、逻辑学、计算机科学等专业知识之外,心理学、社会学等相关学科也是需要关注的(软件开发是一项社会性的工作);从细节的方面来说,开发工具、编程语言、方法论、计算模型,以及本书所涉及的所有内容,都应该包含在内。

最后,我想总结一下本节的内容。

  • 知识需要积累;
  • 对软件开发来说,需要积累数学、逻辑学、计算机科学、心理学、社会学以及各种工具和方法论知识;
  • 知识积累是一个长期的过程;
  • 知识积累的最高阶段是常识;
  • 基础性研究是知识积累的一个重要形式;
  • 人是知识积累的关键因素;
  • 对知识的热爱是知识积累的基础。