「掘金·启航计划」
如果你面临一个复杂的问题没有头绪,那就提高一个次元去解决 —— 广中平佑
写在前面
随着移动互联网的红利见顶,各大厂商进入到细分领域的“内卷时代”。这就要求工程师需要具备多种技能(语言、架构、思维模式等)来应对环境的变化。面对层出不穷的各种框架、语言,很多开发者不由自主地会陷入迷茫,各种贩卖焦虑的小作文随之而来。这时候只要跳出技术细节,站在更高的维度重新认识一下软件工程便能破局。软件工程的核心是高质量(高迭代速度、高稳定性)地解决复杂的领域问题,任何技术细节(包括但不限于框架、语言、架构)都是为了达成这个目标的手段而已。 因此不要过分陷入具体的技术细节无法自拔,提高一个次元就能探索到底层逻辑。
我眼中的软件工程
软件工程起源
每门学科都是为了解决某个问题而确立的,“软件工程”这门学科也不例外。由于计算机的蓬勃发展及应用领域的不断扩大但是软件的开发没有相应的的标准,1970-1980年代出现了软件危机。很多大型软件出现了延期、重大的质量缺陷,甚至造成人员伤亡。鉴于软件危机迫在眉睫,北约的计算机学家于1968年在西德首次提出“软件工程”的术语,倡导以工程的原理、原则与方法进行软件开发,以解决软件危机,这个时间节点也是“软件工程”这门学科被确立的里程碑。
软件工程的本质
正如IBM大型电脑之父佛瑞德·布鲁克斯在1986年发布的论文《没有银弹:软体工程的本质性与附属性工作》中描述的观点:软件的本质是通过代码描述真实世界的运行,软件工程的本质问题是解决业务逻辑的复杂性。
- 软件的本质性:把真实世界的运行机制抽象到计算机维度的建模过程
- 软件的附属性:将模型通过编码等方式部署到计算机上
大多数程序员在各种论坛上探讨的语言、框架、架构等问题都不是软件工程的本质问题。现代的开发语言解决的是软件工程的附属属性,软件的本质性仍然没有银弹可以快速提高。
关于领域建模(Domain Driver Design)
真实的世界之所以能正常运转必然存在各种逻辑自洽的机制或规则,因此只有严格贴合真实世界的运行本质(即通过领域建模把这些机制与规则抽象出来)才能得到正确的软件系统。举个例子:某人生病后去医院,如果出现了误诊,那医生、科室主任、院长、医疗负责官员都会有一定的判责,有没有很熟悉,这就是责任链模型。前人已经把这种模型抽象到设计模式中在很多领域复用。软件的迭代过程本质上是真实世界的领域运行机制需要更新,如果建模恰当便可以最大限度地保证工程的迭代速度与健壮性。
要求团队中的每个人都有能力对业务进行抽象与建模是一件不现实的,所以直接拿前人们抽象好的模型进行落地就能解决部分绝大部分业务问题。23种设计模式、《领域驱动设计》等方法论便是领域建模最好的参考资料。
关于架构
架构的意义是保持程序员的高产能,使业务迭代速度不随着时间与代码量的增加而变慢,因此架构分层便极其重要。 合理的分层可以清晰地定义模块之间的边界与职责,使经常改的与不常改的代码分离并保证领域代码的可移植。经过分层的架构设计至少会有以下几个好处:
- 代码文档化:代码所在的模块已经能清晰地说明此代码的作用,在不需要额外文档的前提下实现了代码的自表达
- 提高团队协作效率:新人入手成本降低,在不依赖老人手把手指导的前提可以知道改哪(探秘成本),怎么改影响范围最小(风险成本)
- 业务逻辑框架无关化:架构分层后可以实现业务逻辑的框架无关化。以财务系统的前端为例,不管你使用的是什么开发框架(React、Vue、Angular),业务逻辑都是一样的,只是业务逻辑表达到具体框架前的胶水层需要适配而已
- 代码健壮性提升:业务逻辑框架无关化后,可以使用 TDD 等方法对业务逻辑进行单测
提到架构设计就不得不提马丁大叔的《架构整洁之道》,里面的四层架构思想实乃巅峰之作,推荐阅读。
灵活性 VS 成本
高灵活性往往带来高成本
不要过分追求架构灵活性,灵活性往往带来很多额外成本(高开发成本、高维护成本、高复用成本)。马丁大叔的书《分析模式—可复用的对象模型》有一个经典的?:
考虑编写一个模拟合球比赛的软件。可以通过描达表面特征的用例来评估这个问题:“玩家击打白球,使它以一定的速度移动;接着,白球又以一定的角度击中了红球,使红球以一定的方向移动了一定的距离。”可以拍摄几百次这样的事件,并测量球的速度、角度和移动的距离。然而仅靠这些恐怕并不足以写出好的模拟程序。要写好这个程序,需要透过表面现象去了解其背后的运动定律,包括质量、速度、动量等。理解这些定律将会使软件开发容易得多。几乎肯定的是:开发这个软件用牛顿定律就足够了,没有必要使用爱因斯坦定律(分子间的碰撞)。在台球比赛这种场景中,分子间的碰撞对运动的影响微乎其微。
作者结论:过分的强调灵活性是一个糟糕的工程实践,我们需要在系统各种成本与提供的功能之间进行权衡,在满足需求的前提下不要添加不太可能用到的灵活性。
限制灵活性反而能释放生产力
很多人往往把限制灵活性看成一个“违背祖宗”级别的错误决定,但是俯瞰软件工程发展的历史长河,大幅提高开发者生产力的三大编程范式都是通过限制编程的灵活性实现的。
- 结构化编程:对程序控制权的直接转移进行了限制与规范——限制 goto 语句的使用
- 面相对象变成:对程序控制权的间接转移进行了限制与规范——限制函数指针的使用
- 函数式编程:对程序中的赋值进行了限制与规范——限制赋值语句的使用
也就是说,自从软件产生的那一刻一直到今天,计算机与程序员的交互过程并没有本质上的改变。之所以现在的生产力比之前大幅提高,都是因为编程范式对程序员的写法灵活性进行了限制。
关于开发过程模型
好的工程一定是可持续交付的。 下面探讨一下软件的开发模型。
软件的开发过程模型有很多:瀑布模型、V模型、敏捷模型等。对于团队规模较大且长期维护的软件系统,我更倾向于瀑布模型。其他的模型依赖较强的团队执行力与技术氛围,而大部分业务开发团队经常面临排期紧张、开发资源不足等问题,往往分身乏术无力承担多余的沟通成本,瀑布模型基于严格的周期划分,明确了各团队的交付时间点,在一定规模的团队可以大幅降低沟通成本。瀑布模型是经过时间考验的经典模型,国内团队基本上都是采用的这种模型或基于这种模型的变种。
关于团队
再好的设计也需要团队去执行,因此团队很重要。世界级软件架构大师 & eBay前 CTO Martin L.Abbott 在他的著作《架构即未来》 中打通了技术架构与组织架构的模型边界,在他看来不管是面对代码的技术架构还是面向人的组织架构,可扩展、高内聚低耦合等设计原则都是通用的。通常来说困扰团队开发效率的因素总结起来有这几个:
- 沟通成本:团队内成员间或两个团队间的沟通成本,应运而生的是 BFF 等技术尝试降低传统前后端团队的协同成本。
- 职责不清:正如SRP原则(任何一个软件的模块都应该有且只有一个被修改的原因)所述,任何违背 SRP 原则的代码设计终究会因为边界不清导致的架构腐化从而引发各种线上问题。相类似的,团队间如果职责不清也会出现重复建设、灰度地带无人问津等问题。因此顶层的组织设计者在基于业务设立组织边界的时候就需要考虑到未来的扩展性与组织边界。
未来
正如前文所述,随着移动互联网的红利见顶,后续的环境要求技术为业务提供更加强有力的支持。我们作为软件工程浪潮的参与者,应该顺应时代的要求在更高的维度思考才能看清本职工作的意义与价值,心有猛虎,细嗅蔷薇。
最后借用马克思唯物主义的一个观点来总结一下我眼中未来的软件工程:“事物的发展是波浪式前进与螺旋式上升”。传统软件工程理念与 GPT 等新兴技术相结合创造出新的工程形态也不是没可能~