何幻

Programming is about ideas,
languages are just a way to express them.


  • 首页

  • 归档

  • 分类

写好一个系统需要额外付出多少努力

发表于 2019-12-02 | 分类于 Design

当今软件生态已经越来越复杂了,一个简单的产品,

可能动辄涉及到十个,甚至数十个子系统,

把所有这些子系统当做一个整体来看的话,写好它是一件很难的事情。


本文就来探讨一下,一个勉强能跑的系统,跟一个生态优良的系统,

到底有什么区别,以及需要额外付出多少努力。

异常

一个勉强能跑的系统,不会覆盖所有的异常流程,

每个函数只有一个返回值,它们被看做永远不会抛异常。


然而,这肯定是一种不合理的假设,

当有一个函数可能会抛出异常时,事情就变得复杂了。

这个函数的调用方到底该怎样“消化”这个异常呢?


除此之外,甚至在业务层面,也会包含一些“非正常”的业务逻辑,

比如一个没有注册的用户访问授权页面,应该怎么处理?

或者是,一个接口被传入了跟预期不符的参数时,应该怎样反应?


把所有功能看做一个整体,那么正常流程应该只有一种,

出错的可能,会有很多种。


如果每一种出错,都经过了深思熟虑的处理,而不是默认结果,

那么,加上异常流程的系统,会比勉强能跑的系统,复杂数倍。

一个可靠的接口,会由一个正常结果,与 N 个异常组成。

日志

对一个没有日志的系统排查问题,是非常困难的,

很难还原出当时的运行现场。


一个常见的方法是在业务逻辑的字里行间写下日志,

这种侵入性的日志编写方法,会降低代码的可读性,

让逻辑变得晦涩难懂。


而非侵入的日志编写方法,可能无法覆盖到所有细节,

一般是通过异常切面来做。


不论用哪种方法,增加了日志功能的系统,会比勉强能跑的系统复杂了一些。

这是毋庸置疑的。

因为日志功能已经算是原系统的一种非功能需求了。

单测

专业程序员喜欢用日志和单测来检查问题,遇到问题跑一下单测,

能省掉很多构造数据的成本。


然而,有单测覆盖的系统,也一定会增加不少代码量,

对于那些功能频繁变动的系统,甚至每改一些代码,就得相应的改改单测。


单测虽然一定程度上增加了代码的可靠性,

但同时,也增加了代码的编写成本。


除此之外,软件的每一次持续集成都会跑一遍单测,

花在修补单测上面的时间,可能会出乎意料的多。


因此,编写成本和软件质量是不可兼得的。

没有一劳永逸的办法。


有单测的系统,在维护者看来,确实更加复杂,

单测可以看做是给代码加了“有测试覆盖”这条额外的属性。

环境

有些系统是要分本地环境和线上环境的,

本地开发时,跟线上运行时,系统的表现可能会不同。

简而言之,某些行为可能会跑到不同的分支里面。


最起码,他们也可能会从不同数据库中读取信息。

这样做也会增加系统的复杂性。


要发布一个系统,我们必须保证它在好几个环境都表现良好,

而不是仅仅线上表现良好就够了。


一旦有多个子系统,都区分了不同环境,问题就更麻烦了,

在调用某个接口的时候,我们必须严格区分调用的是哪个环境中的接口。

这比只有线上一套环境的系统,又复杂了好多。

调试

有些系统的设计之初,只是为了满足功能,

可一旦跟其他系统打交道时,就会涉及调试和排查问题。


一个不能调试的系统,在排错时是特别麻烦的,

我们只能一行行的读代码,在大脑中记录程序状态,人肉调试。


这种情况在遇到多态函数时,会变得超级烧脑。

很多运行时数据的丢失,导致我们不知道程序到底该执行哪段代码。


因此,一个设计优良的系统,一定会在必要时考虑它自身的调试问题,

怎样对开发者友好,如何通过程序断点,调试进去。


系统具备可调试性,也相当于给系统增加了额外要求。

文档

一个接口,并不是仅提供功能就完事了,

接口以及如何使用这个接口,本来应该是一体的。

没有使用文档的接口,相当于只完成了一部分功能。


除此之外,系统的架构文档,也一样重要,

它被划分为了几个子系统,每个子系统是如何交互的?

否则,子系统之间类似“元胞自动机”一样的诡异交互方式,就没办法理解了。


注释应该也算是一种说明性的文档,只不过它是写到代码中的,

有丰富注释的代码,确实会省去每个人的阅读成本,这些成本累积起来是很可观的,

但对代码编写者来说,也会花费不少精力。


有详细文档和注释的系统,会比勉强能跑的系统,更好维护,

从可维护角度来讲,这是对系统提出了更高的要求。

监控

现在有很多系统是 7*24 小时运行的,一旦出错每一秒都会有损失,

因此,有监控是对这种系统提出的另一种要求。

当出现错误的时候,我们希望能检测到,通过报警机制,自动通知相关的责任人。


增加了监控机制的系统,会比正常的系统略微复杂一些,

如果把监控系统也看成原系统一部分的话。


如果没有额外的通用监控系统,为每个系统单独配一个监控,是一件麻烦的事情。

还得保证这些监控系统自身不会对现有功能产生影响。


不可监控的系统,相当于扔出去的炸弹,不知道什么时候它会爆炸,

但是使得系统可监控,也在一定程度上增加了复杂度,

需要额外的努力才能做到。

回滚

可回滚是系统的一种较为苛刻的属性,要想能回滚,需要系统先具有这种属性才行。

一个系统是否可回滚,在设计阶段就得考虑清楚。

并且它的每一次发布,都得指定详细的发布和回滚计划。


一个能回滚的系统,得做到它每一次发布所做的修改可撤销,

包括对数据库的修改,以及接口的兼容性,还有依赖方其他系统的影响。


这件事看起来简单,实则很难做到,

比如,对数据库结构进行了变更,而老接口又不可兼容的时候,

很多系统其实是不可回滚的。


又比如一些工具类库,版本号只能往上加,不能撤销,

那么一旦发布了某个不兼容的版本,就只能硬着头皮往下走了。

结语

为系统提出的每一个要求,都是要付出成本的。

一个勉强能跑的系统,人们对它的要求更低。


如果要求它更可靠,覆盖所有的异常流程,有日志可跟踪,

有单测覆盖,支持多套环境,可调式,有详细的文档,

支持监控,可回滚,它就会变得越来越复杂。


除此之外,肯定还有对系统的其他要求,例如自动扩缩容,

自动降级,安全方面的防护,等等。


因此,写好一个系统,并不是仅仅让它能跑就完事了,

额外还要做出很多事情。


这些功能加起来,足以让一个简简单单的应用,变得超级复杂,

很多侵入式的写法,会让代码变得几乎不可维护。


最后,一个系统到底要不要做得这么复杂,其实是设计者该考虑的事情,

系统要不要具备这样的隐性功能,要不要投入这么多人力去实现,

都是值得商榷的,并不是额外的功能越多越好。


没有好的系统,只看是否达到预期罢了。

<1…848586…309>

309 日志
11 分类
知乎 简书
© 2025
由 Hexo 强力驱动
主题 - NexT.Pisces