近两年来软件工程上的得与失(一)

算上大四的话,我在创宇工作了两年多时间,在这期间,我经历了一些开发项目,有些是非常简单的项目,有些是正式的大型项目,这些项目让我在各方面都成长很多,那么我今天想回顾的是,在这些项目中,我有哪些软件工程方面的收获。想到哪说到哪,欢迎交流内容。

我对软件工程挺感兴趣的,在网上买了些课,就数软工相关的看的最津津有味。虽然在我目前这个阶段,并不需要关注太多软工相关的技能,但我就权当是看闲书了。在创宇工作的时候,尤其是后期,我有机会能够主导开发项目的节奏,我借机会实践了一些软工的方法论,有些效果不错,有些踩了坑。这种自由的锻炼环境,我想,对一个技术人的成长有利有弊,但总之,我很感恩这种锻炼的机会,毕竟我感受到了自己的成长,况且不是所有程序员都有这样充分试错的机会。

第一点收获:影响软件开发过程的几个要素

我们一般说一个软件的开发,有几个关键要素:时间范围、功能范围、成本范围,这三者共同决定最终的因素,也就是软件质量。时间范围就是交付日期,大家都知道软件项目的延期是普遍的;功能范围就是要做哪些功能;成本范围就是能用多少个工程师、能花多少钱。如果在某个因素上砍了价,比如增加了功能,要么延长时间,要么增加成本,如果时间和成本都动不了,那必然要牺牲的就是软件质量(一堆BUG的程序)。当然,一般这个过程还伴随着时间范围的增加,也就是加班的意思。但是我又觉得,在绝大多数情况下,加班时写出的BUG比白天要多。

对于绝大多数正常工程师来说,大家都认同软件质量是不可妥协的,或者至少,我们对自己人可以妥协,但是对客户不能说我们打算要牺牲软件质量。我在自己的实际工作中,最常遇到的一种范式就是:成本非常有限(人力成本和开发硬件成本),同时,功能码的太多。并且,我们又尽可能地想要确保软件质量的稳定(至少得达到能够交付的标准)。那么结论就很简单了:质量、成本、功能都没啥调整的余地了,能牺牲的只有时间范围这一个因素,于是时间的力量作用在人身上的结果就是加班;作用在项目上的结果就是延期交付。

没有任何吐槽的意思,我想这种加班/延期软工范式是极其普遍的,即便是对于《人月神话》的作者来说,职业生涯也是从这样的项目起步的。在工作中,几乎所有开发项目的负面事件,往这个模型上套,一般都能找到答案,很有意思。那么我也不能只发现问题不解决问题,如果有人问我,这种困境该如何解决?或是有没有什么问题的首要矛盾?我会说最有操作空间的一点就是功能范围。“功能范围”不仅仅是功能的多少问题,还有架构的复杂度问题,下面来说。

第二点收获:做最小交付,避免过度设计

这两年间我大概经历过4个项目的开发,我认为这些项目全部犯了同样的错误:过度设计。

在任何讲软工的课程和书里,都能看到同样的观点:不要过度设计,要做最小交付。但是为什么我经历的项目,又都在项目初期的设计阶段,就进行了过度设计呢?我认为主要有以下几点:1. 低估了架构的技术复杂度 2. 在架构设计中使用了不必要的组件 3. 技术人的自娱自乐。这三点的本质应该是一回事,所以我混着点说。

先说第一点,关于低估架构的技术复杂度。当我们设计一个软件时,最好的架构就是没有架构,因为没有架构说明这个软件足够简单,比如大三的课程之软件工程课设,我相信对于这个软件来说真的不需要什么架构,反正也没人在乎。但对于公司里的软件项目来说,很难说不需要用到架构设计,毕竟(在大多数情况下)公司里的项目是比较大型且面向实战的,对于软件代码的性能、可维护性、可扩展性等方面需要依靠架构的支持。

我的观点非常坚定,和奥卡姆剃刀原则一样:如无必要,勿增实体。对于软件架构的设计来说,在满足业务需求的前提下,能少一个组件就少一个组件,能用简单的东西就不要用复杂的东西,能复用现成的框架就不要自己造轮子。

对于一个软件系统来说,肯定是架构越庞大,系统复杂程度就越高,而且这种复杂程度的增长,不是随着架构组件数量的增长而线性增长的,而是会有一个非线性的增长,有时候这个复杂度的增长速度,会超过开发团队的能力,这种时候,要么及时遏止复杂度快速增长的势头(有没有一种房价的感觉?),要么团队就要面临陷入“复杂度泥沼”的风险,在沼泽中以很低的效率前进,甚至迷失前进方向。

再说第二点,在架构设计中使用了不必要的组件。或者,这一点也可以换个说法:高估了软件架构对业务的影响。我们说软件架构是为业务服务的,那么我们在设计架构的时候,是否应当考虑这个架构对业务有什么帮助?有什么因素是从业务的角度出发,必须要这个架构设计不可的?比如说,我们要给一个系统上消息队列,因为我们认为数据量“可能”很大,“可能”需要消息队列,要不然系统“可能”会撑不住。是的,从技术的角度来说,我们都认同对于一个高性能、高并发、高可用的系统来说,消息队列可能是必不可少的要素,也许还有缓存、数据库等要素,这都是当然的。但是,对于现有的交付目标来说,我们真的需要上消息队列吗?我们是否有自信,能够在满足未来的潜在扩展需求的同时,还能控制好现有的时间范围、功能范围、成本范围,交付出质量足够OK的软件?或者说,如果不上消息队列,业务上是否会有什么ERROR级别及以上的问题?

