本节书摘来自异步社区《Microsoft.NET企业级应用架构设计(第2版)》一书中的第2章,第2.2节,作者: 【意】Dino Esposito(埃斯波西托) , Andrea Saltarello(索尔塔雷罗)著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.2 软件项目的机制
如果你问:“什么导致项目失败?”,你得到的最常见的回答可能会把失败归咎到与业务有关的问题,比如说,缺少需求,项目管理不到位,成本估算不正确,缺少沟通,甚至各个团队的人员相互不配合。你很难看到坏代码可能导致问题这种情况。
有鉴于此,我们认为未被发现的BBM可以严重损害软件项目,但未能处理的BBM却可以真的毁了它。
最终,个体以及个体之间的实际互动才能真的决定软件项目的成功或失败。但是,组织结构及其整体文化也会影响最终结果。
2.2.1 组织文化
Apple公司的组织看起来很受“一人秀”(One-man-show)创意的启发,至少在史蒂夫·乔布斯时代是这样。一个人推动创意和策略,所有团队支持和实现这个策略。只要这个创意是伟大的,策略是合适的,成功就会到来。
你可能还记得发生在2008年的一件事,当时Microsoft内部的两个组出品了两个几乎等同的框架—LINQ to SQL和Entity Framework。外界很难理解是什么原因导致了这样的情况。
注意:
在Walter Isaacson写的史蒂夫·乔布斯传记里,你可以读到关于按部门划分公司的一段非常有趣的见解。史蒂夫·乔布斯分享了他对为什么是Apple而不是Sony在iPod的创意上取得成功的看法。为了实现创意,Apple首先要构建硬件和软件,然后就音乐的版权进行谈判。Sony这样的公司在硬件和软件上拥有的经验至少与Apple一样,此外,自家已经拥有音乐和电影的版权。那么,为什么Sony没有建立iPod业务呢?
根据乔布斯的看法,Sony的文化是在公司里拥有多个分部,每个分部都有自己的盈利/亏损账户。或许,从音乐版权获得盈利的分部认为从MP3播放器赚钱的分部是一个威胁。两个分部互相打架而不是为了公司的成功共同努力。这本书是Walter Isaacson写的《Steve Jobs》(Simon & Schuster,2011)。
1.团队和队员
有一个笑话是关于一个意大利队和一个德国队参加八人划船比赛的。德国队只有一个领队,其余都是队员,他们赢了这场比赛。意大利队调查输掉的原因,发现他们的队只有一个队员,其余都是领队。
团队就是让在技能上互补的人们互相合作。
注意:
划船队笑话的后续是讨论意大利队怎样计划报复赛,但这可能超出本书的范围了。不管怎样,如果你好奇我可以告诉你,意大利队解雇了队员,重组队伍,里面有4个领队、两个领队的上司、一个总司令以及一个新的老队员(因为他更有经验)。
在软件项目里,管理者和开发者都有自己的目标。管理者的做法比大多数开发者的做法更有压迫感。开发者向管理者回报,这有时会使开发者更倾向于直接接受任何任务和期限。不应该低估大多数开发者想要成为英雄的本能。开发者期望成为超级英雄,来到这个地球就是为了拯救世界。
举个例子,假设经理打算在星期一早上给一个潜在的客户做一场演示。他想确保他的演示可以给人留下深刻的印象。所以经理找到开发团队,要求在星期一准备一个演示。这可能打破当前冲刺(sprint),甚至影响了原来计划的工作。在公司或者团队里处理这种不可避免的利益冲突使用哪种方式最好?以下是几个可能的场景。
开发团队可能尝试适应新的期限,这样经理就能实现他自己的正当目标了。
开发团队不接受新的期限,坚持当前安排,这会妨碍经理让客户满意。
开发团队和经理一起找到合理的目标,既适合双方的安排,又不会妨碍任何人实现各自的目标。
根据我们的经验,第一个场景是最常见的,而最后一个则是最令人满意的。就开发者尝试适应任何安排改动而言,我们发现Uncle Bob的看法特别有价值。承诺尝试适应新的期限,开发团队似乎在暗示自己留了一手,就像在说:“是的,我们可以接受更多工作,但出于某种原因我们一直没有这样做;现在是时候用尽我们所有精力了。”但是,如果团队没有保留精力,尝试适应更紧的期限会强迫成员加班,牺牲他们自己的私人时间。这对开发者来说是不公平的,他们也该有自己的生活;同样对管理层来说也是不公平的,他们听到了谎言。
我们有过多少次尝试适应更紧的安排?如果我们做过这样的事,我们可能是为了避免潜在的不礼貌的对抗。成为一个团队的好队员的唯一金科玉律是成为一个好的沟通者,永远坦诚,永不说谎。
这里的关键字是协商,目的是分享合理的目标,在各自需要和安排之间寻求合理的折中方案。就前面的例子而言,一个好的折中方案可能是为某些工作创建分支,使用虚构代码创建一个仅适用于演示的专门构建。这不会从主分支删除很多内容,不会导致当前冲刺出现明显延迟,也不需要团队成员加班或砍掉特性。
2.Scrum救火员
尤其在敏捷环境里,每个没有预见的事件都是潜在的危机,都需要恰当地、及时地处理。
在Scrum里,常见的做法是赋予团队的一个或多个成员救火员头衔。
Scrum救火员负责迭代之外保护其他队员工作所需的任何额外工作。就像现实世界的救火员有时候会空闲一样,Scrum救火员也会空闲,或者在迭代的过程中,在项目上保持最低活跃度。
因为成为Scrum救火员可能非常无聊,所以这个角色应该让团队的所有成员轮流来做。根据我们的经验,20%的开发精力应该是你腾出来救火的最大值。由此看来,你可能会想,你的生产力将会缩减20%;实际上,你可能会得到更高的产出。
3.领导与老板
我们都知道成本是软件项目的痛点。成本是实现所有特性所需的时间的函数,包括测试、调试、文档以及一些其他周边工作。开发团队领导会负责这些,他通常向项目经理汇报。
有时候,这两号人物相互之间缺乏信任:经理认为开发团队保留精力,开发团队认为经理只想付出更少而得到更多。
毋庸置疑,领导艺术是关键的技能。
经理腰斩估算然后抱怨项目延迟的情况并不罕见。他们跑到老板那里指责开发团队,并要求更多资源。在这种情况下,他们会体验到*s法则的效果,即“向已经延迟的软件项目增加人手会使之更加延迟。”
领导和老板之间有着巨大区别。
首先,也是最重要的,老板期望团队为他们服务,而不是他们为团队服务。老板位于商业等级制度的最高点,可以命令其他人执行他们不愿意做或者不会做的任务。相反,领导专注于业务以及带领开发团队走出深沟。简单来说,这个区别就是老板培养跟班,而领导培养其他领导。
2.2.2 帮助团队更好地写代码
我们发现很多开发者似乎认为烂代码最终并没有带来太多伤害。
如果你数一下有记录在案的因代码问题而失败的项目个数,那么,我们认同这个数字并不会很大。但是,你不必创造真正的灾难导致软件项目损失大量金钱。
作为一名架构师,你可以做什么来帮助团队更好地写代码呢?
1.烂代码真的比好代码更昂贵
我们不清楚你的情况,但我们肯定认为写烂代码真的比写好代码更加昂贵。至少,我们认为在生命周期比较长,业务影响比较大的项目里是更加昂贵的。
听起来可能很简单,当使用烂代码(即创建、测试和维护它)的成本超过业务模型可以忍受的代价时,项目才会因它而败。同样地,如果公司设法使代码的成本保持在极低水平,没有项目会因为代码问题而失败。
这就是痛点。
你如何定义最终影响代码成本的因素?哪些动作组成了“写代码”:编码、构建、调试?你应该把测试当作一个附加的按需的特性吗?文档呢?缺陷修复呢?
有时候,管理者只是投机取巧,通过雇佣廉价开发者或砍掉测试和文档等缩减开发成本的手段来解决问题。
不幸的是,这些管理者没有意识到他们只是缩减了产生可能(但不一定)工作的代码的成本。产生刚好可以工作的代码只是问题的一面。现在,需求经常改变,复杂性不断增长,更糟糕的是,复杂性通常仅在行进的过程中才能完全了解。在这种情况下,产生代码只是影响总体成本的一个因素。代码维护和进化才是最大因素。
好的架构师都很清楚,只有写得好的代码,对软件原则和语言特性有很好的了解,恰当使用模式和实践,以及注重可测试性才能解决代码维护的问题。这使得编码比产生刚好可以工作的代码更加昂贵,但比维护和进化刚好可以工作的代码就廉价得多了。
2.使用工具辅助编码
我们认为成功的项目基于两个因素:懂得领导艺术的管理层,以及懂得代码质量的开发团队。
就编码而言,不一定有时间让开发者现在写代码,然后在往后的某个时间修复和整理它。每个开发者都会发誓,第二遍处理永远都不会发生,即使发生,也不会造成很大影响。
如果想在第一次就写出更好的代码,最好使用代码辅助工具。这些工具通常集成在IDE里,可以简化常见开发任务,使开发者的工作进展得更快,可以写出更好的代码。在最坏的情况下,代码可以写得更快,留有一些时间做第二遍处理。
自动完成、惯用设计提示(即根据语言或框架建议的惯用方式写代码)、代码检查、支持键盘输入的预定义代码片段,以及支持预定义和自定义模板等服务都是加快开发以及确保一致性和更好、更干净代码的实践。
代码辅助工具使开发得以持续发展,只需两次点击就能极大地改善你所写的代码的质量。代码辅助工具可以发现重复和没用的代码,使重构体验变得愉快,简化导航和检查,以及强制使用某些模式。
比如说,所有开发者原则上都同意适当的命名规范对于代码的可读性和质量来说是很关键的。(参见第4章“编写优质软件”。)但是,当你意识到你应该重命名一个命名空间或者一个方法时,你就会面临至少要在你自己的整个代码库里这样做的问题。这么疯狂的工作原本需要你自己在极短的时间内完成,现在可以由代码辅助工具代劳了。
但是,你要记住,代码辅助工具不是魔法,它们所做的只是让你付出更低的代价和更少的努力就可以写出更好和更干净的代码。除此以外,一切仍然取决于你。你在重构过程里以及在代码编辑阶段操作工具。
3.如何告诉别人他们的代码很烂
假设你发现你团队里有人在写烂代码。你会如何跟他们说?
这里涉及一些心理学方面的东西。你不想表现得尖锐,你也不想伤害任何人;与此同时,你不想其他人的工作在某一时刻伤害到你。沟通是关键,不是吗?所以你需要找到最佳方式在别人的代码很烂时告诉他们。
总体而言,我们认为让人注意到某些代码的最佳方式是不经意地问为什么用这种方式来写。你可能会找到更多背后的动机,不管是信息有误,态度不好,技能局限,或者你所不知的约束。
在没有确凿证据之前,不要断定你的编码方式更好。那么,你只需对问题代码背后的真正动机表现出好奇和兴趣,并且表达出想了解更多的意愿,因为如果换了你会用不同的方式来编码。
4.使每个人都变成更好的开发者
下面总结一下让团队写出好代码的金科玉律:
针对代码,而不是写代码的人。但通过写代码的人来尝试改善代码。
你可以通过某种方式修复任何一块烂代码。但是,当这种情况发生时,你不要责怪写代码的人;你可以帮助写代码的人改进他做事的方式。如果你可以这样做,你至少可以得到两方面的好处:你的团队得到一个更好的开发者,你的团队可能得到一个更快乐更有动力的开发者。你使这个开发者感觉更像英雄,因为他现在有了完成他的工作的最佳方式。
为了改进某些方面,每个人都需要培训和实践。最有效的方式是以敏捷的方式结合培训和实践。但是,我们经常看到一些公司购买了培训服务,让他们在短短几天内完成和交付,然后期望人们在接下来的星期一就能投入工作。事情并不是这样的,至少不会如此有效。
这让我们想起几年前非常流行的一个短语:在职培训。它指的是一边学习、一边做实际的工作。这起因于拥有不同技能的人在同一个团队里协同工作。
5.在签入代码之前检查一下
你的公司可能会有最好的编码标准,但你怎样实施它们?信任开发者是好的,但验证可能更加有效。结对编程和常规设计审核是检查代码库健康程度的具体方式。在一次典型的设计审核里,你可以和大家一起开放地讨论某些示例代码。这些代码可以是来自项目的真实代码片段,它是某些参与者写的,或者为了避免牵涉到情绪问题,也可以是为了阐明你想表达的观点而专门写的一段代码。
为了实施编码标准,你也可以考虑对你的代码控制系统采用签入策略,不管是Microsoft Team Foundation Server(TFS)、TeamCity或者其他系统。这个过程可以自动化吗?
今天,几乎任何源代码管理工具都提供针对签入文件实施控制的方式。比如说,TFS支持封闭签入(Gated Check-ins)。封闭签入本质上就是根据规则签入。换句话说,文件只有在符合既定规则的时候才会被系统接受。当你选择创建封闭签入时,TFS会要求你指定一个现有的构建脚本。只有在构建成功完成的时候,这个文件才会签入。
在TFS里,一个构建只是有一个MSBuild脚本,它可以使用各种任务来定制。TFS自带一些可以集成的预定义任务。比如说,你会找到代码分析(以前的FxCop)任务和一个运行选定测试列表的任务。因为MSBuild任务只是一个实现了约定接口的注册组件,所以你可以自己创建新的任务,添加自己的验证规则。
值得注意的是,JetBrains的ReSharper,前面提到的其中一个代码辅助工具,在它的最新版里提供了一组免费的命令行工具,可以在自定义的MSBuild任务里检测重复代码以及执行常见检查,包括根据你定义的自定义模板执行自定义检查。有趣的是,你甚至不需要ReSharper许可证就能使用这个命令行工具。
6.值得欣喜的是,这个项目不需要英雄
开发者倾向于超越自我,至少在他们深藏的梦想里,希望他们每周可以工作超过80小时来拯救项目,让客户满意,并且成为管理者和开发者同伴眼里的真英雄。
我们想改编诗人和剧作家Berthold Brecht的一句名言:我们总想活在不需要英雄主义的世界里。对英雄的需要以及由此而来的高压力通常源自不足的期限。
有时候,期限从项目一开始就不公平了。在其他情况下,期限是在进展的过程中被证明为 错的。
当这种情况出现时,情感上容易默许,但对指出不公平的期限的害怕产生对英雄的需要。沟通以及把问题挑明是一种坦诚,也是恢复更多控制以及降低压力的有效途径。
在软件里,我们可以说,你感到压力是因为迫在眉睫的最后期限或者缺少所需技能。如果及时沟通,两种情况都能很好解决。
我们不想要英雄,虽然我们自己也做过几次英雄(我们猜你们中的大多数也做过),我们认为英雄主义是一种例外情况。在软件里,例外通常是要避免的。
7.鼓励实践
几乎任何运动的专业运动员每天都会花上好几个小时来实践,到底是为什么呢?开发者和专业选手之间是否存在某种相似之处?看情况而定。
一种看法是开发者每天在工作中实践,并且没有与其他开发者竞争。有鉴于此,有人可能会得出没有相似之处的结论,因此没有必要实践。
另一种看法是选手经常练习基本动作,以便他们可以自动地重复这些动作。定期回顾面向对象基础、设计模式、编码策略以及某些领域的API可以使这些知识记忆得更牢固,回忆得更 快速。
注意:
写过多本ASP.NET的书,也实践过验证和成员系统,时隔多年,Dino最近在使用基于角色的ASP.NET系统时感到问题很大。“老兄”,他最近跟我说,“我上次处理角色是什么时候?”最终,创建基于角色的UI基础设施以及相关的成员系统耗费比预期更多的功夫。
8.持续改变是工作的一部分
持续改变是描述现代软件项目动态的有效方式。软件项目始于一个想法或者一个相对模糊的业务想法。架构师和领域专家需要收集一些正式的需求,使原来的想法或业务需要更加明显。
根据我们的经验,大多数软件项目就像活动目标,而需求就是把目标到处移动的东西。每次添加一个新的需求,这个环境以及系统的动态(在没有这个特性的情况下可以正常工作的设计)也会改变。需求的改变是因为问题领域有了更好的了解,问题领域变化很快,或者时间压力的问题。
需求波动(Requirements Churn)这个术语通常用来表示软件项目里的需求变化率(功能性需求、非功能性需求,或者两者都有)。高需求波动将会为BBM提供理想的藏身之所。
每当处理新的需求都重新审视整个系统的架构是切实避免BBM的唯一办法。重新审视整个系统的架构确实需要重构,也确实具有较大成本。这里的重点是找到保持低成本的方法。重构是其中一个很难察觉会为项目带来价值的东西。未能重构会导致这些价值流失,这很糟糕。
注意:
Twitter在2010年上线,当时的Web前端充满了客户端功能。大量功能是通过在动态下载的JSON数据之上即时生成HTML来提供的。在2012年,Twitter重构了整个系统,选择了服务器端渲染。这是一个架构层面的重构,毫无疑问是昂贵的。但他们认为这是必要的,并且持续服务数亿用户,不管是对还是错,反正它能工作。
作为一名架构师,架构和设计重构都是关键工具。架构师无法控制历史以及业务场景和现实世界的发展。架构师需要一直做出调整,避免重新构建。这正是重构派上用场的地方。