【书摘】代码大全

在这里不会按照书本的分类每一章都进行总结,而是挑选那些在我现在的水平看来比较有意义,还不算过时的内容。并且关于概念等大道理的部分会比较简略,重点会着眼与编码的一些经验和技巧。

毕竟是有些年头的书了,很多具体的开发实践已经不算特别适用了,但是背后体现的分析问题和解决问题的思路是绝不会过时的,另外整本书的组织非常有逻辑,不过有个问题,没有写过很多代码,做过大项目的人可能不是很能理解


  • 第 1 章 欢迎进入软件构建的世界 Welcome to Software Construction
  • 第 2 章 用隐喻来更充分地理解软件开发 Metaphors for a Richer Understanding of Software Development
  • 第 3 章 三思而后行:前期准备 Measure Twice, Cut Once: Upstream Prerequisites
    • 架构的典型组成部分 Typical Architectual Components
    • 要点
  • 第 4 章 关键的『构建』决策 Key Construction Decisions
  • 第 5 章 软件构建中的设计 Design in Construction
    • 理想的设计特征 Desirable Characteristics of a Design
    • 设计的层次 Levels of Design
    • 设计构造块:启发式方法 Design Building Blocks: Heuristics
    • 要点
  • 第 6 章 可以工作的类 Working Classes
    • 类的基础:抽象数据类型(ADTs) Class Foundations: Abstract Data Types
    • 良好的类接口 Good Class Interfaces
    • 良好的封装 Good Encapsulation
    • 有关设计和实现的问题 Design and Implementation Issues
    • 创建类的原因 Reasons to Create a Class
    • 与具体编程语言相关的问题 Language-Specific Issues
    • 要点
  • 第 7 章 高质量的子程序 High-Quality Routines
    • 创建子程序的理由 Summary of Reasons to Create a Routine
    • 在子程序层上设计 Design at the Routine Level
    • 要点
  • 第 8 章 防御式编程 Defensive Programming
    • 要点
  • 第 9 章 伪代码编程过程 The Pseudocode Programming Process
  • 第 10 章 使用变量的一般事项 General Issues in Using Variables
  • 第 11 章 变量名的力量 The Powerof Variable Names
  • 第 12 章 基本数据类型 Fundamental Data Types
    • 要点
  • 第 13 章 不常见的数据类型 Unusual Data Types
  • 第 14 章 组织直线型代码 Organizing Straight-Line Code
  • 第 15 章 使用条件语句 Using Conditionals
  • 第 16 章 控制循环 Controlling Loops
  • 第 17 章 不常见的控制结构 Unusual Control Structures
  • 第 18 章 表驱动法 Table-Driven Methods
  • 第 19 章 一般控制问题 General Control Issues
  • 第 20 章 软件质量概述 The Software Quality Landscape
  • 第 21 章 协同构建 Collaborative Construction
  • 第 22 章 开发者测试 Developer Testing
    • 要点
  • 第 23 章 调试 Debugging
    • 要点 Refactoring
  • 第 24 章 重构
    • 重构总结 Summary of Refactorings
    • 要点
  • 第 25 章 代码调整策略 Code-Tuning Strategies
  • 第 26 章 代码调整技术 Code-Tuning Techniques
    • 逻辑 Logic
    • 循环 Loops
    • 数据变换 Data Transformations
    • 表达式 Expression
    • 子程序 Routines
    • 要点
  • 第 27 章 程序规模对构建的影响 How Program Size Affects Construction
  • 第 28 章 管理构建 Managing Constructions
    • 要点
  • 第 29 章 集成 Integration
  • 第 30 章 编程工具 Programming Tools
  • 第 31 章 布局与风格 Layout and Style
  • 第 32 章 自说明代码 Self-Documenting Code
  • 第 33 章 个人性格 Personal Character
    • 要点
  • 第 34 章 软件工艺的话题 Themes in Software Craftsmanship
    • 要点

第 1 章 欢迎进入软件构建的世界 Welcome to Software Construction

  • 软件构建是软件开发的核心活动;构建活动是每个项目中唯一一项必不可少的工作
  • 构建活动主要是编码与调试,但也涉及详细设计、规划构建、单元测试、集成、集成测试等其他活动
  • 构建也常被称作『编码』和『编程』
  • 构建活动的质量对软件的质量有着实质性的影响
  • 你对『如何进行构建』的理解程度,决定了你这名程序员的优秀程度

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

重要的研发成果常常来自类比(analogy)。你对隐喻有多理解,也就决定了你对软件开发有多理解。

  • 隐喻是启示而不是算法,因此它们往往有一点随意(sloopy)
  • 隐喻把软件开发过程与其他你熟悉的活动联系在一起,帮助你更好地理解
  • 有些隐喻比其他一些隐喻更贴切
  • 通过把软件的构建过程比作是房屋的建设过程,我们可以发现,仔细的准备是必要的,而大型项目和小型项目之间也是有差异的
  • 通过把软件开发中的实践比作是智慧工具箱中的工具,我们又发现,每位程序员都有许多工具,但并不存在任何一个能适用于所有工作的工具,因地制宜地选择正确的工具是成为能有效编程的程序员的关键
  • 不同的隐喻并不排斥,应当使用对你最有益处的某种隐喻组合

第 3 章 三思而后行:前期准备 Measure Twice, Cut Once: Upstream Prerequisites

架构的典型组成部分 Typical Architectual Components

  • 程序组织 Program Organization
    • 应该明确定义各个构造快的责任。每个构造快应该负责一个区域的事情,并且对其他构造块负责的区域知道的越少越好
  • 主要的类 Major Classes
  • 数据设计 Data Design
    • 数据通常只应该由一个子系统或一个类直接访问
  • 业务规则 Business Rules
  • 用户界面设计 User Interface Design
    • 架构应该模块化,以便在替换为新用户界面时不影响业务规则和程序的输出部分
  • 资源管理 Resource Management
  • 安全性 Security
  • 性能 Performance
  • 可伸缩性 Scalability
  • 互用性 Interoperability
  • 国际化/本地化 Internationalization/Localization
  • 输入输出 Input/Output
  • 错误处理 Error Processing
  • 容错性 Fault Tolerance
  • 架构的可行性 Architectural Feasibility
  • 过度工程 Overengineering
  • 关于『买』还是『造』的决策 Buy-vs.-Build Decisions
  • 关于复用的决策 Reuse Decisions
  • 变更策略 Change Strategy
  • 架构的总体质量 General Architectural Quality

