软件设计之道

前言

计算机科学的根本问题是如何分解问题,然而这门技艺却很有少学校会教。本书内容是作者多年实践经验的结晶,也是斯坦福的大学课程

本书根本目的是要解决软件设计的复杂性,提供了一系列软件设计原则,观点,读者可结合个人实际情况实践。

引言

复杂性是现代软件系统开发不可回避的问题。原因在于随着系统规模越大,复杂性也会快速增加,从而降低开发效率。

作者从两个维度简化复杂度:
1 代码本身
诸如一致的命名风格,清晰准确的命名等
2 封装复杂性
即模块化设计

最后作者提倡了增量式,演进式设计。以及在演进式设计下如何减少复杂性

本书目标:
1 是定义复杂性的本质,教你识别软件复杂性
2 是教授一些方法减少复杂性

如何使用本书:
结合代码检视,分析代码是否符合本书讨论的问题
发现代码设计问题往往比重构更难,当检视代码时,对每个设计问题标红并提出更加简洁的设计
书中每条规则有例外,要适度应用

复杂性本质

识别复杂性并用不同的设计实现去简化设计实现,从而达到更加简单化的设计实现

  1. 识别复杂是关键的步骤
  2. 利用常见的简单化的设计去解决问题

因此,首先就需要定义复杂性。

复杂性定义

简言之易于理解和修改的系统是简单的。反之复杂
另外作者还从成本与收效角度考虑了复杂性。就是说如果一个逻辑复杂的系统不经常变化修改,也就不需要付出成本去修复了,这样整系复杂性就低。

理论公式:如果一个系统由n部分组成,开发人员花在每部分的时间比例是t。则整系统复杂度为每个部分的复杂度乘以其比例t之和。

这意味着如果一个特别复杂的模块根本不会被修改,就说明基本消除了该部分复杂性。

点评:这么来看作者的复杂性定义还是很实在的。从实际开发维护成本出发。去考虑复杂性。后续也将从复杂性定义出发去提出一些减少复杂性的方法原则。从公式可以看出,减少复杂性的方向一定是让复杂的模块稳定。让不复杂的模块依赖于复杂模块。如此可极大降低复杂性。

复杂性症状

  1. 变化放大
    一个知识在系统中到处重复,当知识变更就需要更改所有的地方。这将就导致了修改成本高

  2. 认知负荷
    对系统实施修改必须要了解的知识,以及了解这些知识所要付出的成本称作认知负荷。显然,如果认知负荷高,系统修改成本必然高

  3. 未知因素
    不清楚修改哪些代码才能让系统正常工作。
    这是最难的情况。你都不清楚需要修改哪些代码才能使系统工作,那么你就必须真实运行系统或才通读每行代码并理解才能准确的实施修改,这样的成本是最高的
    PS:我司大部分系统就是这样,是架构,系统设计太拙了导致的

因此设计一个清晰整洁的架构至关重要,让读者一看代码结构就知道系统如何运作的,以及每个模块的职责。这个观点跟整洁架构不谋而合。

复杂性原因

依赖和模糊
依赖:软件系统本身是依赖的,减少依赖和使依赖关系简洁明了就能够降低复杂性。依赖会导致变化放大,认知负荷
模糊:模糊导致混淆。比如随意的变量名,没有文档声明的接口等它增加了认知负荷与未知因素

因此作者总结出来,将软件系统复杂性最终归因于依赖性和模糊性。解决这两点就可以降低复杂性。

点评:这一步跳变还是很大的。软件系统复杂性可能还有其它因素,比如人,协作,管理因素等等。但若仅从软件本身出发,作者提到的两点确实是主要因素。

复杂性的积累

强调了复杂性是由千万个依赖性和模糊性问题积累导致的。单一的依赖和模糊性都好解决。积累多了就会导致巨额的修复成本甚至于无法修改。因此作者强调对待单个依赖性和模糊性问题要随时修改。

PS: 这和业界提倡的随时重构观点不谋而合。强调随时清理系统债务。如果系统有10个小的问题,将他们逐个修改。成本就是10。但若累积到最后修改,成本可能就是100了。因此累积到最后修改时,这些小问题会相互影响增加认知,变化放大,未知成本。

战略与战术编程

本章没有太多新东西。
战术编程就是不管设计及代码质量,以最快速度交付
战略编程就是随时重构,产出高质量的设计和代码
战术编程短期可能快但是不可持续
战略编程前期可能慢点但会产出更好的架构,可持续
作者建议前期投入10%-20%在设计上。

模块化设计

模块化设计

模块由接口与实现构成。接口描述了模块做什么而不是怎么做。它隐藏了实现细节

接口中信息包括显示的与隐式的
显示信息包括参数类型返回值等
隐式信息包括调用约束,调用副作用等等必须由调用者知道但未在接口声明中表明的。通常通过注释说明

抽象

抽象就是对实体的一种简化视图,它忽略了不重要的细节,但具备独立于其它抽象体的独特特征,抽象思维非常有用,它让人们可以思考和解决复杂事务。

模块化编程中,每个模块都以接口形式提供了一个抽象。

抽象关键是隐藏不重要的细节,暴露重要的细节

深模块
浅模块

信息隐藏与暴露

这里的信息隐藏指的是某个具体实现完全隐藏在模块内部。外部不感知。这各类的私有变量和方法不太一样。

信息暴露:
当多个地方使用相同的知识时,就发生了信息暴露。
这里特别注意隐式暴露,例如,如果两个类均依赖于同一数据格式,那么数据格式就相当于隐式暴露了。隐式暴露最常见于结构化编程中,将一个顺序执行的不同步骤拆到不同的类中,而这不同类依赖了相同的结构。这便造成了隐式暴露。像管道-过滤器模式就是隐式暴露。

时间分解

典型例子如读文件,修改,写文件。这个流程在不同时间先后执行。若划到三个类,则三个类均依赖了相同结构体。造成结构依赖。
这种依赖执行顺序去划分类或不同模块的方式常常导致信息暴露

备注:这种信息暴露是有一定危害的。但是像管道过滤器模式以及责任链等模式不同类依赖相同结构体。只要暴露的结构体(知识)不易变。就可以减小它带来的危害
显示暴露常见于接口中。

信息暴露实例:

Http服务器通常包括接收请求,解析请求然后发到不同的类去处理。常见错误是将数据接收与处理分开为两个不同的浅类。由于它们依赖相同的结构,导致了信息暴露

重点:解决信息暴露通常可以难过合并类并提供合并的简化接口去处理。
类应该尽量隐藏用户不需要感知的技术实现细节,如果某些细节在特定场景下需要感知。则提供修改接口去修改。在模块的外部使用接口中。可以提供默认值。这样大部分用户便无须感知这些知识。简化了接口使用。

最后总结

不要陷入根据时间执行顺序去分解模块的陷阱。应该识别出完成系统需求需要的知识点。用不同的类去封装不同的知识点。这样可以得到基于深模块且接口简洁少的干净,简单的设计。

你可能感兴趣的:(笔记)