从我接触软件开发开始的很长一段时间里,「架构」一直是一个颇为神秘并且高深莫测的概念。知道搞架构很难、架构很重要的同时也觉得离自己很遥远:架构应该是架构师该操心的吧?我要学多少年才能成为架构师啊……

在公司经历了几个项目的历练之后,我才终于改变了曾经对架构的偏见,建立了较为清晰的认知。恰逢公司最近在推行软件能力提升变革,今天就来浅谈一下我对架构的理解。

为什么需要良好的架构

面向未来架构 面向当下实现 面向过去反思

提到「架构」一词,往往让人联想到宏伟的建筑、飞机船舶的龙骨,或者企业的人员组织架构、文章的内容组织方式。没错,软件架构和他们都具备相同的特征:

  • 面向未来
  • 协调合作

面向未来指的是在很长一段时间尺度上需要稳定可靠。建筑以十年、百年为时间尺度;飞机轮船的设计目标也是需要服役数十年;软件虽然快速迭代,但是稳定演进也格外重要,出于兼容性、成本等诸多方面的考量,动不动就推倒重来是绝对不可能的。

协调合作指的是其内部组件有序、和谐的合作。企业需要良好的组织架构管理员工,使员工间、部门间能良好合作,力出一孔;一篇文章行文也讲究架构,如何组织文字,为整篇文章的立意、节奏等服务,使读者读的舒服,能跟上作者的思路。软件像乐高积木一样,由很多小组件堆积而成,使组件间高效、良好地合作就需要良好的架构。

架构设计不是开发人员间对一对函数要有什么参数、某个功能是你做还是我做,更不是如何划分模块、如何相互扯皮。很多人会把架构设计想的很简单,也有很多人会觉得架构设计不如功能快速实现重要。

我们时常会听到这样一种论调:「领导处于比你更高的视角,他们能看到你看不到的事儿,实现功能抢占市场比对代码力求完美更重要」。这应该是一句对忽视架构设计的万能说辞,而且通过偷换概念使其似乎十分合理。其实,在架构设计上加大投入并不意味着开发周期的同比延长:

  1. 从长远来看,良好的架构设计可以缩减产品整个生命周期的开发成本。匆忙敷衍的代码会是一颗定时炸弹,而后续迭代的迁就方案会让这笔技术债务像滚雪球一样越滚越大,终究会有一天东窗事发,不得不再投入大量成本推倒重来,这个过程的损耗是不容小觑的;
  2. 架构设计是每个开发人员的责任,开发人员能力的提升自然会促使产生良好的架构。我们时常会自我怀疑,公司是否应当为员工的能力提升支付薪水,或者说员工是否有权利「带薪学习」。我认为这个问题的答案显而易见,所谓「养兵千日 用兵一时」,对于军人来说,难道操练武艺是自己的事情而不应由国防经费埋单?

忽视开发前的架构设计投入,我觉得要么是没吃过架构腐化再推倒重构的苦头,要么就是已经对无休无尽的加班麻木。

那么如何才能设计出良好的架构?

如何设计良好的架构

站在巨人肩膀上 你比巨人还要强

软件行业经过了这么多年的发展,早已经总结出了一套切实可行且卓有成效的方法论。最根本的指导思想就是「六大设计原则」了。在架构设计过程中刻意实践,就一定会有效果。

架构细节如何设计、如何选型、如何权衡依赖于架构师丰富的经验,但指导原则是每一位开发者都可以在实践中不断探索体会的。

开闭原则 可以说是整个面向对象编程的设计总纲,即「拓展现有功能,而非修改」。就像人间正道是我们通过努力不断拓展自己的生存技能,而非与撒旦交易出卖灵魂换取什么超能力,那只会变成一个大反派,与人和魔鬼都不兼容。

总的来说,设计原则我认为可以分为两大类理解。

抽象

抽象,是对看似不同的问题的本质的提炼、具体细节的隐藏。人生苦短,如果不能对问题抽象并举一反三,那只能淹没在无尽的琐事之中平庸一生。软件工程也是一样,抽象做不好,就难以从无尽的冗(la)余(ji)代码的泥沼中抽身。

抽象,也是一种协议。协议是什么?是大量个体面向未来协作时的一种约定。秦始皇统一度量衡,就是推行了一套协议,统一的度量衡促使了社会合作效率的提升;手机接口也是一种协议,比如 TypeC 接口,你手机没电了却忘带了原装充电器,借别人的充电器一样可以充,因为你们的手机充电口都遵循了同一套协议。

程序设计中所说的「接口」和现实生活中的接口其实是一个意思,是一种抽象、一种协议。依赖倒置原则说的就是「永远面向接口」编程:使遵循了同一套接口协议的组件可以随意插拔,灵活易复用。同时,这也是迪米特法则的一种体现,即「最少知道」,接口的使用者无需操心内部细节,某个组件只要实现了这个接口,拿过来用就好,里面到底用了何种数据结构、算法、逻辑都无需操心。

