读书笔记:《代码大全第2版》 02.创建高质量的代码之软件构建中的设计

软件构建中的设计

1、软件设计的特征与挑战

软件的设计是一个“险恶”的问题,所谓“险恶”就是你必须首先把这个问题"解决"一遍以便能够明确地定义它,然后才能再次真正地解决该问题,这是软件设计的一大挑战。

同时,软件设计无法判断设计到何种程度才是“足够好”了。而且,软件设计存在着诸多限制,比如资源等。你需要在这些限制中做出选择与平衡,所以没有好的设计或不好的设计,只有适合的设计。

合适的设计不是一蹴而就的,是不确定的、演进式的,不断探索、演练、推到、总结而形成的。

满足同一个需求,不同的人会设计出完全不同的设计,所以,设计又具有不确定性。

2、管理复杂度

人的大脑处理能力有限,无法同时关注太多东西,关注东西太多将会导致某些遗漏和混乱。比如,同时让你忙的太多东西,你肯定会丢三落四!虽然你忙的焦头烂额的,但结果却是不尽人意。

就像是玩杂耍:抛在空中的球两个是比较容易控制,增加复杂度两个以上球难度会增大很多很多。在代码设计过程中也是如此,如果关注的太多,将会越复杂,越复杂手忙脑乱势必会造成遗漏导致错误发生。需要同时关注的事情或者逻辑太多是不可取的,但是该软件中存在诸多逻辑和复杂的事情是不可避免的。为了保证代码的各方面性能,应该降低管理复杂度!

如何降低管理复杂度:

  • 减少在同一时间所关注的本质复杂度的量
  • 避免生成不必要的偶然复杂度

3、好的设计所具有的特征

这些目标之间有时会相互抵触,所以需要在这些目标中做出一套最好的折中方案,这正是设计的挑战与魅力。

  • 最小的复杂度(Minimal complexity)

    • 在设计时应该只专注于程序的某一部分,安心的忽视其他部分。
  • 易于维护(Ease of maintenance)

    • 在设计时要从做维护工作的程序员角度思考该设计是否易于维护
  • 松散耦合(loose coupling)

    • 设计时应该让程序的各个组成部分之间关联性降低最小
    • 模块之间,类之间,尽可能的合理抽象、封装和信息隐藏
    • 减少关联也就减少了集成、测试与维护工作量
  • 可扩展性(extensibility)

    • 增强系统的功能时无须破坏其底层结构
  • 可重用性(reusability)

    • 所设计的系统的组成部分能在其他系统中重复使用
  • 高扇入(high fan-in)

    • 让大量的类使用某个给定的类(比如工具类)
  • 低扇出(low fan-out)

    • 一个类里少量或适中地使用其他的类,一般不要超过7个,否则该类就可能会变得复杂
  • 可移植性(portability)

    • 设计出的系统应该很方便地移植到其他环境中
  • 精简性(cleanness)

    • 设计出的系统没有多余的部分。如:一个函数一个逻辑,一个类功能单一
    • 任何多余的代码也需要开发、Review和测试,并且修改了其他代码后还要重新考虑这部分
  • 层次性(stratification)

    • 对系统进行分层设计,按层次进行访问交互;对依赖部分进行统一抽象提供一致接口
  • 标准技术(Standard techniques)

    • 要尽量用标准化的、常用的方法,让整个系统给人一种熟悉的感觉。比如设计模式

4、软件设计的层次

软件设计是分层次性的,软件设计从高到低,大概有如下几个层次:

软件设计层次

5、软件设计方法

5.1 找出现实世界中的对象

  • 辨识对象及其属性
  • 确定可以对各个对象进行的操作
  • 确定各个对象可对其他对象进行的操作
  • 确定对象的哪些部分对其他对象可见
  • 定义每个对象的接口

以上步骤并无特定的顺序来完成,他们经常反复的被执行。经过上述步骤之后,会得到一个高层次的、面向对象的系统组织结构,然后可以通过两种方式进行迭代:在高层次上面进行迭代,以便更好的组织类的结构;或者在每个已经定义好的类上进行迭代,把每个类的设计细化。

5.2 形成一致的抽象

抽象能够让你在关注某一个概念时可以放心的忽略其中一些细节。

抽象是一种以简化的形式来看待复杂操作的能力。