要点

  • 构建活动的准备工作的根本目标在于降低风险
  • 软件开发过程必须由始至终关注质量。在项目初期关注质量,对产品质量的正面影响比在项目末期关注质量的影响要大
  • 如果没有明确的问题定义,那么你可能会在构建期间解决错误的问题
  • 如果没有做完良好的需求分析工作,你可能没能察觉待解决的问题的重要细节
  • 如果没有做完良好的架构设计,你可能会在构建期间用错误的方法解决正确的问题
  • 理解项目的前期准备所采用的方法,并相应地选择构建方法

第 4 章 关键的『构建』决策 Key Construction Decisions

  • 每种编程语言都有其优点和弱点
  • 在开始编程之前,做好一些约定(convention)
  • 『构建的实践方法』的种类比任何单个项目能用到的要多。有意识地选择最适合你的项目的实践方法
  • 记得『深入一种语言去编程』,不要仅『在一种语言上编程』
  • 你在技术浪潮中的位置决定了哪种方法是有效的——甚至是可能用到的。确定你在技术浪潮中的位置,并相应调整计划和预期目标

第 5 章 软件构建中的设计 Design in Construction

理想的设计特征 Desirable Characteristics of a Design

  • 最小的复杂度(Minimal complexity)。应该做出简单且易于理解的设计。如果你的设计方案不能让你在专注于程序的一部分时安心地忽视其他部分的话,这一设计就没有什么作用
  • 易于维护(Ease of maintenance)。设计出能自明(self-explanatory)的系统
  • 松散耦合(Loose coupling)。设计时让程序的各个组成部分之间关联最小。通过应用类接口中的合理抽象、封装性及信息隐藏等原则,设计出相互关联尽可能最少的类。减少关联也就减少了集成、测试与维护时的工作量
  • 可拓展性(Extensibility)。能够增强系统的功能而无须破坏其底层结构
  • 可重用性(Reusability)。所设计的系统的组成部分能在其他系统中重复使用
  • 高扇入(High fan-in)。让大量的类使用某个给定的类。这意味着设计出的系统很好地利用了在较低层次上的工具类(utility classes)
  • 低扇出(Low fan-out)。让一个类里少量或适中地使用其他的类
  • 可移植性(Portability)。应该这样设计系统,使它能很方便地移植到其他环境中
  • 精简性(Leanness)。设计出的系统没有多余的部分
  • 层次性(Stratification)。尽量保持系统各个分解层的层次性,使你能在任意的层面上观察系统,并得到某种具有一致性的看法
  • 标准技术(Standard techniques)。一个系统所依赖的外来的、古怪的东西越多,别人在第一次想要理解它的时候就越是头疼。要尽量用标准化的、常用的方法,让整个系统给人一种熟悉的感觉

设计的层次 Levels of Design

一个程序中的设计层次。系统首先被组织为子系统子系统被进一步分解为,然后又被分解为子程序数据。每个子程序的内部也需要进行设计。

  • 第 1 层:软件系统 Software System
    • 先从子系统或者**包(package)**这些类的更高组织层次来思考会更有益处
  • 第 2 层:分解为子系统或包 Division into Subsystems or Packages
    • 识别出主要的子系统,确定如何把程序分为主要的子系统,并定义清楚允许各子系统如何使用其他子系统
    • 在每个子系统的内部可能要用到不同的设计方法——请对系统中的每一部分选用最恰当的方法
    • 不同子系统之间相互通信的规则特别重要。如果所有的子系统都能同其他子系统通信,你就完全失去了把它们分开所带来的好处。应该通过限制子系统之间的通信来让每个子系统更有存在意义
    • 为了让自从之间的连接简单易懂且易于维护,就要尽量简化子系统之间的交互关系
    • 一个很好的基本原则是使用**无环图(acyclic graph)**来进行系统层设计
  • 第 3 层:分解为类 Division into Classes
    • 识别出系统中所有的类
    • 当定义子系统中的类时,也就同时定义了这些类与系统的其余部分打交道的细节
  • 第 4 层:分解成子程序 Division into Routines
    • 把每个类细分为子程序
    • 完整地定义出类内部的子程序,常常会有助于更好地理解类的接口,反过来,这又有助于对类的接口进行进一步的修改
  • 第 5 层:子程序内部的设计 Internal Routine Design
    • 为每个子程序布置详细的功能
    • 编写伪代码、选择算法、组织子程序内部的代码块,以及用编程语言编写代码

设计构造块:启发式方法 Design Building Blocks: Heuristics

下面是一些思考问题的方法

  • 找出现实世界中的对象 Find Real-World Objects
    • 辨识对象及其属性
    • 确定可以对各个对象进行的操作
    • 确定各个对象能对其他对象进行的操作
    • 确定对象的哪些部分对其他对象可见
    • 定义每个对象的公开接口
  • 形成一致的抽象 Form Consistent Abstractions
  • 封装实现细节 Encapsulate Implementation Details
  • 当继承能简化设计时就继承 Inherit-When Inheritance Simplifies the Design
  • 隐藏秘密(信息隐藏) Hide Secrets(Information Hiding)
    • 两种方式:隐藏复杂度,隐藏变化源
    • 障碍:信息过度分散,循环依赖,把类内数据误认为全局数据,可以察觉的性能损耗
  • 找出容易改变的区域 Identify Areas Likely to Change
    1. 找出看起来容易变化的项目
    2. 把容易变化的项目分类出来
    3. 把看起来容易变化的项目隔离开来
    4. 一些容易发生变化的区域:业务规则,对硬件的依赖性,输入和输出,非标准的语言特性,困难的设计区域和构建区域,状态变量,数据量的限制
  • 保持松散耦合 Keep Coupling Loose
    • 耦合标准:规模,可见性,灵活性
    • 耦合的种类:简单数据参数耦合,简单对象耦合,对象参数耦合,语义上的耦合
  • 查阅常用的设计模式 Look for Common Design Patterns
    • Abstract Factory: 通过指定对象组的种类而非单个对象的类型来支持创建一组相关的对象
    • Adapter: 把一个类的接口转变为另一个接口
    • Bridge: 把接口和实现分离开来,使他们可以独立地变化
    • Composite: 创建一个包含其他同类对象的对象,使得客户代码可以与最上层对象交互而无需考虑所有的细节对象
    • Decorator: 给一个对象动态地添加职责,而无须为了每一种可能的职责配置情况去创建特定的子类
    • Facade: 为没有提供一致接口的代码提供一个一致的接口
    • Factory Method: 做特定基类的派生类的实例化时,除了在 Factory Method 内部之外均无须了解各派生类对象的具体类型
    • Iterator: 提供一个服务对戏那个来顺序地访问一组元素中的各个元素
    • Observer: 使一组相关对象互相同步,方法是让另一个对象负责:在这组对象中的任何一个发生改变时,由它把这种变化通知给这个组里的所有对象
    • Singleton: 为有且仅有一个实例的类提供一种全局访问功能
    • Strategy: 定义一组算法或者行为,使得它们可以动态地相互替换
    • Template Method: 定义一个操作的算法结构,但是把部分实现的细节留给子类
  • 其他的启发式方法 Other Heuristics
    • 高内聚性 Aim for Strong Cohesion
    • 构造分层结构 Build Hierarchies
    • 严格描述类契约 Formalize Class Contracts
    • 分配职责 Assign Responsibilities
    • 为测试而设计 Design for Test
    • 避免失误 Avoid Failure
    • 有意识地选择绑定时间 Choose Binding Time Consciously
    • 创建中央控制点 Make Central Points of Control
    • 考虑使用蛮力突破 Consider Using Brute Force
    • 画一个图 Draw a Diagram
    • 保持设计的模块化 Keep Your Design Modular

