《代码大全》笔记 —— 模块七:软件匠艺

代码的布局和风格

基本理论

  • 代码视觉布局的首要任务是凸显代码的逻辑组织结构。用于衡量代码格式是否达到这个首要任务的标准包含:准确性、一致性、可读性和可维护性。
  • 相对这些衡量标准,代码美观排在它们后面,并且远远不及其他标准重要。如果已经满足这些衡量标准条件,而且递呈代码的性能良好,那么代码通常也会比较美观。
  • Visual Basic 语言本身就是纯程序块风格,而 Java 通常使用纯程序块风格,所有,如果用的是这两种语言,就应该使用纯程序块布局。在 C++ 语言中,无论是模拟纯程序块还是使用开始 - 结束来界定程序块,都可以。
  • 相比布局本身,代码的布局这一行为更重要。具体遵循哪一种惯例并不重要,重要的是始终如一。代码布局如果前后不一致,实际上会损害代码的可读性。
  • 布局在很多方面上都类似于信仰问题。试着把客观偏好和主观偏好区分开。对代码的布局偏好进行讨论时,最好有明确的衡量标准。

代码格式化的基本原理是,好的布局能够清楚地呈现程序的逻辑结构。美学方面的价值必须让位于清楚展现代码逻辑结构的价值。

每个人的知识结构可以帮助他们记住特定类型的信息。

在编码过程中,程序的结构必须保持一致,这比纠结于具体采用怎样的代码布局风格更为重要。

优秀的程序员应该对代码的布局风格保持开放的态度,虚心接受证明更好的做法,适当少用或者不用自己惯用的,即使一开始可能有些不适应。

好的代码布局想要达到的目标

  • 准确展现代码的逻辑结构。这也是代码风格的基本原则:好的布局,主要是想展示代码的逻辑结构。程序员通常用缩进和其他空白来展示代码的逻辑结构。
  • 始终以一致的方式来展现代码的逻辑结构。
  • 有利于增强代码的可读性。合理的缩进策略如果使得代码更难理解,也是无用的。
  • 方便对代码进行修改。好的代码布局,即使是代码有改动,也能继续保持好的格式。

布局技术

可以通过空白(比如空格、制表位、换行或空行)与括号来实现更好的代码布局。

注释的布局

注释的缩进与其对应的代码保持一致。

每个注释,至少要空一行。

自文档代码

  • 注释,还是不注释,这是个需要认真对待的问题。注释做得不好,只会浪费时间,有时甚至还会帮倒忙。注释做得好,才有价值。
  • 源代码应该纳入绝大部分与程序有关的关键信息。只要程序还在用,源代码就比其他资料更有可能维持在最新状态,因此,将重要信息融入代码是很有用的。
  • 优质代码本身就是最好的文档。如果代码差到需要进行大量注释,就应该先尝试改进代码,使之不再需要很多注释。
  • 注释应该描述代码本身无法表达的信息,到概要或意图这一层级即可。
  • 有些注释风格会带来很多重复劳动,不可取,需要该用易于维护的注释风格。

外部文档

单元开发文件夹:单元开发文件夹(UDF)或软件开发文件夹(SDF)是一种非正式文档,包含供开发者在构建期间使用的备注。

详细设计文档:详细设计文档是最低级别的设计文档。

编程风格即文档

依靠良好的编程风格来分担绝大部分的文档工作开销。

高效注释的关键

注释种类:

  1. 复述代码:复述型注释用不同的文字再次描述代码做了什么。
  2. 解释代码:解释型注释通常用于解释复杂的、机巧的或敏感的代码片段。
  3. 标记代码:标记型注释并非有意要留在代码中。
  4. 概述代码:概述代码的注释就是将若干行代码提炼成一两句话。(更简单的复述)
  5. 描述代码意图:意图层级的注释旨在描述一段代码的目的。意图型注释针对的是问题,而非针对解决方案。
  6. 传达代码自身无法表述的信息。

高效注释:

  1. 使用不会打断或打击修改积极性的注释风格。(更简单的注释)(如果只要按一个键就能得到排列整齐的星号那就用吧。不拒绝任何一种方便的工具。)
  2. 使用伪代码编程过程来减少注释时间。
  3. 把注释工作集成到开发风格里。
  4. 性能不是逃避注释的好借口。