优秀的程序员会在方法的层次上、类接口的层次上以及包接口的层次上进行抽象,这样才能更快、更稳妥的进行开发。

5.3 封装实现细节

封装填补了抽象留下的空白。
抽象是说:“可以让你在高层次的细节上来看待一个对象。”
封装是说:“除此之外,你不能看到对象的任何其他细节层次”。
封装帮助你管理复杂度的方法是不让你看到那些复杂度。

5.4 有可能的情况下继承

继承是面向对象编程中最强大的工具之一。如果使用得当,它能带来极大的益处,然后如果使用过不当,它也会带来极大的弊端。当继承能简化设计时可以考虑使用继承。

5.5 信息隐藏

在软件设计中,有两种信息应该被重点隐藏:

  1. 隐藏复杂性,因为软件设计的首要原则就是管理复杂性,对复杂性进行隐藏,有助于在设计软件时暂时不关注太多的细节
  2. 隐藏变化源,这样当变化发生时,其影响就能被限定在局部范围内,这其实也就是面向对象所提倡的封装变化

5.6 找出容易改变的区域

好的程序设计的挑战之一就是适应变化。将不稳定的区域隔离出来,从而把变化的影响隔离在方法、类或包的内部,将影响范围限制在最小范围内。也就是设计模式中常说的封装变化点。

以下是一些容易发生变化的地方

  • 业务规则
  • 输入输出
  • 对硬件的依赖
  • 困难的设计区域或构建区域
  • 状态变量
    • 不要用布尔变量做状态变量,请换成枚举类型
  • 数据量的限制
  • 非标准的语言特性

5.7 保持松散耦合

耦合表示类和类之间,以及方法和方法之间关系的紧密程度。松耦合的目标是创建出小的、直接的、清晰的类或方法(也称为模块),使它们和其他的类或方法之间的关系尽可能的灵活。应该尽量使创建的模块不依赖或少依赖于其他的模块。

规模(指模块之间的链接数)、可见性、灵活性是耦合的标准。尽量构建规模小、可见性大、灵活性强的模块。

松散耦合的关键在于,一个模块提供了一层附加的抽象,一旦其Ok,你就可以想当然的去使用它。这样就降低了系统的复杂性,使你在同一时间只关注一件事情。如果在同一时间需要同时关注几件事情——内部细节、全局变量等,那么就失去了抽象的能力,模块所具有的管理复杂性的能力也削弱或完全丧失了。

5.8 使用设计模式

设计模式精炼了众多现成的解决方案,可用于解决很多软件开发中常见的问题。有些软件问题需要全新的解决方案,但大部分的问题都和过去遇到过的问题类似,因此可以用类似的解决方案或者模式加以解决。

应用设计模式时存在两大陷阱:

  • 强迫代码来适应某个模式。这样做有时反而会把问题复杂化
  • 为了模式而模式。不能因为想使用某个模式,而不考虑该模式是否适合就使用它

5.9 其他的设计方法

  • 高类聚性
  • 构造分层结构
  • 严格描述类契约(接口)
  • 分配责任(类)
  • 为测试而设计(TDD)
  • 避免失误
  • 有意识的选择绑定时间(动态绑定)
  • 创建中央控制点
  • 考虑使用蛮力突破
  • 画一个图
  • 保持设计的模块化

6、软件设计实践

  • 迭代(尝试的可能性越多,设计方案就会越好)
  • 分而治之
  • 自顶向下设计(分解;从一般性问题出发,将问题分解成可控的部分)
  • 自底向上设计(合成;从可控的部分出发,构造出一个通用的方案)
  • 建立实验性原型(写出用于回答特定设计问题的、量最少且能够随时扔掉的代码)
  • 合作设计(找人求助或一起讨论/审查你的设计方案)
  • 记录设计结果(以下任意一种方式都可以)
    • 正式的设计文档
    • 将设计文档插入代码中或 JavaDoc
    • 用 Wiki 来记录设计讨论和决策
    • 写总结邮件
    • 保留设计挂图,让大家能够随时查阅和修改
    • 在适当的细节层创建UML图

7、总结

不要停留于你所想到的第一套解决方案,而是去寻求合作,探求简洁性,在需要的时候做出原型,迭代,进一步迭代。好的设计都是迭代的,你尝试设计的可能性越多,你的设计能力就会越强,最终的设计方案就会越好。

核对表

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值