《软件设计的哲学》读书笔记

1 篇文章 0 订阅
1 篇文章 0 订阅

一、复杂性的本质

在作者看来,软件设计的本质是降低“复杂性”。对于复杂性,作者是这样定义的。复杂性与软件系统的结构有关,这使它很难理解和修改系统(复杂性是指那些让系统难以理解或修改的与系统相关的任何事物)。复杂性的表现主要体现在三个方面。一是变更放大,看似简单的变更需要在许多不同地方进行代码修改。二是认知负荷,为了完成一项任务,开发人员需要去了解比较多的知识才能完成。较高的认知负担意味着开发人员必须花更多的时间来学习所需的信息,并且由于错过了重要的东西而导致错误的风险也更大。三是未知的未知,必须修改哪些代码才能完成任务,或是必须获得那些信息才能成功地执行任务,这些都是不明确的。

造成上述复杂性的原因主要有两个,其一主要是依赖性和模糊性。在软件开发中,当无法孤立地理解和修改给定的一段代码时,便存在依赖关系。而依赖性是软件结构的基本组成部分,不可能完全将其消除。这正是其表现的变更放大和认知负荷的特性。其二是晦涩性。当重要的信息不明显时,就会变得模糊。比如:一个变量的文档可能没有指定它的单位,所以找到它的惟一方法是扫描代码,查找使用该变量的位置。晦涩常常与依赖项相关联,在这种情况下,依赖项的存在并不明显。这正是其表现的认知负荷和未知的未知。

复杂性不是由单个灾难性的错误引起的,而是由许多小块堆积而成。单个依赖项或模糊性本身不太可能明显影响软件系统的可维护性。之所以会出现复杂性,是因为随着时间的流逝,成千上万的小依赖性和模糊性逐渐形成。最终,这些小问题太多了,以至于对系统的每次可能更改都会受到其中几个问题的影响。

同时,在日常工作中,仅能提供工作的代码是远远不够的。我们需要用战略的思维做系统设计,从短期开可能耗费了整个系统开发时间的10%-20%,但从长远来看,该时间的投入是值得的,避免随着系统规模的扩大,系统的复杂性越来越高。如果仅满足能工作的代码,那么反过来随着时间的推移,后续将花费更多的时间来对整个系统进行改造,升级。

二、降低复杂性

在了解了复杂性的本质后,我们看看怎么才能降低复杂性。在书中,作者从模块划分,接口及接口实现的定义,异常处理,注释,命名等方面对降低复杂性进行了阐述。

1.模块划分

做软件设计时,我们应该按模块进行划分,模块之间的依赖尽可能少,模块内部尽可能的抽象。模块可以分为深度模块和浅模块,深度模块内部高度抽象,比如:Unix系统的文件I/O,JVM的垃圾回收器等。浅模块的抽象程度不高,过多的浅模块将增加系统的复杂性,如:Java里的文件流,要打开文件以便从文件中读取序列化的对象,必须创建三个不同的对象:

FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);

三个对象中,一旦文件被打开,fileStream和bufferedStream将不再被使用,后续都是在操作objectStream。在最后使用完成后,还必须记得关闭流,对使用者来说复杂性就比较高,如果忘记了还会导致系统出错。

2.接口及接口实现

模块划分好了后,需要通过接口、接口实现来体现类的功能。接口是模块对外提供功能的最直接表现,在定义接口时,应尽可能减少外部的输入,将重要信息的数量尽量减到最少;同时,在接口设计时,应专注于执行每个任务所需的知识,而不是任务执行时的顺序。接口以最少的信息暴露,对接口使用者而言对其无关的信息进行隐藏。在模块设计时,不同的层要有不同的抽象,否则,很有可能它们不能提供足够的利益来补偿其代表的其他基础结构。

对于通用接口,其越深入越好。这主要表现在:

  • 通用接口比专用接口具有许多优点。它们往往更简单,使用的方法更少;
  • 它们还提供了类之间的更清晰的分隔,而专用接口则倾向于在类之间泄露信息;
  • 模块具有某种通用性是降低整体系统复杂性的最佳方法之一;

在降低复杂性上,提倡和鼓励被降低的复杂度与该类的现有功能密切相关,降低复杂度将导致应用程序中其他地方的许多简化。但需要防止走向另一个极端,就是将整个应用程序的所有功能归为一个类。所以,在日常开发中,为了减少用户的痛苦,我们要找机会给自己多吃一点苦。

功能、代码是在一起还是分开更好的识别标准中,如果信息共享则汇集在一起,如果可以简化接口则汇集在一起,如果能消除重复则汇集在一起。通常,系统的下层倾向于更通用,而上层则更专用。拆分或合并模块的决定应基于复杂性。选择一种结构,它可以最好的隐藏信息,产生最少的依赖关系和最深的接口。

3.异常处理

