评价架构好坏是一个很主观的东西。既然大家写出来的程序都能跑,凭什么就说你架构好,我的架构就差?拿出来大家评评理,张三说好,李四说不行,王五说将就……究竟谁说了算?现在已经不是一个迷信权威的时代了,所以不管你多少光环加持,你都得说出子丑寅卯来,都得服众才行。
我觉得,这种现象的产生,抛开“同行相轻”和“流派之争”之类无厘头的东西,一个很重要的原因就是没有明确判断标准。所以在网上,常常就出现这样一种很热闹很奇葩很无奈的现象:我和你说性能,你跟我说安全;我跟你说安全,你跟我说扩展;我跟你说扩展,你跟我说维护;我跟你说维护,你跟我说成本……
这是一个很简单的道理,没有标准,就无法进行评判!所以,如果不能统一一个评判架构优劣的标准,我们永远无法达成一致。你说有标准啊,性能安全可扩展……但这样还是不行,标准过多,一样等同于没有标准。假设以100分为满分,性能占多少分?安全占多少分?可扩展又占多少分?因为要想提高性能,就可能就要牺牲可扩展性;要想安全,就会牺牲性能;要想……就会……;哪一方面更重要,哪一些可以牺牲?如果不是胸有成竹的话,最后还是会左支右绌手忙脚乱,乱成一锅粥。
所以我再提出一个观点:以是否实现架构师的设计目标为标准。如果说一个系统的架构,最终实现了架构师的设计目标,我们就可以说这是一个好架构;如果说没有能实现架构师的目标,这就是一个不那么好的架构。
“等一下等一下”,你要是反应够敏锐的话,肯定会跳起来,“这是不是太主观(儿戏)了?我随便一堆烂代码,然后告诉你,‘是啊,我设计的目的就是让他烂,越难越好’,按你的逻辑,这样也行?”(⊙_⊙),嗯,你要是这样玩儿我还真没办法。但是话又说回来,要让一堆代码能跑又还够“烂”,也还不是一件容易的事,你觉得呢?
总之,我希望大家能明白我的意思:架构师开始一个新项目,应当设立一个适当的设计目标;然后通过架构,努力实现其预定目标。如果最终系统的运行,符合其设计预期,我们就可以说:这个架构不错还行!反正,架构就出了问题。
我们的目标
软件行业有各种各样的系统,每一种系统的开发都可能会有不同的目标。比如导弹发射的系统,我们可以想象,目标(甚至是基本要求)肯定是:1、稳定(绝对不能走火);2、迅速反应(不允许按下发射按钮后一分钟导弹才开始发射)。你可能觉得这种要求很好啊!任何系统不都是应该满足这样要求的吗?比如我在买T恤,结果给我发一条丁字裤,这怎么行?一个网页半天打不开还有理了?我还真得答一句,它就是有理了。“存在即合理”,这里的合理,合理在成本。我们目前日常使用到的绝大部分软件,都是有bug的,而且是一堆的bug,但我们仍然在使用它们。如果你想使用像“导弹发射”一样稳定精确迅捷,可能最后的结果只有一个:你用不起。
所以,其实我们是做了一个妥协,“便宜点,将就用吧”。我们为了达到我们的基本目的,牺牲掉一些“无关紧要”的东西。对于很多追求卓越的程序员来说,这种牺牲妥协是难以接受的。“白玉微瑕,你让我怎么能够接受?”——但很多时候,你必须接受。这个问题这个观点,我们会在整个系列中不断的提及。请试着接受;如果你暂时还不能接受,请牢记:没有牺牲,就没有胜利!
那么,我们的策略是:特色突出、整体均衡。说得更直白一点:有亮点,没硬伤。这就够了!而我们的亮点就是:可维护性。(注意:不是可扩展,可维护性包含可扩展,但不仅仅是可扩展)
为什么是可维护性
幸或者不幸,我进入软件行业之后,绝大部分的工作是几乎所有程序员都不齿厌恶的维护。我曾经维护过一个有十年历史的、糅合了C、VB、java、C#各种语言在内的一个物流系统的部件。我在那家单位工作了一年多,说实话,直到我离职,对整个系统,我连边都没摸到——这个系统太大了,而且连我们都只是其主营单位众多单位中的一个。
在我花了两周的时间找到一个bug的位置之后,我以为我终于明白了为什么会说:“维护和开发的花费比是80:20”。但这只是我以为——现实更加残忍:差不多一个月后,我又花了一个星期的时间,找到了另外一个bug的根源,正是我fix前一个bug所产生的。我泪流满面,有没有?脑子里一下就蹦出个词:“按下了葫芦浮起了瓢”!总之,如果fix前一个bug就会导致后一个bug;如果fix后一个bug,就会导致前面的bug。我忘了最后是怎么处理这个问题的,依稀记得是让项目经理去和稀泥去了。因为这不是一个很关键很常用的功能,所以最后大概是不了了之吧。
后来我了解到,很多的开发项目,是这样一个流程:一群人根据文档开始开发,几个月后通过验收上线;然后开发团队解散,留下一两个项目组里最菜的菜鸟做“维护”。Game Over!皆大欢喜。这种现象,在各种团队(尤其是以项目计价的廉价团队)中更加的突出(这或许也是大家普遍歧视单位的一个原因?)
既然是这样一种开发模式,很多开发人员根本体会不到维护的痛苦。在他们看来,“维护嘛,修修补补,加一两个if...else而已,让我们开发人员做更高大上的工作吧!”但他们也不是总这么幸运,有时候,他们会被抓去“填坑”。据说最通常的做法,就是在“老坑”周围再挖一堆“新坑”,填平之前的老坑即可。周而复始,直到有一天,“受不了啦!我们重写吧!”——等等,为什么不重构?呵呵,好问题,你觉得呢?
需求变更
很多程序员把这种困境归咎于“需求变更”。如果不是那些傻逼客户一天到晚的改需求,我一定会做出一个完美的作品!
或许是因为我是半路出家的原因,和很多程序员相反,我觉得:不是需求变更驱动着软件的不断更改,而是“软件可以随意更改”的这种特性刺激了不断的需求变更。你装修好的房子,是不是住一段时间之后就会觉得这里那里不合适?这里少了一个插座,阳台上该加一个龙头,橱柜用着不顺手……“要是能改改就更好了!”,只是这样的改动太费力,所以大多数时间我们都还是算了。但软件可以!理论上怎么改都可以。想想软件真的是一种很特殊的商品——它是可以交付“半成品”的。你先用着,如果有问题我再改改,有新需求我再改改,一直可以改到面目全非。没有在其他传统行业里待过的程序员无法理解,“可以随意更改”是一种多么出色的特质。这意味着产品可以自我进化,应对各种变化,可以永生!想象这样一台“汽车”,开始可以在马路上跑,过段时间改一下就可以在水里游,再拆装一下可以当摩托拉风,堵车的时候展开翅膀……这是什么样一种屌爆天的体验啊?
所以,“拥抱变化”绝不是一句口号,这是一种胸怀。
作为示例的这两个系统,我是希望能用他们一辈子的。但我甚至无法想象一年之后他们会是什么样子——他们需要接受市场的检验,应对技术的升级换代,会有各种想象不到的变化。所以,可维护性无疑是必须放到首位的。
为了可维护
明确了架构的首要目标,我们就可以做一些基础的选择了。比如开发语言,可是是面向对象的C#,不需要“性能卓越”的C。
说道“面向对象”,可能有些同学就会比较high,脑子里就会冒出“抽象”、“封装”、“设计模式”等各种高大上的东西出来。但我不得不提醒你们:首先,这些都是微观层面考虑的东西,而架构是宏观的;然后,这些都不是架构,而是润滑黏合支持架构的东西;最后,在其他条件不变的情况下,系统中这些东西用得越少,说明架构越好。
我们以“设计模式”为例。大家在学习设计模式的过程中有没有这样一种困惑,“这样继承封装多态乱七八糟的绕来绕去的干嘛?”我花了很长一段时间才明白,要理解设计模式,必须要明白三个字:“不得已”。是迫不得已,才用设计模式来解决一些特定的问题,而不是说正常的代码就应该这样写!这种迫不得已,有很多种原因。个人觉得最容易理解的就是“适配器模式”,因为出现了接口的冲突,所以我们不得不进行适配。但一个很自然的问题就是:为什么不直接改接口让他们自然融洽呢?这不是一种更自然更直观的解决方案吗?答案很有可能就是因为架构——大的架构已经确立,局部必须服从整体。那么,如果一个完全理想化的架构,是不是根本就不应该出现这种问题接口冲突的问题,因而根本就不需要这种设计模式?
所以,我说设计模式之类的东西是润滑剂是黏合剂,他们的作用是弥补架构的局部缺陷,更好的支撑架构。更极端的一种说法可以送给痴迷于设计模式的同学:设计模式是药,没病就不要吃药!
那么,为了可维护性,架构中究竟应该注意些什么?这是一个很大的话题,开篇我们只说一点。
模块划分
模块有大有小,大可以是一个分层一个项目,小可以是一个方法一个类。我们通常的做法是由大到小,逐步细分。
模块的划分是相当的考验架构能力的。良好的模块划分,能够让我们方便的安排人手、合理的组织项目进度、迅速的定位代码……各种好处说都说不完。所以还是说说不好的模块划分有什么问题更容易一些,嗯,这个好像根本就不要说,想想你在一堆乱七八糟的代码里不断的F11的情形吧!
我个人认为,模块划分的难度在于“整齐”和“灵活”之间取舍。通常来说,大的模块我们都是“一刀切”,着重强调的是“整齐”,比如口熟能详的UI层、BLL层和DAL层,但这种“一刀切”的做法,更多的是一种无奈。我们的人类的思维局限决定了我们在考虑复杂问题时无法深入到每一个细节,所以只能先“大而化之”的把一个复杂问题先进行简单化。这样带来的一个严重的副作用就是,限制了代码的灵活性;而灵活性,正是应对复杂变化的有效武器。所以,在更小一些的模块(比如说:类)里,我们引入了丰富多彩的抽象继承设计模式等一系列充满各种灵活性的机制,以弥合“一刀切”造成的问题。这一松一紧一张一弛中“度”的掌握,就只能说是一种艺术了。
模块划分,笼统的说教用处不大,我们将在后面的文章中结合具体情况逐一说明。但我希望大家能够明白:模块划分是必须的——这种必须,是一种无可奈何的选择。所以,喜欢从页面直接写sql到数据库的同学,老大让你把你的代码拆成几段放到不同地方的时候,不要嫌麻烦;喜欢把一个简单项目切成七层的同学,先仔细想不想这样做是不是真的有必要。
代码之外
为了代码能够长期有效的维护,我们还需要做很多工作,比如良好的文档、完善的项目管理流程。但我想说的,还是不是这个,而是代码之外的因素对项目架构的影响。比如开发团队的背景能力偏好,一群C#程序员,你一定要整个node.js,这纯粹是给自己找不痛快。除了这些稍稍用脑袋想一想就能明白的东西,有一件事,很多程序员并没有意识到。
架构的一个天然目的就是:让代码更智能让程序员更傻瓜。换一张说法就是,架构要“创造便利,让程序员更关注业务”。
这可能是一个让程序员感到悲哀的事实。正如机械师不停的发明,让机器变得越来越聪明,取代流水线上的工人,最终取代了他们自己。从某种意义上说,我们都是自掘坟墓的人。一个良好的架构,就应该是让每一个普通开发人员,都是一个个尽量廉价随时可以替换的螺丝钉,这样才能保证系统永远健康正常的运行下去。告诉你这个事实可能让你一整天都不开心,但接受这个事实之后能帮助你在工作中变得更加的“心平气和”。螺丝钉就要有螺丝钉的觉悟;更何况,当好一颗螺丝钉也不是一件很容易的事。