CodeComplete 笔记 第1部分 打好基础

191 阅读18分钟

01 Software Construction

01 Software Construction.png

What is Software Construction

软件开发过程中的各种活动activity

  • 定义问题 problem definition
  • 需求分析 development
  • 规划构建 construction planning
  • 软件架构 software architecture 或 高层设计 high-level design
  • 详细设计 detailed design
  • 编码与调试 coding and debugging
  • 单元测试 unit testing
  • 集成测试 integration testing
  • 集成 integration
  • 系统测试 system testing
  • 保障维护 corrective maintenance

构建活动主要是编码与调试,其中具体任务task

  • 验证有关的基础工作已完成,因此构建活动可顺利地进行下去
  • 确定如何测试所写的代码
  • 设计并编写类class和子程序routine
  • 创建并命名变量variable 和具名常量 named constant
  • 选择控制结构 control structure , 组织语句块
  • 对你的代码进行单元测试和集成测试
  • 评审开发团队其他成员的底层设计和代码,并让他们评审你的工作
  • 润饰代码,仔细进行代码的格式化和注释
  • 将单独开发的多个软件组件集成为一体
  • 调整代码tuning code,让它更快、更省资源

Why is Software Construction important

构建活动是软件开发的主要组成部分

构建活动是软件开发中的核心活动

把主要精力集中于构建活动,可大大提高程序员的生产率

构建活动的产物 - 源代码 - 往往是对软件的唯一精确描述

构建活动是唯一一项确保会完成的工作

02 Metaphors for a Richer Understanding of Software Development 用隐喻来更充分地理解软件开发

02 Metaphors for a Richer Understanding of Software Development 用隐喻来更充分地理解软件开发.png

The Importance of Metaphors

隐喻的重要性

重要的研发成果常常产自类比analogy。通过把你不太开解的东西产生更深刻的理解。这种使用隐喻的方法叫做“建模modeling”

科学发展的历史并不是一系列从“错误”的隐喻到“正确”的隐喻的转变,而是一系列从“不太合适”的隐喻到“更好”的以隐喻的转变

How to Use Software Metaphors

与其说一个软件隐喻像是一张路线图,不如说它是一盏探照灯。它不会告诉你到哪里去寻找答案,而仅是告诉你该如何去寻找答案。

隐喻的作用更像启示 heuristic ,而不是算法 algorithm

算法是一套定义明确的指令,使你能完成某个特定的任务。算法是可预测的predictable 、 确定的 deterministic 、不易变化的 not subject to chance

而启发式方法是一种帮你寻求答案的技术,但它给出的答案是具有偶然性的subject to chance

对于编程来说,最大的挑战还是将问题概念化 conceptualizing,编程中的很多错误都是概念性的错误

那么该如何使用软件中的隐喻呢?应该用它来提高你对编程问题和编程过程的洞察力; 用它来帮助你思考编程过程中的活动,想像出更好的做事情的方法

Common Software Metaphors

常见的软件隐喻

Software Penmanship: Writing Code

软件中的书法:写作代码

  • 关于软件开发最原始的隐喻是从写作 writing
  • 对于个人规模的小型项目来说,这种隐喻足够了,然而对于其他场合而言,还远远不够 —— 它没有完整、充分地刻画软件开发工作。书写通常只是个人活动,而软件项目会涉及多个职位的很多人。
  • 对写作而言,最重要的是其原创性。但是对于软件构建来说,“原创成果”的开发效率,往往低于专注于重用reuse 以往项目的一些设计思想、代码以及测试用例的开发效率。
  • 文章写作这一隐喻暗示着软件开发过程是一种代价昂贵的说错 trial and error 过程,而非仔细的规划和设计
  • 当作第一次尝试时就让它成功 —— 或在成本最低时多试几次。其他一些隐喻更好地阐明了达到这个目标的途径

Software Farming: Growing a System

软件的耕作法:培植系统

  • 将创造软件想象厂类似播种和耕作的情形。你一次设计系统的一小部分、写出一段代码、做一点测试,并将成果一点点添加到整个系统中。通过这种小步前进,你可把每次可能遇到的麻烦减到最小
  • 这个例子里的增量技术是很有价值的,但把它比做播种和耕作却非常糟糕。其隐喻的弱点在于它暗示了人们将无法对开发软件的过程和方式进行任何直接的控制。你在春天播下代码的种子,然后按照农历节耕作,你将会在秋天收获到丰盛的代码

Software Oyster Farming: System Accretion