异常处理是软件系统中最糟糕的复杂性来源之一。许多编程语言都包含一种正式的异常机制,该机制允许异常由低级代码引发并由捕获代码(try catch)捕获。一段特定的代码可能会以几种不同的方式遇到异常:

  • 调用方可能会提供错误的参数或配置信息。
  • 调用的方法可能无法完成请求的操作。例如,I/O 操作可能失败,或者所需的资源可能不可用。
  • 在分布式系统中,网络数据包可能会丢失或延迟,服务器可能无法及时响应,或者节点间可能会以意想不到的方式进行通信。
  • 该代码可能会检测到错误,内部不一致或未准备处理的情况。

既然异常在程序开发中不可避免,那我们在日常开发中能有什么办法减少异常的发生呢。通常,常见的方法有:

  • 通过定义规避异常或错误

设计、定义API时,不要定义异常,使其本身无需有异常要处理;

  • 屏蔽异常

在系统的较低级别或模块上检测和处理异常,因此,较高级别上无需关心和处理

  • 异常聚合

当一段代码有许多异常要处理时,与其为多个单独的异常编写不同的处理程序,不如用一个处理程序在一个地方将它们全部处理。

  • 让程序奔溃

在大多数应用程序中,有些错误是不值得去处理的。通常,这些错误很难或不可能处理,而且很少发生。比如:内存不足。

4.注释

大多数研发人员都知道注释的重要性,但在日常工作中却不写注释。大致的理由有如下几点:

  • 好的代码是自解释的

反驳观点:方法是抽象的,不能靠通过阅读代码了解方法的功能。

  • 没时间写注释

反驳观点:需要有投资的心态。

  • 注释过时并误导

反驳观点:可通过代码审查,完成对注释的修改、更新。

  • 我锁看到的所有注释都是毫无价值的

说明注释写的不够友好,不够完整。好的注释应该是要描述代码中不明显的内容。主要从下列几个方面来说明怎么写好注释。注释的指导原则,注释应描述代码中不明显的内容。注释要解答的是什么以及为什么,而不是如何。对于跨模块的注释,可以考虑把注释放到跨模块的枚举类中。注释的目的是确保系统的结构和行为对读者来说是显而易见的。

对于新类、新方法,建议是先写注释,后编码。注释也是一种设计工具,注释写得越早越有利开发,同时耗费的时间也越短,大约是开发时间的5%到10%。如果先写代码后写注释,其实这并不是好的注释,因为这时候功能已经实现了,大多数情况下并不会比先写注释后写代码时的注释更详细,精确。

对于现有代码,保持战略性。维护注释,将注释保留在代码附近,同时,注释属于代码而不是提交日志,维护注释时,避免重复,检查差异。更高级别的注释更易于维护。

5.一致性

在一致性中,主要从命名规范上表述一致性的重要性。随意命名会导致一些意想不到的错误,选择名称时,能在脑海中创建一幅关于命名事务的图像是最好不过的,同时,命名的精度和一致性需要准确。精心选择的名称有助于使代码更明显。类、变量、接口、方法、编码风格等的一致性是降低系统复杂性并使其行为更为明显的强大工具。为确保一致性,在文献、注释中用词要准确,同时,对代码加入自动检查机制,确保各种规范、规约的落地执行。一致性是投资心态的另一个例子。

三、最终的收获

1.高质量交付

通过上述的各种方法降低了系统复杂性后,在代码层面,我们还需要一些规范来确保我们最终能高质量的完成系统的交付。在代码层面,为确保我们的代码是显而易见的,可以参考下面的方法:

  • 空白的使用
  • 代码格式化的方式会影响其理解的容易程度
  • 空白行使注释更可见
  • 注释
  • 当无法避免显而易见的代码时,可配合注释进行解释

相反,使代码不明显的事情有:事件驱动的编程(很难遵循控制流程)和通用容器。而软件设计的原则之一是,软件的设计应易于阅读而不是易于编写。在代码开发中,确保代码清晰可见的方法有如下几种:

  • 最好的方法是使用抽象等设计技术并消除特殊情况,以减少所需的信息量
  • 其次,您可以利用读者在其他情况下已经获得的信息(例如,通过遵循约定并符合期望),从而使读者不必为代码学习新的信息
  • 第三,您可以使用诸如好名和战略注释之类的技术在代码中向他们提供重要信息

综上,我们的系统复杂性将大为降低,提升系统后续的可用性和可维护性。

2.系统性能的提升

在系统设计时,如何在不牺牲系统简洁性的前提下来考虑性能。为了提升系统的性能,我们需要了解常见的性能牺牲比较大的一些操作,这些操作有:网络通信、IO到辅助存储、动态内存分配和高速缓存未命中等场景。了解了影响性能的操作,接下来就要有针对性的进行改进。在改进前,需要完成怎么度量,即确保改进后的方案比改进前的有提升。为此需要对改进前的设计进行评估,度量。确定性能调整影响最大的地方,围绕关键路径进行设计,确保调整后的性能好于调整前的。

简洁、干净的设计和高性能是兼容的,找到对性能最重要的关键路径并使其尽可能的简单是提升系统性能的关键。

附:全书思维导图

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值