要点

  • 软件的首要使命就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助
  • 简单性可以通过两种方式来获取:一是减少在同一时间所关注的本质性复杂度的量,而是避免生成不必要的偶然的复杂度
  • 设计是一种启发式的过程。固执与某一种单一方法会损害创新能力,从而损害你的程序
  • 好的设计都是迭代的。你尝试设计的可能性越多,你的最终设计方案就会变得越好
  • 信息隐藏是个非常有价值的概念。通过询问『我该隐藏些什么?』能够解决很多困难的设计问题
  • 很多有用有趣的、关于设计的信息存在于本书之外。这里所给出的观点只是对这些有价值资源的一点提示而已

第 6 章 可以工作的类 Working Classes

类的基础:抽象数据类型(ADTs) Class Foundations: Abstract Data Types

ADT 是指一些数据以及对这些数据所进行的操作的集合。举个例子,假设要写一个程序,用不同的字体、字号和文字属性来控制显示在屏幕上的文本。如果你用一个 ADT,就能有捆绑在相关数据上的一组操作字体的子程序,这些子程序和数据集合为一体,就是一个 ADT。

如果不使用 ADT,就要用一种拼凑的方法来操作字体,代码可能是这样的

currentFont.size = 12
currentFont.attribute = BOLD

而如果使用 ADT,代码可能是这样的

currentFont.setSize(12)
currentFont.setBoldOn()

这些子程序里的代码可能很短,但是对于字体的操作都被隔离出来了,也就是提供了更好的抽象层,同时也可以在针对字体的操作发生时提供一层保护。

根据这个例子,可以得到如下的指导建议:

  • 把常见的底层数据类型创建为 ADT 并使用这些 ADT,而不再使用底层数据类型
  • 把像文件这样的常见对象当成 ADT
  • 简单的事物也可以当做 ADT
  • 不要让 ADT 依赖于其存储介质

良好的类接口 Good Class Interfaces

创建一个可以通过接口来展现的合理的抽象,并确保细节仍被隐藏在抽象背后。一些指导建议:

  • 类的接口应该展现一致的抽象层次。每一个类应该实现一个 ADT,并且仅实现这个 ADT
  • 一定要理解类所实现的抽象是什么
  • 提供成对的服务。大多数操作都有和其相应的、相等的以及相反的操作
  • 把不相关的信息转移到其他类中
  • 尽可能让接口可编程,而不是表达语义
  • 谨防在修改时破坏接口的抽象
  • 不要添加与接口抽象不一致的公用成员
  • 同时考虑抽象性和内聚性

良好的封装 Good Encapsulation

封装是一个比抽象更强的概念。抽象通过提供一个让你忽略实现细节的模型来管理复杂度,而封装则强制阻止你看到细节,下面是一些指导建议:

  • 尽可能地限制类和成员的可访问性
  • 不要公开暴露成员数据
  • 避免把私用的实现细节放入类的接口中
  • 不要对类的使用者做出任何假设
  • 避免使用友元类(friend class)
  • 不要因为一个子程序里仅使用公用子程序,就把它归入公开接口
  • 让阅读代码比编写代码更方便
  • 要格外警惕从语义上破坏封装性
  • 留意过于紧密的耦合关系

有关设计和实现的问题 Design and Implementation Issues

  • 包含(『有一个….』的关系) Containment(“has a” Relationships)
    • 在万不得已时通过 private 继承来实现『有一个』的关系
    • 警惕有超过约 7 个数据成员的类
  • 继承(『是一个…』关系) Inheritance(“is a” Relationship)
    • 用 public 集成来实现『是一个…』的关系
    • 要么使用继承并进行详细说明,要么就不要用
    • 遵循 Liskov 替换原则(Liskov Substitution Principle, LSP)
      • 派生类必须能通过基类的接口而被使用,且使用者无须了解两者之间的差异
    • 确保只继承需要继承的部分
    • 不要『覆盖』一个不可覆盖的成员函数
    • 把共用的接口、数据及操作放到继承树中尽可能高的位置
    • 只有一个实例的类是值得怀疑的
    • 只有一个派生类的基类也值得怀疑
    • 派生后覆盖了某个子程序,但在其中没做任何操作,这种情况也值得怀疑
    • 避免让继承体系过深
    • 尽量使用多态,避免大量的类型检查
  • 成员函数和数据成员 Member Functions and Data
    • 让类中子程序的数量尽可能少
    • 禁止隐式地产生你不需要的成员函数和运算符
    • 减少类所调用的不同子程序的数量
    • 对其他类的子程序的间接调用要尽可能少
  • 构造函数 Constructors
    • 如果可能,应该在所有的构造函数中初始化所有的数据成员
    • 用私有(private) 构造函数来实现 singleton 属性
    • 优先采用 deep copies,除非论证可行,才采用 shallow copy

创建类的原因 Reasons to Create a Class

  • 对现实世界中的对象建模
  • 对抽象对象建模
  • 降低复杂度
  • 隔离复杂度
  • 隐藏实现细节
  • 限制变化所影响的范围
  • 隐藏全局数据
  • 让参数传递更顺畅
  • 创建中心控制点
  • 让代码更易于重用
  • 为程序族做计划
  • 把相关操作放到一起
  • 实现特定的重构

应该避免的类

  • 避免创建万能类
  • 消除无关紧要的类
  • 避免用动词命名的类

与具体编程语言相关的问题 Language-Specific Issues

下面列出跟类相关的,不同语言之间有着显著差异的一些地方:

  • 在继承层次中被覆盖的构造函数和析构函数的行为
  • 在异常处理时构造函数和析构函数的行为
  • 默认构造函数的重要性
  • 析构函数或终结器(finalizer)的调用时机
  • 和覆盖语言内置的运算符相关的知识
  • 当对象被创建和销毁时,或当其被声明时,或者它所在的作用域退出时,处理内存的方式

