代码大全读后感——控制软件复杂性是软件构造过程的第一要务

为什么软件会趋于复杂

  1. 处理的任务本质复杂。软件的作用在于和现实交互,完成现实世界的任务。但是现实世界本身有其复杂性,实体、流程、规则、输入、输出、运行平台等都有其复杂性。软件中必须有对应现实中各部分的组成部分,和现实中各个实体对应后,软件会趋于复杂。同时,现实中的实体定义边界常常较为模糊,而软件是需要精确定义的,从模糊到精确,会有一定的信息衰减。
  2. 人脑理解复杂问题的局限性。人脑思考时无法同时容纳超过两位数的实体,导致人脑无法同时兼顾全局和细节。软件本质的复杂却要求人脑能同时顾及各种实体和关系,和人脑局限性相矛盾。
  3. 非直接可见导致的复杂。软件中大多数组成部分是不可见的,最终展现形式是代码和可执行的程序。其虚拟性,导致了解其整体需要分析阅读代码才能了解各部分。一个软件代码量越多,完全掌握软件所需的成本就越大。当软件代码量达到一定数量级后,团队中甚至没有任何人能完全掌握所有代码。
  4. 团队开发导致的复杂性。如果软件全部为一个人开发,开发人员对需求、设计、编码整个过程都了如指掌,则软件复杂性将不是问题。但是,典型软件是团队协作的产物,不同人介入的阶段不一样,团队协作的整体一致理解的达成较为困难。
  5. 未来视角的复杂性。软件生命周期长,软件构造不仅仅为当前需求的满足,同时是为未来构建。未来需要修复软件的潜在 bug、需求会变更、团队成员会变动。从未来的成员看待软件是复杂的。

面对软件复杂性的解决方法

  1. 面对需求,尽量减小软件处理问题的范围。尽量小的问题范围,会从源头上减小软件复杂性。解决方案:建立实事求是的企业文化,不要在开始定义高大全的目标,而是有精益创业、敏捷开发的思想,通过迭代开发的流程,减小每次问题解决的范围,逐步扩大范围。
  2. 分析问题时,使用合理的抽象。在对应现实需求时,合理地分析问题,理解问题本质的前提下,识别并合并相同和相似的子问题,尽量减少软件中的独立的实体、流程、规则、输入、输出、运行平台等概念。如无必要,勿增实体。同时尽量减少信息的衰减。
  3. 务实地接受人脑的局限性现实。由于人脑同时兼顾的实体数量有限,难以兼顾全局和细节,所以需要全局上,将软件设计分层,每一层中分模块,模块中分类,类中分方法,使得从各个层次看待的软件复杂性得到控制。从原则上说,总层数、每一层的模块数、每个模块的类数、每个类的方法数控制在两位数以内。同时,比起随机组合在一起的东西,人脑容易理解有具体定义的实体。所以同一层次上的各个组成部分需要由明确的定义,而不是为了控制各层次的复杂度而进行的拼凑。
  4. 抽象的可视化,将概念通过图的方式展现出来。特别是通过层次化和模块化,各层次和层次中各模块有了明确的定义,这时可视化方法可以表达得更清晰到位,让人容易对软件有直观的印象,可以通过视觉快速地了解软件的组成,而不需要从代码层次理解软件。
  5. 制度化的团队沟通。团队协作的最终目标是让团队中所有人,对目标实体,产生正确的共同认知。沟通是解决人与人相互认知问题的唯一方法。沟通行为包括提供信息和接收信息两类参与者,按照提供信息的方式分为静态和动态两种形式。静态的沟通中,提供信息者只要花一次沟通的时间,表现为可以稳定成文的规范、行为准则、记录。如开发流程、编码规范、分析方法指导、需求文档、设计文档等。动态的沟通需要提供信息者每次沟通都花时间,表现为需求确认会、设计评审会、代码评审会、结对编程、在线代码审核等。团队协作中,需要尽量增加静态沟通的沉淀,同时将必要的动态沟通制度化。所有的沟通都是为了相同的正确认知,而这个过程越早发生能越早解决问题,避免花大量时间解决已经发生的问题。
  6. 面向未来的开发。最容易解决问题的时机为问题尚未发生时。但同时,未来是难以预测的。不作限制地预测未来,做过早的未来预判反而会增加软件复杂度。合理地判断未来应当把握问题的本质,从问题本质演绎出的预判不会偏离实际情况过远。从需求分析的角度来说,要抓住需求通过哪几个基本方法解决谁的什么问题的本质,明确解决的问题、服务谁、采用的基本思路。从软件设计的角度,需要明确抽象对应实际问题域中的实体。从开发流程的角度,要将问题尽早解决,避免到后来解决问题。从团队协作的角度,要让相应的人尽早参与,在同一个问题上要有不止一个人知道发生了什么、应该如何解决。

对复杂性的更多讨论

复杂性的哲学本质为“万物皆有规则,万物皆有关系”,表现两方面,首先每个实体概念都有其特殊性和内在规则,同时每两个实体概念之间都存在或强或弱的关联。

可以将实体概念定义为一个节点,实体概念间的关系定义为节点之间的连线。节点加节点间的连线形成了网络。网络的复杂度可以定义为节点数+连接数。

从“万物皆有关系”的角度,每两个节点间都有或强或弱的关系,那么这个网状的图为全连通图,是最为复杂的图的形式。

如何降低全连通图的连通性为降低复杂度的全部任务。从复杂度的定义中,可以发现,降低复杂度就是要

  1. 减少实体概念的节点数。解决方案:实体概念有大有小,可以通过合并相似实体概念,获得更大的实体概念来减小节点数。
  2. 减少实体间的关联数量。解决方案:实体间关系有强有弱,可以通过让强关系节点代理弱关系。如:A 节点和 C 节点存在弱关系,A 节点和 B 节点存在强关系,B 和 C 节点存在强关系,B 代理 A 和 C 之间的弱关系。

所以复杂度控制可以抽象为对网络进行多层级聚类。在最高层,存在少数几个节点,之间的全连通已经尽量去除。在最高层的每个节点中,又存在代表更低一层的节点的少数几个类。

代码是软件构建的最终产物,从代码的角度,最小的实体为单行代码。软件设计的过程,可以看作对代码进行聚类的过程,聚类后形成了方法、类、包、模块、层等。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

代码大全读后感
Previous post
How To Secure Nginx with Let’s Encrypt on Ubuntu 18.04
Next post