注释的技术

  • 单行代码的注释
    • 避免矫情型注释
  • 行尾注释及其问题
    • 避免单行代码的行尾注释
    • 避免针对多行代码的行尾注释
    • 使用行尾注释来标注数据声明
    • 避免用行尾注释来写维护注解
    • 使用行尾注释来标记这个代码块结束(if、for 等代码块最后的花括号)
  • 注释代码段落
    • 编写代码意图层级的注释
    • 注重代码自身的文档化
    • 注重用段落注释阐述为什么而不是怎么做
    • 用注释为后续内容做好铺垫
    • 让每行注释都发挥其作用
    • 记录惊喜:如果发现有些信息无法通过看代码直接看出来,那就把它写到注释里。(“意外发现”,需要记录下来。)
    • 别用缩写词:注释不要有歧义,无需揣摩缩写词就能读懂。
    • 区分主次注释:有些情况下,或许需要区分开不同层级的注释,指出某个详细的注释是先前某个概要型注释的一部分。
    • 若是发现特定语言或者环境下有任何错误或未记录特性,要加注释
    • 阐明违背良好编码风格的原因:如果不得不违背良好的编码风格,请务必解释原因,以免某个好心的程序员试图改良代码风格,却好心办坏事,把代码改坏了。
    • 玩小聪明的代码不要加注释,重写(“骚操作”)
    • 注释数值数据的单位
    • 注释数值的有效范围:如果一个变量值有一个预期范围,就把它记录下来。
    • 注释代码寓意
    • 注释入口数据的限制:入口数据的来源包括传入参数、文件或用户直接输入。
    • 注释 “位标志”:如果变量被用作一个位域,就把每个数据位的意思记录下来。
    • 用变量名为相关注释盖戳:如果是某个特定变量有关的注释,要确保更改变量后还要同步更新注释。
    • 注释全局数据:如果要用全局数据,就在数据声明出处进行注释。注释应该表明数据的目的和设定为全局数据的原因。命名规范是凸显变量的全局性的首选。
  • 注释控制结构
    • 在每个 if、else、循环或代码块前面加注释
    • 注释每个控制结构的结束
    • 将循环尾部注释视为复杂代码的警示征兆
  • 注释子程序
    • 让注释靠近其描述的代码
    • 在子程序头部用一两句话对它进行描述
    • 在声明处对参数做说明
    • 充分利用 Javadoc 等代码文档工具的优势
    • 区分输入和输出数据
    • 记录接口假设:可以将注释接口假设视为是其他注释建议。
    • 注释子程序的限制条件:如果子程序提供的是一个数值化结果,请指明该结果的精确度。
    • 说明子程序的全局效果:如果子程序会修改全局数据,要准确描述它对全局数据做什么处理。
    • 记录所用算法的来源
    • 使用注释来标识程序各个部分
  • 注释类、文件和程序
    • 注释类的一般原则
      • 描述该类的设计方法
      • 描述其局限性和用法假设等
      • 注释类的接口
      • 不要在类的接口里面记录实现细节
    • 注释文档的一般原则
      • 描述每个文件的目的和内容
      • 将自己的名字、电邮和电话号码写入块注释
      • 纳入版本控制标签
    • 程序文档的书籍范式
      • “序” 一般放在文件头部,是一组介绍性的注释
      • “目录” 展示顶层文件、类和子程序(相当于 “章节”)
      • “节” 则是子程序内部的划分,比如子程序声明、数据声明以及可执行语句

个人性格

  • 人的个人性格对其编程能力有直接的影响。
  • 关系最大的性格是谦逊、好奇心、理性诚实、创造力和自律以及高明的懒惰。
  • 优秀程序员的性格与天赋无关,而是与主动参与个人发展有关。
  • 出乎意料的是,小聪明、经验、坚持和疯狂是双刃剑,既有利也有害。
  • 很多程序员不愿意主动吸收新技术和知识,只靠工作时偶尔接触新信息。如果能抽出少量时间阅读和学习编程知识,要不了多久,就能鹤立鸡群。
  • 优秀品格和培养正确的习惯关系很大,要想称为一名优秀的程序员,先养成良好的习惯,之后,其他的自然水到渠成。

编程工作本质上是无法管理的,因为没有人真正知道你在做什么。

聪明和谦卑

编程实践旨在减少大脑的负载

  • 分解系统的目的使其更容易理解。
  • 评审、检查和测试的目的是补偿预期的人为错误。
  • 保持日常活动常规化和简单化,可以减少大脑的负担。
  • 根据问题领域而不是帝城的实现细节来编程,可以减少脑力劳动。
  • 通过使用各种约定,可以将大脑从编程的繁琐细节中解放出来,尽管相应的回报很少。

好奇心

一旦承认自己的脑容量太小以至于无法充分理解大多数程序并且意思到有效编程才是想法补短,就意味着职业生涯规划和成长的开始。

理性诚实