要点

  • 类的接口应提供一致的抽象。很多问题都是由于违背该原则而引起的
  • 类的接口应隐藏一些信息——如某个系统接口、某项设计决策、或一些实现细节
  • 包含往往比继承更为可取——除非你要对『是一个/is a』的关系建模
  • 继承是一种有用的工具,但它会增加复杂度,这有违于软件的首要技术使命——管理复杂度
  • 类是管理复杂度的首选工具。要在设计类时给予足够的关注,才能实现这个目标

第 7 章 高质量的子程序 High-Quality Routines

什么是『子程序(rountine)』?子程序是为实现一个特定的目的而编写的一个可被调用的方法(method)或过程(procedure)。例如 C++ 中的函数(function),Java 中的方法(method)。

创建子程序的理由 Summary of Reasons to Create a Routine

  • 降低复杂度
  • 引入中间的、易懂的抽象
  • 避免代码重复
  • 支持子类化
  • 隐藏顺序
  • 隐藏指针操作
  • 提高可移植性
  • 简化复杂的逻辑判断
  • 改善性能
  • 隔离复杂度
  • 隐藏实现细节
  • 限制变化所带来的影响
  • 隐藏全局数据
  • 形成中央控制点
  • 促成可重用的代码
  • 达到特定的重构目的

在子程序层上设计 Design at the Routine Level

功能的内聚性(functional cohesion):最强也是最好的一种内聚性,也就是说让一个子程序只进行一项操作。

  • 好的子程序名字 Good Routine Names
    • 描述子程序所做的所有事情
    • 避免使用无意义的、模糊或表叔不清的动词
    • 不要仅通过数字来形成不同的子程序名字
    • 根据需要确定子程序名字的长度
    • 给函数命名时要对返回值有所描述
    • 给过程起名时使用语气强烈的动词加宾语的形式
    • 准确使用对仗词
    • 为常用操作确立命名规则

子程序最好不要超过 200 行

  • 如何使用子程序参数 How to Use Routine Parameters
    • 按照输入-修改-输出的顺序排列参数
    • 考虑自己创建 in 和 out 关键字
    • 如果几个子程序都用了类似的一些参数,应该让这些参数的排列顺序保持一致
    • 使用所有的参数
    • 把状态或出错变量放在最后
    • 不要把子程序的参数用做工作变量
    • 在接口中对参数的假定加以说明
    • 把子程序的参数个数限制在大约 7 个以内
    • 考虑对参数采用某种表示输入、修改、输出的命名规则
    • 为子程序传递用以维持其接口抽象的变量或对象
    • 使用具名参数
    • 确保实际参数与形式参数相匹配

要点

  • 创建子程序最主要的目的是提高程序的可管理性,当然也有其他一些好的理由。其中节省代码空间只是一个次要原因;提高可读性、可靠性和可修改性等原因都更重要一些
  • 有时候,把一些简单的操作写成独立的子程序也非常有价值
  • 子程序可以按照其内聚性分为很多类,而你应该让大多数子程序具有功能上的内聚性,这是最佳的一种内聚性
  • 子程序的名字是它的质量的指示器。如果名字糟糕但恰如其分,那就说明这个子程序设计得很差劲。如果名字糟糕而且又不准确,那么它就反映不出程序是干什么的
  • 只有在某个子程序的主要目的是返回其名字所描述的特定结果时,才应该使用函数
  • 细心的程序员会非常谨慎地使用宏,而且只在万不得已时才用

第 8 章 防御式编程 Defensive Programming

  • 保护程序免遭非法输入数据的破坏 Protecting Your Program from Invalid Inputs
    • 检查所有来源于外部的数据的值
    • 检查子程序所有输入参数的值
    • 决定如何处理错误的输入数据
  • 使用断言的指导建议 Guidelines for Using Assertions
    • 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况
    • 避免把需要执行的代码放到断言中
    • 用断言来注解并验证前条件和后条件
      • 前条件是子程序或类的调用方法代码在调用子程序或实例化对象之前要确保为真的属性。前条件是调用方代码对其所调用的代码要承担的义务
      • 后条件是子程序或类在执行结束后要确保为真的属性。后置条件是子程序或类对调用方代码所承担的责任
    • 对于高健壮性的代码,应该先使用断言再处理错误
  • 错误处理技术 Error-Handling Techniques
    • 返回中立值
    • 换用下一个正确的数据
    • 返回与前次相同的数据
    • 换用最接近的合法值
    • 把警告信息记录到日志文件中
    • 返回一个错误码
    • 调用错误处理子程序或对象
    • 当错误发生时显示出错消息
    • 用最妥当的方式在局部处理错误
    • 关闭程序

**正确性(correctness)**意味着永不返回不准确的结果,哪怕不返回结果也比返回不准确的结果好

**健壮性(robustness)**则意味着要不断采取某些措施,以保证软件可以持续地运转下去,哪怕有时做出一些不够准确的结果

  • 异常 Exceptions
    • 用异常通知程序的其他部分,发生了不可忽略的错误
    • 只在真正例外的情况下才抛出异常
    • 不能用异常来推卸责任
    • 避免在构造函数和析构函数中抛出异常,除非你在统一地方把它们捕获
    • 在恰当的抽象层次抛出异常
    • 在异常消息中加入关于导致异常发生的全部信息
    • 避免使用空的 catch 语句
    • 了解所用库函数可能抛出的异常
    • 考虑创建一个集中的异常报告机制
    • 考虑异常的替换方案
  • 隔离程序,使之包容由错误造成的损害 Barricade Your Program to Contain the Damage Caused by Errors
    • 把某些接口选定为『安全』区域的边界,对穿越安全区域边界的数据进行合法性校验,当数据非法时做出敏锐的反应
    • 在输入数据时将其转换为恰当的类型
  • 辅助调试的代码 Debugging Aids
    • 不要自动地把产品版的限制强加于开发版之上 Don’t Automatically Apply Production Constraints to the Development Version
    • 尽早引入入住调试的代码 Introduce Debugging Aids Early
    • 采用进攻式编程 Use Offensive Programming
    • 计划移除调试辅助的代码 Plan to Remove Debugging Aids
  • 确定在产品代码中应该保留多少防御式代码 Determining How Much Defensive Programming to Leave in Production Code
    • 保留那些检查重要错误的代码
    • 去掉检查细微错误的代码
    • 去掉可以导致程序硬性崩溃的代码
    • 保留可以让程序稳妥地崩溃的代码
    • 为你的技术支持人员记录错误信息
    • 确认留在代码中的错误消失是友好的