还是拿消息队列开刀,因为我和我们都在这东西上耗费了太多和业务无关的劳动时间。除了考虑技术选型是否是业务所需要的时候,还有一个重要的因素就是团队的能力,是否能handle的住这种技术或是架构设计。我们一般说采用微服务的话,在总体架构规模比较小的时候,微服务反而比单一架构的复杂度更高,维护难度和调试难度也更高,因为对于微服务来说,组件之间的联系比单一架构多得多。所以说,当我们做技术选型和架构设计的时候,应当考虑到团队中开发人员的技术能力,以及我们有多少个开发人员可以用,也就是软件项目的“成本范围”。

总之:在设计软件架构时(包括软件架构演进过程中的决策时),业务的需求应放在首位,除非是地主家过日子,时间和资源都多的没处用;同时,考虑团队开发人员的能力,衡量团队能够处理怎样一个复杂程度的架构。如果一旦团队的能力(这个能力不仅仅是指技术能力,也是指团队的项目推进能力,比如人数多少),不足以应付项目的架构复杂度的时候,这个项目就不可避免的越来越走向衰败,或者说,要么强行推进项目,导致熵越来越大,越来越混乱;要么是维持现状,让熵不增不减——但是哪有愿意维持现状的团队和技术人呢。

最后是第三点:技术人的自娱自乐。没有任何贬义的意思,因为技术人天性如此,我也同样。我在做设计的时候,也引入过实则没有必要的组件;在编码的时候,我也追求过“代码的优雅”。大概在一年前的时候,我面临一个具体函数的开发,我需要根据传入参数的内容长度,生成一份json格式的配置文件。当时,我想要追求一种非常优雅的编码方案:根据参数内容长度,“动态地”生成配置文件,以面对未来可能的扩展需求。但当时我接触Golang不久,在编写代码的时候有点不顺利。恰好,我和leader聊到了我正在做的这个工作,他给我提出的建议是根据不同的内容长度,直接硬编码写几个配置文件,然后用非常土的字符串替换的方式,生成所需要的配置文件。我照此方法不太困难的就实现了函数,虽然当时我觉得有点别扭,因为这样做的话是严重牺牲了“扩展性”,不过事实证明那段代码在接近一年的时间内都工作的很好,因为没有任何“扩展需求”的到来。

说个题外话,就在前两天,我刚刚重构了这段代码,让函数能够真正实现“动态地”生成配置文件。如今的我来写当初同样的需求,现在感觉轻松多了,也许当初的那种设计,的确就是超过了我当时的实际代码能力。顺带一提,这段代码是标准的“纯函数”,我当时就写了比较完备的单元测试,这些单元测试在我重构代码时起到了非常关键的作用,当我的新代码通过了全部的老测试用例时,我就能比较有信心的说,新的代码应该不会有什么BUG。如果这时候出现了代码逻辑BUG,那只能是说我之前的单元测试还是写的不够好。

言归正传,我在当时尝试去写看上去更加优雅的代码,可能这就是我在自娱自乐,因为这样做是有风险的。如果我写的代码确实超出了我的能力范围,或者是触及了我能力的边界,那我写出的代码或是设计出的架构就可能存在隐患,尤其是在code review不太成熟的环境下,这种隐患很容易埋下,因为更多的情况是大家只看你的代码是否能够正常工作,但是很多代码BUG都是在“非正常”的情况下才会发生。

我也太高估所谓的“可扩展性”了,以我对程序员这个职业的了解,这种高估也实在是非常普遍。我们很容易高估未来可能的新增需求、数据量规模的增加,但是绝大多数对未来的估计都难以落地。

并且,我忘了一个原则就是技术应当为业务服务。咱们还是考虑项目开发的3大要素,如果说在时间比较宽裕的情况下,我尝试用一个比较新颖的技术或是更耗时的实现方式去做一件事,这倒也无所谓,但是对于软件项目来说,更常见的情况是没有那么充裕的时间资源,这时候如果我们铭记,技术的选型也好、实现方式也好,都是为软件的交付、乃至业务的成功而服务,我们就不会去选择那些能达成同样效果,但会冒更大风险和花更多时间的方案。和项目延期、软件交付质量下降的风险对比,“面向未来的可扩展性”的收益真的是微不足道了。

我还有一个不太成熟的想法:在商业公司里,持续的小成功比长周期的大成功,更容易维持一个技术团队的活力、信心和资源方面的持续发展。所以对于技术团队来说,选择敏捷开发的方法,对团队的发展有天然的优势。至于说,如果前期的架构设计的太土了,导致可扩展性的严重不足——那么我们能否依靠前期团队的成功,直接让“成本资源”这一项顺利进行扩张?比如让开发团队人数翻一倍,直接重写底层架构?这会不会比软件架构的可扩展性更有说服力?

再说到自娱自乐这个话题,在这一点上我收获的教训可以总结如下:当我在做任何和技术相关的事情时,不管是架构设计,还是编写代码,还是做CI优化之类的事情,我都想要先问自己:你要做的事情,以及你的方案,是否真的是业务和大局所需要的?还是说,这只是“我”所需要的?

我想,任何一位技术人都难以完全避免这用技术来自我娱乐的倾向,如果完全丧失这一点,那也就变得不再geek了,也就没有技术人的创造力了。但是,这一切额外的bonus都是建立在项目进展顺利、团队和公司发展顺利的前提下的。所以这里的精妙之处也许就在于一种平衡,一种在技术追求和业务产出之间的trade-off,我想这可能就是技术leader的责任所在。