软件的牡蛎养殖观点:系统生长

  • 在谈论培育(growing)软件时,有时人们实际上是指软件的生长 accretion, 这两种隐喻是紧密相关的,而软件生长是一幅更发人深省的景象。
  • 你需要学会如何一次为软件系统增加一个小部分。别“生长”密切相关的另一些词语有:增量的 incremental 、迭代的 iterative 、自适应的 adaptive 以及 演进的 evolutionary 。以增量方式进行设计、编译和测试,都是目前已知的最强有力的软件开发概念
  • 在进行增量式开发时,我们先做出软件系统的一个尽可能简单、但能运行的版本。它不必接受真实的输入,也无须对数据进行真正的处理,更不用产生真实的输出 —— 它仅需要构成一个足够强壮的骨架,支撑起未来将要开发的真实系统。这个最基本的起点,就像牡蛎开始孕育珍珠的那颗细小沙粒。
  • 在骨架形成之后,你要一点点地在其上附着肌肉和皮肤:把每个虚假的类、假装接受的输入掉换为真正的类和真实的输入。你一次增加一小部分代码,直到得到一个完全可工作的系统。
  • 作为一个隐喻而言,增量式开发的优势在于未做过度的承诺。比起耕作那个隐喻来,对它不恰当的引申要更困难些。

Software Construction: Building Software

软件构建:建造软件

  • 建造building 它和软件生长的概念是相通的,且提供了更详细的指引。
  • 如果你要盖一个简单的建筑物—— 比如一个狗屋。即使你忘了弄个门,那也没什么大不了,修改一下或干脆从头再来就是了。最多损失一个下午时间。这种宽松的方式对于小型项目来说也还算合适
  • 如果你是在建一栋房子,那么这个建造过程就会复杂得多,而糟糕的设计所引发的后果也更严重。首选你要决定准备建造一个什么类型的房子 —— 在软件开发里的类似事项称为问题定义 problem definition。接下来,你必须和某个建筑师 architect 探讨这一总体设计,并得到批准。这跟软件架构设计 architectural design 十分相似。然后你画出蓝图,就像软件的详细设计。再然后,你要准备好建筑地点、打地基,搭房屋框架,砌墙,通水电等。这就如同软件开发的构建 construction一样。 在之后,装修工还要美化一番。这就好比软件的优化 optimization 过程。在整个过程中,还会有各种监察人员来检查,这相当于软件评审 reviews 和详查 inspections
  • 盖房子和软件项目,原材料相对廉价,劳动力却是最昂贵的。
  • 除此之外,建筑房子你不会建造洗衣机、冰箱等。当开发软件时,你不会自己写操作系统层次的代码、科学计算函数、UI组件等。
  • 但如果你要建造高档住宅,那们可能需要特别订制橱柜,可能需要特殊的形状和特别尺寸订制的窗户。在软件开发中如果你要开发一款一流的软件产品,你可能会自己编写科学计算函数以便获得更快的速度和更高的精度。可能还需要自己编写容器类、UI组件等。
  • 有很多常见的软件开发术语都是从建筑这一隐喻中衍生出来的:软件机构 architecture、支撑性测试代码 (脚手架 scaffolding)、构建construction

03 Measure Twice, Cut Once/ Upstream Prerequisites 三思而后行:前期准备

03 Measure Twice, Cut Once: Upstream Prerequisites 三思而后行:前期准备.png

Measure Twice, Cut Once:

Upstream Prerequisites 三思而后行:前期准备

Importance of Prerequisites

前期准备的重要性

如果你在项目末期强调质量,那么你会强调系统测试。但是测试只是完整的质量保证策略的一部分。

如果你在项目中强调质量,那么你会强调构建实践。这些是本书绝大部分篇幅的关注点

如果你在项目的开始阶段强调质量,那么你就会计划、要求并且设计一个高质量的产品。

如果在开始构建活动之前认真地进行适当的准备活动,那么项目将会运作得极好。

准备工作的中目标是降低风险:目前,软件开发中最常见的项目风险是糟糕的需求分析和糟糕的项目计划,因此准备工作就倾向于集中改进需求分析和项目规划

Requirements Prerequisite

需求的先决条件

Why Have Official Requirements

  • 明确需求有助于确保是用户(而不是程序员)驾驭系统的功能、避免争论、减少开始编程开发之后的系统变更情况
  • 充分详尽地描述specify需求,是项目成功的关键,它甚至很可能比有效的构建技术更重要

Handing Requirements Changes During Construction