要点

  • 最终产品代码中对错误处理的方式要比『垃圾进,垃圾出』复杂得多
  • 防御式编程技术可以让错误更容易被发现、更容易修改,并减少错误对产品代码的破坏
  • 断言可以帮助人尽早发现错误,尤其在大型系统和高可靠性的系统中,以及快速变化的代码中
  • 关于如何处理错误输入的决策是一项关键的错误处理决策,也是一项关键的高层设计决策
  • 异常提供了一种与代码正常流程角度不同的错误处理手段。如果留心使用异常,它可以成为程序员们知识工具箱中的一项有益补充,同时也应该在异常和其他错误处理手段之间进行权衡比较
  • 针对产品代码的限制并不适用于开发中的软件。你可以利用这一优势在开发中添加有助于更快地排插错误的代码

第 9 章 伪代码编程过程 The Pseudocode Programming Process

  • 创建类和子程序通常都是一个迭代的过程。在创建子程序的过程中获得的认识常常会反过来影响类的设计
  • 编写好的伪代码需要使用易懂的英语,要避免使用特定编程语言中才有的特性,同时要在意图的层面上写伪代码(即描述该做什么,而不是要怎么去做)
  • 伪代码编写过程是一个行之有效的做详细设计的工具,它同时让编码工作更容易。伪代码会直接转化为注释,从而确保了注释的准确度和实用性
  • 不要只停留在你所想到的第一个设计方案上。反复使用伪代码做出多种方案,然后选出其中最佳的一种方案再开始编码
  • 每一步完成后都要检查你的工作成果,还要鼓励其他人帮你来检查。这样你就会在投入精力最少的时候,用最低的成本发现错误

第 10 章 使用变量的一般事项 General Issues in Using Variables

  • 数据初始化过程很容易出错
  • 最小化每个变量的作用域。把同一变量的引用点集中在一起。把变量限定在子程序或类的范围之内。避免使用全局数据
  • 把使用相同变量的语句尽可能集中在一起
  • 早期绑定会降低灵活性,但有助于减小复杂度。晚期绑定可以增加灵活性,同时增加复杂度
  • 把每个变量用于唯一的用途

第 11 章 变量名的力量 The Powerof Variable Names

  • 好的变量名是提高程序可读性的一项关键因素。对特殊种类的变量,比如循环下标和状态变量,需要加以特殊的考虑
  • 名字要尽可能地具体,那些太模糊或者太通用以至于能够用于多重目的的名字通常都是很不好的
  • 命名规则应该能够区分局部数据、类数据和全局数据。它们还应当可以区分类型名、具名常量、枚举类型名字和变量名
  • 无论做哪种类型项目,你都应该采用某种变量命名规则。你所采用的规则的种类取决于你的程序的规模,以及项目成员的人数
  • 现代编程语言很少需要用到缩写。如果你真的要使用缩写,请使用项目缩写词典或者标准前缀来帮助理解缩写
  • 代码阅读的次数远远多于编写的次数。确保你所取的名字更侧重于阅读方便而不是编写方便

第 12 章 基本数据类型 Fundamental Data Types

  • 数值概论 Numbers in General
    • 避免使用 magic number
    • 如果需要,可以使用硬编码的 0 和 1
    • 预防除零错误
    • 使类型转换变得明显
    • 避免混合类型的比较
    • 注意编译器的警告
  • 整数 Integers
    • 检查整数除法
    • 检查整数溢出
    • 检查中间结果溢出
  • 浮点数 Floating-Point Numbers
    • 避免数量级相差巨大的数之间的加减运算
    • 避免等量判断
    • 处理舍入误差问题
  • 字符和字符串 Characters and Strings
    • 避免使用神秘字符和神秘字符串
    • 避免 off-by-one 错误(超出字符串末尾)
    • 了解你的语言和开发环境是如何支持 Unicode 的
    • 在程序生命期中尽早决定国际化/本地化策略
    • 如果你知道只需要支持一种文字的语言,请考虑使用 ISO 8859 字符集
    • 如果你需要支持多重语言,请使用 Unicode
    • 采用某种一致的字符串类型转换策略
  • 布尔变量 Boolean Vairables
    • 用布尔变量对程序加以文档说明
    • 用布尔变量来简化复杂的判断
    • 如果需要的话,创建你自己的布尔类型
  • 枚举类型 Enumerated Types
    • 用枚举类型来提高可读性
    • 用枚举类型来提高可靠性
    • 用枚举类型来简化修改
    • 将枚举类型作为布尔变量的替换方案
    • 检查非法数值
    • 定义出枚举的第一项和最后一项,以便用于循环边界
    • 把枚举类型的第一个元素留作非法值
    • 明确定义代码编写标准中第一个和最后一个元素的使用国泽,并且在使用时保持一致
    • 警惕给枚举元素明确赋值而带来的失误
  • 数组 Arrays
    • 确认所有的数组下标都没有超出数组的边界
    • 考虑用容器来取代数组,或者将数组作为顺序化结构来处理。在数组中随机访问就像在程序里面随意使用 goto 语句一样
    • 检查数组的边界点
    • 如果数组是多维的,确认下标的使用顺序是正确的
    • 提防下标串话

要点

  • 使用特定的数据类型就意味着要记住适用于各个类型的很多独立的原则
  • 如果你的语言支持,创建自定义类型会使得你的程序更容易修改,并更具有自描述性
  • 当你使用 typedef 或者其等价方式创建了一个简单类型的时候,考虑是否更应该创建一个新的类

第 13 章 不常见的数据类型 Unusual Data Types

  • 结构体可以使得程序更简单、更容易理解,以及更容易维护
  • 每当你打算使用结构体的时候,考虑采用类是不是会工作得更好
  • 指针很容易出错。用访问器子程序或类以及防御式编程实践来保护自己的代码
  • 避免使用全局变量,不只是因为它们很危险,还是因为你可以用其他更好的方法来取代它们
  • 如果你不得不使用全局变量,那么就通过访问器子程序来使用它

第 14 章 组织直线型代码 Organizing Straight-Line Code

  • 组织直线型代码的最主要原则是按照依赖关系进行排列
  • 可以用好的子程序名、参数列表、注释以及——如果代码足够重要——内务管理变量来让依赖关系变得更明显
  • 如果代码之间没有顺序依赖关系,那就设法使相关的语句尽可能地接近

第 15 章 使用条件语句 Using Conditionals

  • 对于简单的 if-else 语句,请注意 if 子句和 else 子句的顺序,特别是用它来处理大量错误的时候,要确认正常的情况是清晰的
  • 对于 if-then-else 语句串和 case 语句,选择一种最有利于阅读的排序
  • 为了捕捉错误,可以使用 case 语句中的 default 子句,或者使用 if-then-else 语句串中的最后那个 else 子句
  • 各种控制结构并不是生来平等的。请为代码的每个部分选用最合适的控制结构