编程生涯成熟的部分标志是养成一种 “理性诚实” 的品格。

  • 如果还不是专家,就要拒绝假装自己很内行
  • 闻过则喜
  • 试图理解编译器警告,而不是视而不见
  • 清楚地理解程序,而不是通过编译来查看它是否工作
  • 提供真实的状态报告
  • 提供切合实际的进度估计,并在管理层要求调整时立场坚定

懒惰

利用自己懒惰的性格,将一些烦人的工作变得自动化。

坚持

排查 Bug 时一段时间没有进展,就该放弃,让潜意识来发挥作用。想个其他法子把问题绕开,理清思绪后再做。和计算机错误斗气是不明智的。

习惯

好的习惯很重要,因为程序员做的大部分事情都是无意识下完成的。

关于软件匠艺

  • 编程的一个主要目标是管理复杂性。
  • 编程的过程对最终产品有着重大的影响。
  • 团队编程更多的是人与人之间的沟通而不是人和计算机的沟通。个人编程更多的是同自己沟通(思考)而不是和计算机。
  • 如果编程规范被滥用,可能会适得其反。如果能够谨慎而合理地使用,编程规范可以为开发环境增加有价值的结构并且有助于管理复杂性和促进沟通。
  • 面向问题而不是解决方案的编程有助于管理复杂性。
  • 重视 “质疑之源” 的警告信号再编程中特别重要,因为编程几乎是一种纯粹的智力活动。
  • 开发活动迭代次数越多,活动的产出就越好。
  • 教条主义方法论和高质量软件开发不能混为一谈。在我的 “智力工具箱” 中装备满各种不同的编程方法,并且提供技能选择合适的工具来完成。

征服复杂性

降低复杂度是软件开发的核心。

  • 在架构层面把一个系统分解成若干个子系统,以便一次只聚集于系统的一小部分。
  • 精心定义类的接口,以便能够忽略类的内部工作机制。
  • 类的接口保持抽象,以便无需记住所有细节。
  • 避免使用全局变量,因为全局变量会显著增加你需要兼顾的代码。
  • 避免深层次的继承,因为这需要耗费大量的精力。
  • 避免循环和条件的深度嵌套,因为其实它们能被简单的控制结构所替代,让我们少费些脑细胞。
  • 避免使用 goto 语句,因为它们引入了非顺序执行,大多数人都看不懂。
  • 小心定义错误处理的方法,不要滥用不同的错误处理技术。
  • 系统地使用内置的异常机制。因为一旦使用不当,会同 goto 语句一样引入非顺序执行,让人看不懂。
  • 不要让类过度膨胀,以至于占据整个程序。
  • 保持程序短小。
  • 变量命名要清楚直观,不需要浪费脑力去记住大量的细节。
  • 尽可能减少参数的数量,或者更重要的是,只传递足够以保持程序抽象的参数。
  • 使用规范来避免大脑去记忆代码不同部分之间随意、偶然的差异。

以功能来命名变量,可以清楚说明问题是 “什么”,而不是问题 “怎么” 实现,可以提升抽象级别。

优选开发过程

对于多个程序员参与的项目,组织因素的影响大于个人技能的影响。

开发过程决定着需求的稳定程度及其需要有多稳定。如果想需求更加灵活,可以使用增量开发的方法(即多个增量逐步)交付软件,而不是一次性交付。

有意关注过程这一原则同样适用于设计。

软件开发过程很重的主要原因是内建质量必须从头做起。

过早优化是另一种过程错误。

底层的过程也很重要。

关注大的过程和小的过程意味着停下来关注软件构建方式。

深入语言去编程,而不是用语言来编程

不要将编程的想象力局限于语言自动支持的范围。一流的程序员会深度思考自己的目标,然后评估如何利用手头的编程工具来达成目标。(先理解应该怎么做,再理解当前工具可以怎么做,是否接受缺陷。)

借助于规范来保持专注

规范是用于管理复杂性的知识工具。

面向问题域编程

另一种处理复杂性的特殊方式是在尽可能高的抽象层次工作。在高层抽象工作的一种方式是面向问题域的编程而不是面向计算机科学的解决方案。

当心落石

编程并不全是一门艺术,也不全是一门科学。它是介于艺术和科学之间的 “手艺”。

迭代,迭代,迭代,重要的事情说三遍

工程的一个定义是以最短的时间和最少的人力与物力做出有价值的东西。软件开发后期,迭代会有事倍功半的效果。

警惕编程中的执念

除了要完成的目标外,其它执念在某些时候都可以打破。

折中主义:如果想找到最有效的编程问题解决方案,盲目相信一种方法会使你失去选择。折中是一种有用的态度。

试验

试验可能是调优一段代码并对其进行基准测试,验证它是否真的更小或者更快。

心态开放的试验和墨守成规的执念,不可以混为一谈。(执念是指一些教条主义)