在构建期间处理需求变更

  • 如果你的需求不够好,那么就停止工作,退回去,先把它做好,再继续前进。如果没有对准正确的方向,那就要停下来检查一下路线

  • 确保每个人都知道需求变更的代价。客户只要想到一个新功能就会很兴奋。最简单的对付这种毒症患者的办法是说:“这听起来不错,不过由于它不是需求文档里的内容,我会整理修订过的进度表和成本估计表,这样就就可决定是现在实施,还是过一阵子在说。” 进度 和 成本 这两个字眼比咖啡都要提神,许多 must haves 很快会变成 nice to haves

  • 建立一套变更控制程序,评审提交的更改方案。让客户认识到他们需要功能,这个是坏事。问题是他们提出更改方案太频繁了,让你跟不上进度。

  • 使用能适应变更的开发方法

    • 演进原型 evolutonary prototyping法能让你在投入全部精力建造系统之前,先探索系统的需求
    • 演进交付evolutionary delivery 是一种分阶段交付系统的方法
  • Checklist: Requirements 核对表:需求

    • 针对功能需求 是否详细定义了

      • 系统的全部输入,包括其来源、精度、取值范围、出现频率等?
      • 系统的全部输出,包括目的地、精度、取值范围、出现频率、格式等?
      • 所有硬件级软件的外部接口?
      • 全部外部通信接口,包括握手协议、纠错协议、通信协议?
      • 用户下雨做的全部事情?
      • 每个任务所用的数据,以及每个任务得到的数据?
    • 针对非功能需求(质量需求) 是否详细描述了

      • 用户期望响应时间?是否为全部必要的操作?
      • 其他与计划时有关的考虑,例如处理时间、数据传输率、系统吞吐量?
      • 安全级别?
      • 可靠性,包括软件失灵的后果?
      • 对“成功”的定义“? “失败”的定义?
    • 需求的质量

      • 需求是用户语言书写的吗?用户也这么认为吗?
      • 每条需求都不与其他需求冲突吗?
      • 是否详细定义了相互竞争的特性之间的权衡 - 例如,假装性与正确性之间的权衡?
      • 是否避免在需求中规定设计?
      • 需求是否在详细程度上保持相当一致的水平?有些需求应更详细或粗略地描述吗?
      • 需求是否足够清晰,开发者也这么想吗?
      • 每个条款都与待解决的问题及其解决方案相关吗?能从每个条款上溯到它在问题域中对应的根源吗?
      • 是否每个需求都可测试?
      • 是否详细描述了所有可能的对需求的改动,包括各项改动的可能性?
    • 需求的完备性

      • 对在开始开发之前无法获得的信息,是否详细描述了信息不完全的区域?
      • 需求的完备度是否能达到这种程度:如果产品满足所有需求,那么它就是可接受的?
      • 你对全部需求都感到很舒服吗?是否去掉了那些不可能实现的需求?那些只是为了安抚客户和老板的东西?

Architecture Prerequisite

架构的先决条件

软件架构 software architecture 是软件设计的高层部分,是用于支撑细节的设计的框架。架构也称 系统架构 system architecture 、高层设计 high-level design 或 顶层设计 top-level design。 通常会用一份独立的文档描述架构,这份文档称为”架构规格书 architecture specification“ 或 顶层设计。

一个经过慎重考虑的架构为"从顶层到底层维护系统的概念完整性"提供了必备的结构和体系,它为程序员提供了指引。它将工作分为九个部分,使多个开发者或多个团队可独立工作

在构建期间或更晚时候进行架构变更,代价是高昂的。无论为了修正错误还是改进设计而引发的架构变更,越早越好。

Typical Architectural Components

