软件设计哲学:理解并降低复杂性

最近在读 A Philosophy of Software Design, 即软件设计的哲学,记录下个人的理解吧。

作者 John Ousterhout 认为软件设计的最大目标,就是降低复杂性。并通过自己的实践经验,沉淀了软件设计的原则以及技巧,从而利用降低复杂性来指导软件设计的整个生命周期。

全书的总体目标主要有两个:

  • 理解/识别复杂性:"复杂性"是什么意思,为什么重要,以及当程序具有不必要的复杂性时如何识别?
  • 减少复杂性:介绍可在软件开发过程中使用的技术,以最大程度地减少复杂性。

一、复杂性是什么

vbnet 复制代码
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.

文中指出,复杂性是指那些让系统难以理解或修改的与系统相关的任何事物

1.1 复杂性造成的影响

作者认为,复杂性一般通过三种方式表现出来,而这里的每一个都使得软件开发更为困难。

  • 变更放大:指的是当需要对系统进行修改时,即使是小的变更也需要在多个地方进行修改,从而放大了变化的影响。这种情况虽然麻烦,但只要明确知道需要修改哪些代码,系统在完成变更后仍然可以正常工作。
  • 认知负载:涉及到理解和修改系统所需的心智成本。高认知负荷会增加进行更改的成本,但如果清楚知道需要阅读哪些信息,更改仍然有可能是正确的。这表明,即使系统复杂,有足够的信息和指引也可以有效地进行修改。
  • 未知的未知:指的是在系统中存在的不确定性,使得开发者不清楚如何进行修改,甚至不确定提出的解决方案是否可行。在这种情况下,唯一确保解决方案正确性的方法可能是阅读系统中的每一行代码,这极大地增加了修改的难度和不确定性。

其中变更放大和认知负载大多情况下,我们是可以获取具体的信息,并作出修改决策的。但未知的未知是最糟糕的。一个未知的未知意味着你需要知道一些事情,但是你没有办法找到它是什么,甚至是否只有一个问题。

1.2 复杂性是递增的

vbnet 复制代码
Complexity isn't caused by a single catastrophic error;

复杂性不是由单个灾难性错误引起的。它是由小块堆积而成的。复杂性的增量性质使其难以控制。可以很容易地说服自己,当前更改所带来的一点点复杂性没什么大不了的。但是,如果每个开发人员对每种更改都采用这种方法,那么复杂性就会迅速累积。一旦积累了复杂性,就很难消除它,因为修复单个依赖项或模糊性本身不会产生很大的变化。

二、复杂性的原因

csharp 复制代码
Complexity is caused by two things: **dependencies** and **obscurity**.

作者认为,复杂性的原因主要有两个:依赖性和模糊性

  • 依赖性:当无法孤立地理解和修改给定的一段代码时,便存在依赖关系。不过,依赖关系是软件的基本组成部分,不能完全消除。软件设计的目标之一是减少依赖关系的数量,并使依赖关系保持尽可能简单和明显。
  • 模糊性:当我们无法获取模块的重要信息时,就会存在模糊。比如:变量命名的不准确,复杂函数缺乏注释,注释不精准,复杂的设计不提供文档。

同时,这两个特性可能同时作用,比如,正是因为依赖性不清晰,从而导致模糊性的进一步加深。

复杂性来自于依赖性和模糊性的积累 。随着复杂性的增加,它会导致变化放大,高认知负荷和未知的未知数。最重要的是,复杂性使得修改现有代码库变得困难且冒险

三、如何降低复杂性

全文作者也列举了很多设计原则,如战术&战略编程,模块深浅封装、文档注释、变量命名、一致性、抽象等。

由于篇幅问题,这里仅介绍下方几点对我个人印象比较深刻的几点

3.1 战术&战略编程

  • 战术编程:关注于解决眼前的具体问题。
  • 战略编程:考虑整个系统的可维护性和可扩展性。

作者强调,优秀的软件设计需要平衡这两者,即在日常编程中不仅要注重立即的问题解决(战术),也要从整体上规划和设计(战略),以确保软件随时间的发展仍能高效运作。

3.2 模块深浅封装

模块的封装不应仅仅是表面上的,而需要深入考虑如何隐藏内部实现的细节,同时提供清晰、简洁的接口。

深封装强调在模块的设计中追求"深度",即通过少量强大的概念和功能实现丰富的功能,而浅封装可能只是简单地隐藏了实现细节而未真正减少复杂性。

3.3 一致性

一致性是降低软件复杂性的关键原则之一。它要求软件的设计、实现及文档在风格和结构上保持一致,包括编码风格、命名约定和设计模式等。

一致性可以减少学习和理解代码的难度,使得维护和扩展变得更加容易。

3.4 不同的层,不同的抽象

软件通常被设计为多层结构,每一层都应该基于适当的抽象级别工作。底层关注于具体实现和性能优化,而高层则应更加关注业务逻辑和用户界面。通过在不同的层次采用不同级别的抽象,可以清晰地分离关注点,降低整体复杂性。

当然,里面的一些设计思路和技巧还有很多,如注释早于编码、设计两次、变量命名等都是不错的设计思想。

在这里,个人理解降低复杂度可以总结为三点

  • 封装,减少依赖信息:封装隐藏细节,减少模块间依赖,简化系统复杂性。
  • 提供准确有用的信息:明确文档和注释帮助快速理解系统,减少误解。
  • 迭代和重构:通过迭代改进和定期重构维护代码清晰度和系统灵活性。

四、总结

这本书是关于一件事的:复杂性。处理复杂性是软件设计中最重要的挑战

在本书的过程中,作者试图描述导致复杂性的根本原因,依赖性和模糊性,并给了我们降低复杂性的一些设计思想。

但知易行难,这个过程还需要我们在日常中具体实践,我们才能更好地理解和运用,并且可能我们也会更新自己的设计哲学。

五、参考链接

相关推荐
晨米酱1 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机6 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机7 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机7 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机7 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤7 小时前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机1 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴1 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤1 天前
工厂模式
设计模式