第 16 章 控制循环 Controlling Loops

  • 循环很复杂。保持循环简单将有助于别人阅读你的代码
  • 保持循环简单的技巧包括:避免使用怪异的循环、减少嵌套层次、让入口和出口一目了然、把内务操作代码放在一处
  • 循环下标很容易被滥用。因此命名要准确,并且要把它们各自仅用于一个用途
  • 仔细地考虑循环,确认它在每一种情况下都运行正常,并且在所有可能的条件下都能退出

第 17 章 不常见的控制结构 Unusual Control Structures

  • 多个 return 可以增强子程序的可读性和可维护性,同时可以避免产生很深的嵌套逻辑,但是使用时要多加小心
  • 递归能够很优雅地解决一小部分问题。对它的使用也要倍加小心
  • 在少数情况下,got 是编写可读性和可维护代码的最佳方法。但这种情况非常罕见。除非万不得已,不要使用 goto

第 18 章 表驱动法 Table-Driven Methods

  • 表提供了一种复杂的逻辑和继承结构的替换方案。如果你发现自己对某个应用程序的逻辑或者继承树关系感到困惑,那么问问自己它是否可以通过一个查询表来加以简化
  • 使用表的一项关键决策是决定如何去访问表。你可以采取直接访问、索引访问或者阶梯访问
  • 使用表的另一项关键决策是决定应该把什么内容放入表中

第 19 章 一般控制问题 General Control Issues

  • 使布尔表达式简单可读,将非常有助于提高你的代码的质量
  • 深层次的嵌套使得程序变得难以理解。所幸的是,你可以相对容易地避免这么做
  • 结构化编程是一种简单并且仍然适用的思想:你可以通过把顺序、选择和循环三者组合起来而开发出任何程序
  • 将复杂度降低到最低水平是编写高质量代码的关键

第 20 章 软件质量概述 The Software Quality Landscape

  • 开发高质量代码最终并没有要求你付出更多,只是你需要对资源进行重新分配,以低廉的成本来防止缺陷出现,从而避免代价高昂的修正工作
  • 并非所有的质量保证目标都可以全部实现,明确哪些目标是你希望达到的,并就这些目标和团队成员进行沟通
  • 没有任何一种错误检测方法能够解决全部问题,测试本身并不是排除错误的最有效方法。成功的质量保证计划应该使用多种不同的技术来检查各种不同类型的错误
  • 在构建期间应当使用一些有效的质量抱枕高技术,但在这之前,一些具有同样强大功能的智力那个保证技术也是必不可少的。错误发现得越早,它与其余代码的纠缠就越小,由此造成的损失也越小
  • 软件领域的质量保证是面向过程的。软件开发与制造业不一样,在这里并不存在会影响最终产品的而重复的阶段,因此,最终产品的质量受到开发软件所用的过程的控制

第 21 章 协同构建 Collaborative Construction

结对编程的好处 Benefits of Pair Programming

  • 能够使人们在压力之下保持更好的状态
  • 改善代码质量。代码的可读性和可理解性都倾向于上升至团队中最优秀程序员的水平
  • 缩短进度时间表
  • 传播公司文化,指导初级程序员以及培养集体归属感

第 22 章 开发者测试 Developer Testing

  • **单元测试(Unit testing)**是将一个完整的类、子程序或者小程序,从完整的系统中隔离出来进行测试
  • **组件测试(Component testing)**是将一个类、包、小程序或者其他程序元素,从一个更加完整的系统中隔离出来进行测试,这些被测代码涉及到多个程序员或者多个团队
  • **集成测试(Integration testing)**是对两个或更多的类、包、组件或者子系统进行的联合测试

要点

  • 即使考虑到了各种可用的测试手段,测试仍然只是良好软件质量计划的一部分。高质量的开发方法至少和测试一样重要,这包括尽可能减少需求和设计阶段的缺陷。在检测错误方面,系统开发的成效至少与测试相当
  • 可以根据各种不同的思路来产生很多测试用例,思路包括基础测试、数据流分析、边界分析、错误数据类型以及正确数据类型等
  • 错误往往集中在少数几个容易出错的类和子程序上。找出这部分代码,重新设计和编写它们
  • 测试数据本身出错的密度往往比被测代码还要高。要像写代码一样小心地开发测试用例
  • 自动化测试总体来说是很有用的,也是进行回归测试的基础
  • 从长远来看,改善测试过程的最好办法就是将其规范化,并对其进行冰库,然后用从评估中获得的经验教训来改善这个过程

第 23 章 调试 Debugging

可以从程序中的错误得到如下好处

  • 理解你正在编写的程序
  • 明确你犯了哪种类型的错误
  • 从代码阅读者的角度分析代码质量
  • 审视自己解决问题的方法
  • 审视自己修正缺陷的方法

要点 Refactoring

  • 调试通整个软件开发的成败息息相关
  • 系统化查找和改正错误的方法至关重要
  • 动手解决问题之前,要理解问题的根本
  • 将编译器警告级别设置为最严格,把警告信息所报告的错误都改正
  • 调试工具对软件开发而言是强有力的支持手段,找出这些工具并加以应用

第 24 章 重构

重构的理由 Reasons to Refactor

  • 代码重复
  • 子程序太长
  • 循环太长或者嵌套太深
  • 类的内聚性太差
  • 类的接口的抽象层次不一致
  • 参数表中参数太多
  • 类的内部修改往往局限于某个部分
  • 需要对多个类进行并行修改
  • 对继承体系的并行修改
  • 需要对多个 care 语句进行并行修改
  • 相关的数据项只是被放在一起,没有组织到类中
  • 成员函数更多地使用了其他类的功能,而非自身类的
  • 过于依赖基本数据类型
  • 一个类不做什么事
  • 一连串传递流浪数据的子程序
  • 中间人对象什么也不干
  • 某个类通其他类关系过于密切
  • 子程序的命名太差
  • 数据成员被设置为 public
  • 派生类紧紧使用了基类的一小部分成员函数
  • 用注释来掩饰拙劣的代码
  • 使用了全局变量
  • 在子程序调用前使用设置代码,调用后使用收尾代码
  • 程序包含的某些代码似乎在将来某个时候才会被用到

