代码大全(五)-- 软件构建中的设计

本文探讨了设计的本质,包括权衡与优先级设定、启发式过程及限制条件下的最佳实践。介绍了攻击复杂性的策略,以及如何通过最小化复杂度、松散耦合等特性来改善设计质量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对设计,作者有些定义,英文原文的意义更为深刻,笔者在后面加上了自己的理解。

  • Design is about tradeoffs and priorities.
    一个系统所能达到的目标,基本上是矛盾的,例如速度与内存,准确率和召回率。在某种程度上,我们只能根据系统的特性寻求一个最佳的平衡点。例如,在一个决不能有错误出现的系统,我们可以基本可以放弃召回率。
  • Design involves restrictions
    在设计的时候,我们会受到硬件的限制、交付时间的限制、人员的限制,在这些限制之下,做出最合理的设计。设计是否最佳,被特定环境决定。
  • Design is a Heuristic progress
    设计是一个启发式的过程,在设计的过程中,我们需要使用多种方法去探测可行的方案,例如原型、曳光弹等等。没有任何一种工具是适用于任何项目的。
如果一个项目最终因为技术问题被延误,那么,其根本原因是没有管理好软件的复杂度。


How to attack complexity

高代价,低效率的设计源于以下三种根源:

  • 复杂的方法解决简单问题    大炮打蚊子
  • 简单但是错误的方法解决复杂的问题    设计阶段的错误将带来巨大的损失
  • 用不恰当的复杂方法解决复杂的问题    同上
Desirable characteristic of a Design
  •  最小的复杂度
  • 易于维护
  • 松散耦合(loosing coupling)
  • 可重用性
  • 高扇入(high-fan-in) 让大量的类使用给定的类,说明系统很好的利用了较低层次上的工具类。
  • 低扇出 (low-fan-out)     让一个类少量或者适中的使用其他类
  • 可移植性
  • 精简性
  • 层次性  (对已有业务的封装,或者对某项特定事物的封装,这样以后在进行代码修改的时候,只需要关注连接层即可)
  • 使用标准技术
Levels of Design

高代价,低效率的设计源于以下三种根源:

  • 复杂的方法解决简单问题    大炮打蚊子
  • 简单但是错误的方法解决复杂的问题    设计阶段的错误将带来巨大的损失
  • 用不恰当的复杂方法解决复杂的问题    同上


设计中的启发式方法 Heuristic
下文将按照软件的首要技术使命——管理复杂度——的原则来讲解每一种启发式方法:
  • 找出现实世界中的对象
    1. 辨识对象及其属性
    2. 确定可以对各个对象执行的操作
    3. 确定各个对象能对其它对象执行的操作(考虑对象之间最普遍的关系,例如继承和组合)
    4. 确定类的访问级别
    5. 定义每个对象的公共接口
    在基本的构建了一个类之后,可以使用如下的两种方法对设计进行迭代:在高层次的系统组织结构上进行迭代,以便更好的组织类结构;在一个已经定义好的类上进行迭代,把整个类的设计细化。
  • 形成一致的抽象 Form consistent Abstraction
    这里对抽象的解释比较精髓——抽象是一种能够让你在关注某一概念时可以放心的忽略其中的一些细节的能力——在不同的层次处理不同的细节。任何时候,在使用聚合对象的时候,就一定在使用抽象。Aggregate
    在合适层次的抽象对以后的设计将起到决定性的作用
  • 封装实现细节 Encapsulated Implementation Details
    采用封装管理复杂度的方法是不让用户看见看见那些复杂度。封装填充了抽象留下的空白,抽象使之能从一个高层的细节查看对象,而封装则保证了除此之外用户看不见其它任何的细节。
  • 当继承能简化设计时就继承 Inherit-When Inheritance Simplifies the Design
  • 信息隐藏 
    信息隐藏是减少重复工作的利器,因为对程序的易变区域执行信息的隐藏,封装了变化,这样当变化来临的时候,不会对修改造成过为严重的灾难。
    一般的信息隐藏手段——具名常量代替字面常量、类型隐藏、接口设计、类设计等等。
    信息隐藏中需要注意隐藏的信息源主要为一下两种:
    1. 隐藏复杂度 (侧面降低系统维护的代价)
    2. 隐藏变化源 (有效的应对需求的变化)
    造成信息隐藏的障碍:
    1. 信息过度分散
    2. 循环依赖
    3. 性能损耗 在编码层不必过多的操心性能问题,因为一般来说,真正决定系统性能的是高层的设计,另外,如果接口的定义良好,那么当确定了性能瓶颈之后,再对相应的子程序进行优化也仍然可行。
  • 找出易变的区域
    好的程序所面临的巨大挑战之一就是适应变化。应对变化一般有如下的措施:
    1. 找出看起来容易变化的项目
    2. 把容易变化的项目分离出来
    3. 将容易变化的类隔离开来——设计好类的接口,使其对变化不敏感,并且将变化限制在类的内部
    一些容易变化的区域大概是如下的项目:
    1. 业务规则
    2. 对硬件的依赖
    3. 输入和输出
    4. 非标准的语言特性(更换环境是否可用)
    5. 较为困难的设计区域和构建区域
    6. 状态变量(优先使用整形表示,使用子程序解释状态变量而非硬编码)
    如何确定一个项目的易变区域呢?首先,找出可能对用户有效的最小子集,这些区域构成了系统较为稳定的区域。以此为基础,逐步迭代,扩展系统,将新添加的功能隐藏并隔离起来。意即,非核心区域均可能处于易变的区域。
  • 保持松散的耦合
    保持松散的耦合将能够使一个模块更容易的被其它模块所使用。一些常见的耦合标准:
    1. 规模      模块之间的连接数
    2. 可见性  模块之间连接的明显程度——放在接口中直接表明连接关系?还是使用全局变量偷偷摸摸的发生关系?U are the boss
    3. 灵活性  模块之间的连接是否容易改动
    最后,再次重复,一个模块越容易被其它模块所调用,其耦合关系就越松散
    一般有如下常见的耦合方式:
    1. 简单数据参数的耦合 通过简单数据类型传参
    2. 简单对象耦合 一个模块实例化一个对象
    3. 对象参数的耦合 obj1要求obj2传递obj3参数
    4. 语义上的耦合 使用了另外模块的语义知识和语法元素
  • 查阅常用的设计模式
    使用设计模式有许多好处:
    1. 设计模式通过提供现成的抽象来减少复杂度 设计模式本身的名字就是最好的注释
    2. 设计模式通常把常见解决方案的细节制度化减少出错
    3. 设计模式提供了多种解决方案,具有启发性的价值
    4. 设计模式本身就是高层的抽象
  • 其它的一些启发式方法:
    1. 高内聚
    2. 构造分层结构
    3. 严格描述契约(按合约编程)
    4. 未测试而设计
    5. 学习失败的代码,从被人的失误中去防御潜在的威胁
    6. 有意识的选择绑定时间:变量的初始化时间
    7. 创建中央控制点

最后,作为结束,笔者很懒的从原书上截取了一张图片:

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值