架构的典型组成部分

  • 很多组成部分是优秀的系统架构所共有的。如果你自己构建整个系统,那么在架构工作会与详细的设计工作有重叠部分。这种情况下,至少应思考架构的每个组成部分。如你目前从事的系统的架构是别人做的,你应该能不费力地找出其中重要组成部分。在这两种情况中,你都需要考虑以下架构组成部分

  • Program Organization 程序组织

    • 系统架构首先要以概括的形式对有关系统做一个综述。
    • 在架构中,你应该能发现对那些曾经考虑过的最终组织结构的替代方案,找到之所以选用最终的组织结构,而不用其他替代方案的理由。
    • 架构应该定义程序的主要构造块 building blocks。每个构造块无论是一个类还是一组协同工作的类和子程序,它们共同实现一种高层功能,如与用户交互、解释命令、封装业务规则、访问数据、等等。如果两个或多个构造块声称实现同一项功能,那么它们就应该相互配合而不会冲突。
    • 应该明确定义各个构造块的责任。每个构造块应负责某一个区域的事情,并且对其他构造块负责的区域知道得越少越好。通过此,你能将设计的信息局限于各个构造块之内。
    • 应明确定义每个构造块的通信规则。对于每个构造块,架构应该描述它能直接使用哪些构造块,能间接使用哪些构造块,不能使用哪些构造块。
  • Major Classes

    • 架构应该详细定义所有主要的类。它应该指出每个主要的类的责任,以及该类如何与其他类交互。如果系统足够大,它应该描述如何将这些类组织成一个个子系统
    • 无须详细说明每个类。瞄准2/8法则:对构成系统80%行为的20%的类进行详细说明
  • Data Design

    • 架构应描述所用到的主要文件和数据表的设计。应描述曾考虑的其他方案,并说明做出选择的理由。在构建期间,这些信息让你能洞察架构师的思想。在维护阶段,这种洞察力是无价之宝。离开它,你就像看没有字幕的外语片。
  • Business Rules 业务规则

    • 如果架构依赖于特定的业务规则,那么它就应该详细描述这些规则,并描述这些规则对系统设计的影响
  • User Interface Design

    • 用户界面常常在需求阶段进行详细说明
    • 架构应该模块化,以便在替换为新UI时不影响业务规则和程序的输出部分。例如,架构应容易地做到:砍掉交互界面的类,插入命令行的类,以便进行单元测试
  • Resource Management 资源管理

    • 架构应光说在正常情况和极端情况下的资源使用量
  • Security 安全性

    • 架构应描述实现设计层面和代码层面的安全性方法
    • 在制定编码规范的时候应该把安全性牢记在心,包括处理缓冲区的方法、处理非受信数据(用户输入的数据、cookies、配置数据等)的规则、加密、错误消息的细致程度、保护内存中的秘密数据等
  • Performance 性能

    • 性能目标可包括资源的使用,资源之间的优先顺序
  • Scalability 可伸缩性

    • 指系统增长以满足未来需求的能力
  • Interoperability 互用性

    • 如预计系统会与其他软件或硬件共享数据或资源,架构应描述如何完成这一任务
  • Internationalization/Localization 国际化/本地化

  • Input/Output

    • 架构应详细定义读取策略是先做 look-ahead、后做 look-behind 还是即时做 just-in-time。而且应该描述在哪一层检测I/O错误:在字段、流,或文件的层次
  • Error Processing 错误处理

    • 错误处理已被证实为现代计算机科学中最棘手的问题之一,在架构中应清楚地说明一种”一致地处理错误“的策略

    • 错误处理常被视为是"代码约定层次 coding-convention-level" 的事情。但是因为错误处理牵连到整个系统,应最好在架构层次上对待它。

      • 错误处理是进行纠正还是仅仅进行检测?
      • 错误检测是主动还是被动?
      • 程序如何传播错误?
      • 错误消息的处理有什么约定?
      • 如何处理异常 exceptions? 何时能抛出异常,在什么地方捕获异常,如何记录log,以及如何在文档中描述异常
      • 在程序中,在什么层次上处理错误?你可在发现错误的地方处理,可将错误传播到专门的类进行处理,或沿着函数调用链往上传递错误。
      • 每个类在验证其输入数据的有效性方面需要负何种责任? 是每个类负责验证自己的数据的有效性,还是有一组类负责验证整个系统的数据的有效性? 某个层次上的类是否能假设它接收的数据是干净的?
      • 你是希望用运行环境中内建的错误处理机制,还是想建立自己的一套机制?
  • Fault Tolerance 容错性

    • 容错是增强系统可靠性的一组技术,包括检测错误; 如果可能的话从错误中恢复;否则应包容其不利影响。
  • Architectural Feasibility 架构可行性

    • 架构应该论证系统的技术可行性。应说明”这些问题是如何经过研究的“——通过验证概念的原型proof-of-concept prototype、研究、或手段。必须在全面开展构建之前解决掉这些风险。
  • Overengineering 过度工程

    • 通过在架构中明确地设立期望目标,就能避免出现”某些类异常健壮,而其他类勉强够健壮“的现象。
  • Buy-vs.-Build Decisions 关于 买 还是 造 的决策

    • 现代的GUI环境中编程的最大好处之一是,大量功能都能购买。如果架构不采用供应的组件,应说明”自己定制的组件应该在哪些方面胜过形成的程序库和组件“
  • Reuse Decisions 关于复用的决策

    • 如使用业界已存在的软件、测试用例、数据格式或其他原料,架构应说明:如何对复用的软件进行加工,使之符合其他架构目标。
  • Change Strategy 变更策略

    • 架构应列出已经考虑过的有可能会有所增强的功能,让架构足够灵活,能够适应可能出现的变化。
  • General Architectural Quality 架构的总体质量

    • 优先的架构耕书的特点在于,谈论了系统中的类、每个类背后的隐藏信息、”采纳或排斥所有可能的设计替代方案“的根本理由。