重构总结 Summary of Refactorings

  • 数量级的重构
    • 用具名常量来代替神秘数值
    • 用更明确或更具信息量的名字来重命名变量
    • 将表达式内联化
    • 用函数来代替表达式
    • 引入中间变量
    • 将多用途变量转换为多个单一用途变量
    • 使用局部变量实现局部用途而不是使用参数
    • 将基础类型转化为类
    • 将一组类型码转化为类或是枚举类型
    • 将一组类型码转化为含派生类的类
    • 将数组转化为对象
    • 封装群集
    • 用数据类替代传统记录
  • 语句级的重构
    • 分解布尔表达式
    • 将复杂的布尔表达式转换为命名精确的布尔函数
    • 将条件语句中不同部分中的重复代码合并
    • 使用 break 或 return 而不是循环控制变量
    • 在嵌套的 if-then-else 语句中一旦知道结果就立即退出,而不是仅仅赋一个返回值
    • 用多态来代替条件语句
    • 创建并使用空对象代替对空值的检测
  • 子程序级的重构
    • 提取子程序
    • 将子程序代码内联化
    • 将冗长的子程序转化为类
    • 用简单的算法替代复杂算法
    • 增加参数
    • 减少参数
    • 将查询操作同修改操作区分开来
    • 合并功能相似的子程序,并用参数来区分他们
    • 通过传递不同的参数使子程序体现不同的功能
    • 传递整个对象而非特定成员
    • 传递特定成员而非整个对象
    • 封装向下转型操作
  • 类实现的重构
    • 将值对象改为引用对象
    • 将引用对象改为值对象
    • 用数据初始化来代替虚函数
    • 改变成员函数或数据的位置
    • 将特定代码提出生成派生类
    • 将相似的代码合并起来放到基类中
  • 类接口的重构
    • 将某成员子程序放到另一类中
    • 将一个类转化为两个
    • 删除某个类
    • 隐藏委托关系
    • 去掉中间人
    • 用委托代替继承
    • 用继承代替委托
    • 引入外部子程序
    • 引入扩展类
    • 封装暴露在外的成员变量
    • 对不能修改的成员去掉 Set() 函数
    • 隐藏在类的外部不会使用的成员函数
    • 封装不会用到的成员函数
    • 如果基类和派生类代码实现相似,将二者合并
  • 系统级的重构
    • 为复发控制的数据创建明确的索引源
    • 将单向类联系改为双向类联系
    • 使用工厂函数而非简单的构造函数
    • 用异常代替错误代码,或者反其道而行之

要点

  • 修改是程序一生都要面对的事情,不仅包括最初的开发阶段,还包括首次发布之后
  • 在修改中软件的质量要么改进,要么恶化。软件演化的首要法则就是代码演化应当提升程序的内在质量
  • 重构成功之关键下雨程序员应学会关注那些标志着代码需要重构的众多的警告或『代码臭味』
  • 重构成功的另一要素是程序员应当掌握大量特定的重构方法
  • 重构成功的最后要点在于要有安全重构的策略
  • 开发阶段的重构是提升程序质量的最佳时机

第 25 章 代码调整策略 Code-Tuning Strategies

  • 性能只是软件整体质量的一个方面,通常不是最重要的。精细的代码调整也只是实现整体性能的一种方法,通常也不是决定性的。相对于代码本身的效率而言,程序的架构、细节设计以及数据结构和算法选择对程序的运行速度和资源占用的影响通常会更大
  • 定量测量是实现性能最优化的关键。定量测量需要找出能真正决定程序性能的部分,在修改之后,应当通过重复测量来明确修改是提高还是降低了软件的性能
  • 绝大多数的程序都有那么一小部分代码耗费了绝大部分的运行时间。如果没有测量,你不会知道是哪一部分代码
  • 代码调整需要反复尝试,这样才能获得理想的性能提高
  • 为性能优化工作做好准备的最佳方法就是在最初阶段编写清晰的代码,从而使代码在后续工作中易于理解和修改

第 26 章 代码调整技术 Code-Tuning Techniques

本章重点讲述提高代码运行速度的方法,同时也对如何减小代码的资源占用给出了一些建议。

逻辑 Logic

  • 知道答案后停止判断 Stop Testing When You Know the Answer
    • 一些语言提供了『短路求值』,如果语言本身并不支持短路求值,就应当避免使用 and 或 or
  • 按照出现频率来调整判断顺序 Order Tests by Frequency
    • 让运行最快和判断结果最有可能为真的判断首先被执行
  • 用查询表替代复杂表达式 Substitute Table Lookups for Complicated Expressions
  • 使用惰性求值 Use Lazy Evaluation

循环 Loops

  • 将判断外提 Unswitching
    • 不在循环中进行判断
  • 合并 Jamming
    • 对同一个数组的操作放到一个循环中
  • 展开 Unrolling
    • 对少量元素,或者简单逻辑的操作,直接进行展开并另外更新循环索引
  • 尽可能减少在循环内部做的工作 Minimizing the Work Inside Loops
  • 哨兵值 Sentinel Values
    • 把哨兵值放到循环范围的末尾以保证循环一定能够终止
  • 把最忙的循环放在最内层 Putting the Busiest Loop on the Inside
  • 削减强度 Stength Reduction
    • 用多次轻量级运算来代替一次高昂的运算

数据变换 Data Transformations

  • 使用整形数而不是浮点数 Use Integers Rather Than Floating-Point Numbers
  • 数组纬度尽可能少 Use the Fewest Array Dimensions Possible
  • 尽可能减少数组引用 Minimize Array References
  • 使用辅助索引 Use Supplementary Indexes
  • 使用缓存机制 Use Caching

表达式 Expression

  • 利用代数恒等式 Exploit Algebraic Identities
  • 削弱运算强度 Use Strength Reduction
    • 用加法代替乘法
    • 用乘法代替幂乘
    • 利用三角恒等式代换等价的三角函数
    • 用 long 或 int 来答题 long long 整数
    • 用定点数或者整形数代替浮点数
    • 用单精度数代替双精度数
    • 用移位操作代替整数乘 2 或除 2
  • 编译期初始化 Initialize at Compile Time
    • 例如如果有一个经常要被用到的常量 log(2),直接给出 0.69314718 要比每次都计算要好
  • 小心系统函数 Be Wary of System Routines
    • 系统函数运行起来很慢,提供的精度常常也是根本不需要的
  • 使用正确的常量类型 Use the Correct Type of Constants
  • 预先算出结果 Precompute Results
  • 删除公共子表达式 Eliminate Common Subexpressions
    • 把计算好的值赋给一个变量,以后需要时直接引用这个变量

子程序 Routines

  • 将子程序重写为内联 Rewrite Routines Inline
  • 用低级语言重写代码 Recoding in a Low-Level Language

要点

  • 优化结果在不同的语言、编译器和环境下有很大差异。如果没有对每一次的优化进行测量,你将无法判断优化到底是帮助还是损害了这个程序
  • 第一次优化通常不会是最好的。即使找到了效果很不错的,也不要停下扩大战果的步伐
  • 代码调整这一话题有点类似于核能,富有争议