迪米特法则的「最少知道」是一种「舍·得」的智慧,知道的越多意味着耦合程度越大。舍弃自己对他人隐私的好奇、舍弃把自己全盘托出的冲动,彼此间只通过刚好能够和谐相处的信息维系就好,当然,不同的关系程度这个最少信息量也是不同的。这样虽然可能会陷入孤独,但是儿女情长、身份曝光往往最后都会成为英雄的致命弱点。

粒度

我惊奇地发现:很多新手开发者和我当初一样,总倾向于写大而全的类、功能复杂的函数。我想这应该是受个人英雄主义熏陶的产物,总想着由一个超级英雄来解决一切问题,殊不知,超级英雄只存在于人们的美好想象中,现实中只有无数平凡的你我。现代社会之所以生产效率极大地提高,是因为分工合作。亚当•斯密在《国富论》中列举了分工的好处:

  1. 工人可以专注于做一件事儿,熟能生巧;
  2. 有效避免一个人在多种工作间切换的效率损耗;
  3. 工序细分简化更方便制造机器,通过机器可以大幅提高生产效率。

我认为还有一个,职责单一意味着「可快速替换」,一个工人的旷工对整条产线的影响微乎其微,可以迅速替补。

「分工」就是对一个复杂任务的拆分,拆分的粒度是关键。粒度太大,无法充分展现分工的优势;粒度太小,又会过于杂碎。

单一职责原则接口隔离原则就是分别从类和接口的视角描述拆分与粒度的问题。这两个原则就是在告诉我们,不要写一坨大而全的类,要适当地按职责进行拆分;接口也不要堆在一起,要适当拆分。拆分之后才能更灵活。天天挂在嘴边的「解耦」说的就是这事儿,但是这个耦在哪儿解、怎么解就靠平时的积累了。

设计原则可以说是根本中的根本,设计模式、编程规范都是由设计原则衍生出来的具体方案与最佳实践。

为什么设计良好的架构很难

没有为什么,架构就是很难

抽象是一种高级的思维方式,可以说正是人类独有的抽象能力帮助我们爬上了生物链的顶端。每个人的抽象能力不同,对架构的理解也就不同。通常,抽象能力依赖于积累的经验,而经验源自刻意实践、不断试错,摸爬滚打是成长的必要条件。架构师固然需要尽职尽责操刀架构的设计,但毕竟架构师个人的精力有限,不可能去指导每一个组件的设计,这就需要组织能给每个成员足够的试错空间,我特别喜欢一句话:

Solve the problem before you code it

操起键盘之前要强迫自己多思考,想清楚了再写,写完之后过了一段时间,就能收到当时设计的优良与否的反馈,再强迫自己进行重构,不断重复这个过程。

但是现实就很残酷了,写前的思考和写后的重构都成本高昂,紧张的项目节奏往往催着你拼命往前跑,而当节奏降下来之后又会因为懒惰对自己松懈,看着沉重的历史包袱叹一句:「还是算了吧」。

另一方面是管理问题,我目前还没有看到一种行之有效的管理实践可以有效牵引对架构的优化(当然遇到问题、瓶颈需要解决不算,有心人未雨绸缪的个人行为也不算,我指的是对每个人都具有牵引力的模式)。优化架构投入巨大,收益却因为没有对比难以衡量,这其实也是生活中普遍存在的一种没办法解决的问题;人的一生每时每刻都在做各种选择,你永远不会知道当时你做了另外一种选择会对你的未来有何影响,再多的分支,你也只能走一条。我们常说一次把事情做对,可是如果不先做错,谁能透过你看似并不费力的表象看到你背后费尽心力的努力呢。

还有就是开发人员个人能力的问题,新手开发者因为缺少经验,代码写的烂是必然的。这么看,做能力提升变革确实非常重要,但是软件能力提升不是考几个考试就能牵引得了的。在经济学中,这叫做提升寻租成本的管理手段,就是说,不合理的政策限制往往会迫使人们通过旁门左道利用政策为自己谋利,最终导致整体成本的提高,资源的浪费。考试使大部分人把精力放在解决考试本身,而不是提升能力。能力提升是一个长期积累的过程,没有捷径,不是可以靠攻关、突击、考核实现的,应该是一种自发性的、内源驱动的行为。考核确实会扒人一层皮,但扒完皮不代表一定可以化茧成蝶,可能只是剧痛过后留下一身伤疤而已。

后记

架构设计是反复权衡利弊之后的一种取舍,一种平衡。如果一个人跑过来只跟你讲他的架构设计如何 Niubility 却绝口不谈他舍弃了什么,记住,他在忽悠你。

转载自我的公众号 2019-08-04

语雀内容