有些人承认,上世纪90年代创造了很多过于复杂和过度设计的软件。其他人则表示,那些创新模式是必要的。无论怎么说,我们从中有学到什么吗?
是的,很多软件过度设计,过于复杂。早在90年代中期,当我作为一个开发人员开始我的职业生涯的时候,我的目标是成为一名架构师。认真的开发人员不会在选好更优的设计模式之前写代码——因为只有在选好之后,我们才能决定如何将业务需求融入到设计模式中。是的,我们会先提出架构,然后是宏观设计(层)和域模型(包括UML图表),之后我们再来考虑业务需求和详细的使用案例。是的,用户故事在当时还不成气候。我们在具备基本流和许多备选流的同时有很长的用户用例。
90年代对于开发者是一段很有意思的时期。我们终于有了互联网,来自于70和80年代关于结构化设计的一些学术论文变得唾手可得。此外,一些关于面向对象编程的书籍开始普及,虽然在很多地方还是一个新事物。
我依然记得我们用无数个月的时间来画类、序列、组件、部署、以及许多其他的图表,试图拿出最佳的软件设计,以便于将来某一天去构建。我们也建立了一些原型,所以我不能说我们在精化阶段没有编码或试验。当然,所有从原型中学到的知识会反馈到我们的图表中。
除了内部设计,90年代的我们还经历了分布式系统的大爆炸。我们有CORBA和DCOM。我们有客户/服务器以及多层体系结构。我们了解在使用数据库作为应用程序之间的通信点时会发生的单点故障。是的,我们碰到过类似于那样的讨厌问题。我们学习如何创建“服务” ——SOA正在成为一种构建的理念。我们还要学习考虑可扩展性和安全性。许多政府和银行以外的项目也变得非常庞大和复杂。
创建伟大设计是大多数最富有激情的软件开发人员的目标:这也是成为架构师和职业进化的途径。我们需要学习并善于软件设计。我们需要深入理解结构化和面向对象设计的原则。我们需要理解分布式系统的原则。我们需要深入了解内聚与耦合的各个层面。我们需要了解协变和逆变。我们需要学习如何设计组件的界限,包括合同和不变条件。我们需要学会如何理解动词和名词,商务语言,并将其映射到软件。如果不知道如何在我们的关系数据库中数据建模,那我们将永远过不了面试,并且更为重要的是,我们还需要学习如何让我们的查询执行良好。我们需要在正确的粒度中定义我们的协定架构,以便于其他系统更容易地消费它们,并同时处理好I / O和带宽问题。是的,我们花了很多时间去做这些事情。
90年代的软件开发都是设计和架构方面的事情。当然,还需要UML。
然而,虽然我们知道如何设计,但是我们依然在很多其他方面败走麦城。我们发布得不够快。事实上,在一些项目中,我们没有发布任何东西。大多数时候,所有试图创建完美设计的想法都因为缺乏快速反馈和无力应付快速发展的业务而完全浪费。我还记得我们曾经用来控制改变请求的庞大臃肿的,甚至在编码前就要制作完成的电子表单。是的,这真是天欲其亡,必先使其狂。而且是许多许多次疯狂。
时间又过去了20年,我们中的一些人渐渐意识到我们正在做的事情是错误的。于是,敏捷,精益等诸多原则和实践开始结合到我们的工作方式中。我们合并了许多新的设计和架构技术到我们的工具包中。我们吸纳了新的技术。我们结合了不同的方式到业务中以及如何构建团队中。我们知道虽然设计很重要,但没有什么比持续交付软件更重要。我们学会了如何获取反馈和迭代。我们知道我们需要负责地测试我们自己的代码。我们了解到我们需要支持我们的产品软件。我们知道创建原型和丢弃原型的价值。我们知道试验的重要性。但是,我们也明白了,为了用一种更好的方式工作,我们没有必要扔掉所有这些年来我们掌握的设计技能。我们并不需要丢弃在我们那段时间以及那段时间之前所有伟大的工作,主要关于70、80、90年代的软件设计。但是,我们学到的最重要的经验教训是,上下文才是王道,软件设计就是权衡。与交付无关的设计是没有意义的。那么,我们可以保持交付代码而无需一个坚实的设计基础吗?我不认为如此。
在当今的软件设计中,我看到的最大的问题之一是二元思维。如果X是坏的,那么Y一定是好的。如果X在A公司可有效工作,那么它也一定能在我们手里工作。知名人士在45分钟的会议谈话中以及在写在博客中的内容,就一定是真的。就是这样的思维模式。
二元思维导致的另一个常见错误是相信软件项目中的所有功能都有同等的复杂度,并且认为单一的设计选择都适用。但事实是,有些功能相当简单,有一些则非常复杂,还有一些则处于两者之间的某个复杂程度上,不一定一致。有的复杂在实现中,还有的则是因为在理解和模拟域时很复杂。有时,我们会觉得理解我们应该构建什么是复杂的。但是有时,复杂在与其他系统集成时。同一个功能的不同部分也会具有不同的复杂程度:有的部分可能微不足道,并且可以快速实现,但其他有的部分可能会非常复杂,并且需要你绞尽脑汁地思考。有些功能比较浅显(只要屈指可数的几行代码即可),而有些则是深不可测(因为不同的模块而延伸了成千上万行代码)。非功能性需求也可能会导致简单功能(业务方面)的实现变得非常复杂。而且有趣的是,所有这些在同一个软件项目中都可以找到。因此,如果我们赞同一个软件项目中的不同功能有不同程度的复杂性和规模,那么我们就不能应用二元思维到软件设计中——没有一种单一的设计方法可以有效工作于一个较为复杂的软件项目。
我们生活在一个可以方便快捷地获取信息的世界里。Google一下,我们就可以找到问题很多现成的解决方案。我的担心是,作为一个行业,我们失去了思考的能力。我们正在失去研究的能力和自己选择的能力。我们越来越趋向于寻找一个现成的方案。快捷的方式。我称之为“Stack Overflow解决方案”。
让我伤心的是,对某些人来说,软件设计是过度设计的代名词。让我同样伤心的是,“毫无设计可言”正成为敏捷、精益创业和快速交付的代名词。我不认为好的软件设计,敏捷和精益原则的发起人和主要支持者是这种意思。过度设计是坏的,但是一点都不设计同样是坏的。简单不等于废话。简单意味着设计得刚好够用。改写爱因斯坦的一句话,软件设计应尽可能的简单,但又不能简化。或者可以这样说:代码必须精心设计而不是过度设计。
在过去几年时间里,在面试了很多开发人员和审查了很多代码之后,我最关心的是,我们正在发展一种速成文化。许多我遇到的开发人员,虽然已经在这个行业淫浸多年,但对软件设计知识所知甚少。当然他们不会承认。如果你觉得我夸张了点,那么你可以去问问你团队中的开发人员如何解释内聚力和它的不同层次。问问他们共生、协变和逆变。问问他们耦合的不同程度和类型。问问他们关于合同和不变条件的设计。对于那些听说过SOLID原则的人,就问问他们SOLID原则出自于哪里。许多当今的开发人员说设计模式不好。那就请他们描述一些模式以及这些模式之间的区别,还有什么时候应当或不应当应用这些模式。问问他们关于不同的模式分类。问他们桥接器Bridge,适配器Adapter和中介者Mediator之间的区别。关于访问者Visitor应该解决什么问题?还可以问问他们:“备忘录模式Memento是什么?”。如果他们无法解释,那么他们怎么能说模式是坏的呢?
现在的许多开发人员不知道20世纪70年代,80年代,90年代间的软件设计基础。还有人则倾向于忽略它:这些都是过度设计的,他们不需要。好吧,我尊重每个人的选择。那么为什么我们仍然有很烂的软件?现在生产的软件真的比20年前出产的软件更好吗?为什么开发人员仍然苦苦挣扎于用TDD设计代码?为什么我们还在谈论遗留代码?在我看来,遗留代码就是设计得很糟糕的代名词,因为设计得很糟糕,所以代码才难以测试和维护。
我决不是在捍卫过度设计,也不是对浪费时间在UML中绘制图表表示赞同。我想说的是,在同一时间,我不会花时间去预先设计核心区域内部的类,我也不会尝试构建企业应用程序,一次一测试,不会在考虑其整体设计之前就开始编码。设计对于软件开发至关重要。如果我要构建的应用程序存在于旁边有许多应用程序的生态系统中,或者有着大量非功能性需求,或者需要遵守规定,是的,那么理所当然地我需要在开始编码之前周密思考其整体结构(宏观—设计),尽管我依然会开发其功能(“微观—层面”),一次一个测试。各个层面上都需要设计:从前期架构层面到作为TDD流程的一部分的即时微观层面。决定有多少设计是一种技巧——这需要找拐点,这方面内容我以前的博客中已经讨论过。
软件设计是软件开发中最重要的技能之一。良好的设计能使得开发人员协作,允许频繁添加和改变业务功能,并完成可靠的自动化测试。随着经验的积累,我们知道了如何快速识别问题并决定应该在这上面花费多少时间。我们还了解到,大多数的设计决策应在最后的责任时刻制定,也就是,我们最好不要在我们还没有充分了解问题的时候提交设计。
这就是为什么我会说我们在90年代所做的一切并非都是徒劳的。虽然我们过度设计了每一样东西,没有发布太多代码,但是我们学会了如何设计。我们学会了如何自己思考。我们学会了如何研究。我们学会了如何有依据地权衡。我们还努力学会了如何避免二元思维,以及克制对新趋势的兴奋。结合了敏捷和精益原则和实践的强大软件设计基础,让我们现在在快速提供软件和持续发布软件方面处于更有利的地位。
我们的目标是使业务具备敏捷性,而这可以通过可连续部署到生产的软件来实现。一次性将软件部署到生产并不难,难的是要一天多次部署软件到生产,然后几个月甚至几年下来一直保持着这个节奏,没错,这可不是一件简单的事情。我们需要大量练习和精心设计才能实现持续交付,这使得软件设计和TDD成为了两种我们必须要掌握的最重要的技术学科。
英文原文:We Did It Wrong, But Not All Was In Vain