本文原创:yangyanchun
在进行软件开发时,我们常常会追求软件的高可维护性,高可维护性意味着当有新需求来时,系统易扩展;当出现bug时,开发人员易定位。而当我们说一个系统的可维护性太差时,往往指的是该系统太过复杂,导致给系统增加新功能时容易出现bug,而出现bug之后又难以定位。
什么是复杂度
修改扩散
修改时有连锁反应,相互依赖,耦合度高,某部分代码不能被独立地修改和理解,必定会牵涉到其他代码。
认知负担
开发人员需要多长时间来理解功能模块,从代码中难以找到重要信息
不可知
开发人员在接到任务时,不知从哪里入手,数据流向混乱。
复杂性的危害在于,它会递增。你做错了一个决定,导致后面的代码都基于前面的错误实现,整个软件变得越来越复杂。"我们先把产品做出来,后面再改进",这根本做不到。
如何降低复杂性
重视设计和细节的改进
拒绝战术编程,采用战略编程
战术编程致力于完成任务,新增加特性或者修改Bug时,能解决问题就好。这种工作方式,会逐渐增加系统的复杂性。如果系统复杂到难以维护时,再去重构会花费大量的时间,很可能会影响新功能的迭代。
战略编程,是指重视设计并愿意投入时间,短时间内可能会降低工作效率,但是长期看,会增加系统的可维护性和迭代效率。
设计两次
为什么该方案可行?
架构图、接口设计、时间人力估算
在已有资源限制下,为什么该方案是最优的?
在关键点或争议处提供二到三种方案,并给出建议方案
水平分层,垂直分模块
层次和抽象
复杂性下沉,尽量让用户使用简单,接口要简单,实现可以复杂。
深模块:功能强大,接口简单,是抽象的最佳实践,通过排除模块内部不重要的信息,让用户更容易理解和使用。
浅模块:功能简单,接口复杂,无助于解决复杂性。因为他们提供的收益(功能)被学习和使用成本抵消了。
好的 class 应该是"小接口,大功能",糟糕的 class 是"大接口,小功能"。好的设计是,大量的功能隐藏在简单接口之下,对用户不可见,用户感觉不到这是一个复杂的 class。
异常处理
尽可能减少需要处理异常的可能性
用户必须面对所有的 error异常"反正我告诉你出错了,怎么解决是你的事。",正确的做法是,除了那些必须告诉用户的错误,其他错误尽量在软件内部处理掉,不要抛出。
通用和专用
满足当前功能,快速实现,接口设计通用化
满足当前需求最简单的接口是什么?在不减少功能的前提下,减少方法的数量,意味着接口的通用性提升了。
接口的使用场景有多少?如果接口只有一个特定的场景,可以将多个这样的接口合并成通用接口。
满足当前需求情况下,接口的易用性?如果接口很难使用,意味着我们可能过度设计了,需要拆分。
注释文档
比如“好代码是自注释的”、”没有时间“、“现有的注释都没有用,为什么还要浪费时间”等等。这些观点是站不住脚的。“好代码是自注释的”只在某些场景下是合理的,比如为变量和方法选择合适的名称,可以不用单独注释。但是更多的情况,代码很难体现开发人员的设计思路。好的注释可以减少文档工作。
使用注释提升系统可维护性, 重视what、why,而不是how(而不是代码是如何实现的)