第 27 章 程序规模对构建的影响 How Program Size Affects Construction

  • 随着项目规模的扩大,交流需要加以支持。大多数方法论的关键点都在于减少交流中的问题,而一项方法论的存亡关键也应取决于它能否促进交流
  • 在其他条件都相等的时候,大项目的生产率会低于小项目
  • 在其他条件都相等的时候,大项目的每千行代码错误率会高于小项目
  • 在小项目里的一些看起来『理所当然』的活动在大项目中必须仔细地计划。随着项目规模扩大,构建活动的主导地位逐渐降低
  • 放大轻量级的方法论要好于缩小重量级的方法论。最有效的办法是使用『适量级』方法论

第 28 章 管理构建 Managing Constructions

  • 设定标准的考虑事项 Considerations in Setting Standards
  • 鼓励良好的编码实践的技术 Techniques for Encouraging Good Coding

要点

  • 好的编码实践可以通过『贯彻标准』或者『使用更为灵活的方法』来达到
  • 配置管理,如果应用得当,会使程序员的工作变得更加轻松,特别包括变更控制
  • 好的软件评估是一项重大挑战。成功的关键包括采用多种方法、随着项目的开展而修缮评估结果,以及很好地利用数据来创建评估等
  • 度量是构建管理成功的关键
  • 程序员和管理人员都是人,在把他们当人看的时候工作得最好

第 29 章 集成 Integration

  • 构建的先后次序和集成的步骤会影响设计、编码、测试各类的顺序
  • 一个经过充分思考的集成顺序能减少测试的工作量,并使调试变得容易
  • 增量集成有若干变型,而且——除非项目是微不足道的——任何一种形式的增量集成都比阶段式集成好
  • 针对每个特定的项目,最佳的集成步骤通常是自顶向下、自底向上、风险导向以及其他集成方法的某种组合。T-型集成和竖直分块集成通常都能工作得很好
  • daily build 能减少集成的问题,提升开发人员的士气,并提供非常有用的项目管理信息

第 30 章 编程工具 Programming Tools

  • 程序员有时会在长达数年的时间里忽视某些最强大的工具,之后发现并使用之
  • 好的工具能让你的日子过得安逸得多
  • 下面这些工具已经可用了:编辑、分析代码质量、重构、版本控制、除错、测试、代码调整
  • 你能打造许多自己用的专用工具
  • 好的工具能减少软件开发中最单调乏味的工作的量,但它不能消除对『编程』的需要

第 31 章 布局与风格 Layout and Style

  • 可视化布局的首要任务是指明代码的逻辑组织。评估该任务是否实现的指标包括准确性、一致性、易读性和易维护性
  • 外表悦目比起其他指标是最不重要的。然而,如果其他指标都达到了,代码质量又好,那么布局效果看上去也会不错
  • 结构化代码有其自身目的。始终如一地沿用某个习惯而少来创新。不能持久的布局规范只会损害可读性
  • 布局的很多方面涉及信仰问题,应试者将客观需要和主观偏好区分开来。定出明确的指标,在此基础上再讨论风格参数的选择

第 32 章 自说明代码 Self-Documenting Code

  • 该不该注释是一个需要认真对待的问题,好的注释才有价值
  • 源代码应当含有程序大部分的关键信息
  • 好代码本身就是最好的说明
  • 注释应说出代码无法说出的东西——例如概述用意等信息
  • 有的注释风格需要许多重复性劳动,应舍弃之,改用易于维护的注释风格

第 33 章 个人性格 Personal Character

  • 聪明和谦虚 Intelligence and Humility
  • 求知欲 Curiosity
    • 在开发过程中建立自我意识
    • 试验
    • 阅读解决问题的有关方法
    • 在行动之前做分析和计划
    • 学习成功项目的开发经验
    • 阅读文档
    • 阅读其他书本期刊
    • 同专业人士交往
    • 像专业开发看齐
      • 入门级 -> 中级 -> 熟练级 -> 技术带头人级
      • 最后一个很难突破
  • 诚实 Intellectural Honesty
  • 交流与合作 Communication and Coorperation
  • 创造力和纪律 Creativity and Discipline

要点

  • 人的个性对其编程能力有直接影响
  • 最有关系的性格为:谦虚、求知欲、诚实、创造性和纪律,以及高明的偷懒
  • 程序员高手的性格与天分无关,而任何事都与个人发展相关
  • 小聪明、经验、坚持和疯狂既有助也有害
  • 很多程序员不愿主动吸收新只是和技术,只依靠工作时偶尔接触新的信息。如果能抽出少量时间阅读和学习编程只是,要不了多久就能鹤立鸡群
  • 好性格与培养正确的习惯关系甚大,要成为解除的程序员,先要养成良好习惯,其他自然水到渠成

第 34 章 软件工艺的话题 Themes in Software Craftsmanship

  • 征服复杂性 Conquer Complexity
  • 精选开发过程 Pick Your Process
  • 首先为人写程序,其次才是为机器 Write Programs for People First, Computers Second
  • 深入一门语言去编程,不浮于表面 Program into Your Language, Not in It
  • 借助规范集中注意力 Focus Your Attention with the Help of Conventions
  • 基于问题域编程 Program in Terms of the Problem Domain
    • 将程序划分为不同层次的抽象 Separating a Program into Levels of Abstraction
      • 第 0 层:操作系统的操作和机器指令 Operating-System Operations and Machine Instructions
      • 第 1 层:编程语言结构和工具 Programming-Language Structures and Tools
      • 第 2 层:低层实现结构 Low-Level Implementation Structures
      • 第 3 层:低层问题域 Low-Level Problem-Domain Terms
      • 第 4 层:高层问题域 High-Level Problem-Domain Terms
  • 当心落石 Watch for Falling Rocks
  • 迭代,反反复复,一次又一次 Iterate, Repeatedly, Again and Again

要点

  • 编程的主要目的之一是管理复杂性
  • 编程过程对最终产品有深远影响
  • 合作开发要求团队成员之间进行广泛沟通,甚于同计算机的交互;而单人开发则是自我交流,其次才是与计算机
  • 编程规范一旦滥用,只会雪上加霜;使用得当则能为开发环境带来良好机制,有助于管理复杂性和相互沟通
  • 编程应基于问题域而非解决方案,这样便于复杂性管理
  • 注意警告信息,将其作为编程的疑点,因为编程几乎是纯粹的智力活动
  • 开发时迭代次数越多,产品的质量越好
  • 墨守成规的方法有悖于高质量的软件开发。请将编程工具箱中填满各种编程工具,不断提高自己挑选合适工具的能力