📒 《代码整洁之道》

131 阅读17分钟

代码整洁之道

有意义的命名

例如获取一个标记的单元格函数为例子:

Bad Code:

function getThem(theList) {
  const list1 = [];
  for (let i = 0; i < theList.length; i++) {
    if (theList[0] === 4) {
      list1.push(theList[i]);
    }
  }
  return list1;
}
  • theList 中什么类型的东西?
  • theList 零下标值有什么含义?
  • 值 4 的意义是什么?
  • 怎么使用返回的列表?

Good Code:

function getFlaggedCells(theList) {
  const flaggedCells = [];
  const STATUS_VALUE = 0;
  const FLAGGED = 4;
  for (let i = 0; i < theList.length; i++) {
    if (theList[STATUS_VALUE] === FLAGGED) {
      flaggedCells.push(theList[i]);
    }
  }
  return flaggedCells;
}

避免误导

  • 必须避免留下隐藏代码本意的错误线索。应当避免使用与本意相挬的词。

  • 提防使用外形相似度较高的名称。

  • 误导性名称真正可怕的例子就是用小写的字母 l 和 大写的字母 O 作为变量名。例如:

Bad Code:

if (O === l) {
  // TODO
}

做有意义的区分

  • 以数字系列命名(a1、a2、、、)是依义命名的对立面。这样的命名纯属误导--完全没有提供正确的信息,没有提供导向作者意图的线索。例如:

Bad Code:

function copyChars(a1,a2) {
  for (let index = 0; index < a1.length; index++) {
    a1[index] = a2[index]
  }
}

Good Code:

function copyChars(source, destination) {
  for (let index = 0; index < source.length; index++) {
    source[index] = destination[index];
  }
}
  • 废话是另一种没有意义的区分。例如:

Bad Code:

class Product {
  // TODO
}

class ProductData {
  // TODO
}

class ProductInfo {
  // TODO
}

名称虽然不同,但意义却无区别。Info 和 Data 就像 a、an、the 一样,是意义含混的废话。

  • 只要体现出有意义的区分,使用 a 和 the 这样的前缀就没有错。例如:

Good Code:

function test(the) {
  let a;
}
  • 要区分名称,就要以读者能鉴别不同之处的方式来区分。

使用读的出来的名称

Bad Code:

class DtaRcrd102 {
  genymdhms() {
    // TODO
  }
  modymdhms() {
    // TODO
  }
  pszqint = "102";
}

Good Code:

class Customer {
  generationTimestamp() {
    // TODO
  }
  modificationTimestamp() {
    // TODO
  }
  recordId = "102";
}

使用可搜索的名称

对于单字母名称和数字常量,很难在一大篇文字中找到。因此尽量使用符合意思的长名称。

窃以为单字母名称 用于短方法中的本地变量。

名称长短应与其作用域大小相对应

若变量或者常量可能在代码中多处使用,则应赋予其便于搜索的名称。

Bad Code:

for (let i = 0; i < 34; i++) {
  let s;
  s += (t[i] * 4) / 5;
}

Good Code:

let realDaysPerIdealDay = 4;
let sum = 0;
const NUMBER_OF_TASKS = 34;
const WORK_DAYS_PER_WEEK = 5;

for (let index = 0; index < NUMBER_OF_TASKS; index++) {
  let realTaskDays = taskEstimate[index] * realDaysPerIdealDay;
  let realTaskWeeks = realTaskDays / WORK_DAYS_PER_WEEK;
  sum += realTaskWeeks;
}

禁止成员前缀

成员前缀完完全全就是废话。

Bad Code:

class Part {
  m_dsc;
  setName(name) {
    this.m_dsc = name;
  }
}

Good Code:

class Part {
  description;
  setDescription(description) {
    this.description = description;
  }
}

避免映射思维

不应当让读者在脑中把你的名称翻译为他们熟知的名称,这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语时。

单字母变量名就是个问题,在作用域较小、没有名称冲突时,循环计数器自然有可能被命名为 i、j、k 之类的。但在多数情况下,单字母名称不是一个好的选择。因为这样还需要在脑海中将它们映射成真实的概念,最好能见名知意,不需要思考。

类名

类名和对象名称应该是名词或者名词短语,例如:Customer、WikiPage、Account、AddressParser。应该避免使用类似于 Manager、Processor、Data、Info 这样的类名。

类名和对象名称不应该是动词。

方法名

方法名称应该是动词或者动词短语。

例如:postPayment、deletePage、save。属性访问器(accessor)、修改器(mutator)、断言(predicate)应该根据其值命名,并依照标准加上前缀 get、set、is。

每个概念对应一个词

给每个抽象概念选一个词,并且一以贯之。例如,使用 fetch、retrieve、get 来给在多个类中的同种方法命名。

避免双关语

避免将同一单词用于不同的目的。同一术语用于不同概念基本就是双关语。

使用源自所涉问题领域的名称

如果不能使用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称。

添加有意义的语境

很少有名称是能自我说明的--大多数都不能,我们需要用命名良好的类、函数名、名称空间来放置名称,给读者提示。

如果没这么做,给名称添加前缀就变成了最后一招。

Bad Code:

function printGuessStatistics(char, candidate, count) {
  let number;
  let verb;
  let pluralModifier;
  if (count === 0) {
    number = "no";
    verb = "are";
    pluralModifier = "s";
  } else if (count === 1) {
    number = "1";
    verb = "is";
    pluralModifier = "";
  } else {
    number = count.toString();
    verb = "are";
    pluralModifier = "s";
  }
  return `There ${verb} ${number} ${candidate} ${pluralModifier}`;
}

拆分这个函数变为类。

Good Code:

class GuessStatisticsMessage {
  number;
  verb;
  pluralModifier;
  constructor(char, candidate, count) {
    this.char = char;
    this.candidate = candidate;
    this.count = count;
  }

  make(char, candidate, count) {
    this.createPluralDependentMessageParts(count);
    return `There ${verb} ${number} ${candidate} ${pluralModifier}`;
  }

  createPluralDependentMessageParts(count) {
    if (count === 0) {
      this.thereAreNoLetters();
    } else if (count === 1) {
      this.thereIsOneLetter();
    } else {
      this.thereAreManyLetters();
    }
  }

  thereAreManyLetters(count) {
    this.number = count.toString();
    this.verb = "are";
    this.pluralModifier = "s";
  }

  thereIsOneLetter() {
    this.number = "1";
    this.verb = "is";
    this.pluralModifier = "";
  }

  thereAreNoLetters() {
    this.number = "no";
    this.verb = "are";
    this.pluralModifier = "s";
  }
}

不要添加没用的语境

只要短名称足够清楚,就比长名称好。别给名称添加不必要的语境。

函数

短小

函数的第一条规则是要短小。

第二条规则是还是要更短小。

最好一个函数维持在 6 行左右即可。

代码块和缩进

if 语句、else 语句、while语句等,其中的代码块应该只占一行,该行大抵应该是一个函数调用语句。

只做一件事

函数应该做一件事。做好这件事。只做这一件事。

函数应该做一件事。做好这件事。只做这一件事。

函数应该做一件事。做好这件事。只做这一件事。

判断函数是否不止做了一件事,就是看它是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。

每个函数一个抽象层级

  • 自顶向下读代码:向下规则

最好的代码理想状态为: 每个函数后面都跟着位于下一抽象层级的函数,这样一来,在查看函数列表时,就能循环抽象层级向下阅读了。-- 这就是向下规则

换一种说法: 程序就像是一系列 TO 起头的段落,每一段都描述当前抽象层级,并引用位于下一抽象层级的后续 TO 起头段落。

这是保持函数短小,确保只做一件事的要诀。

switch 语句

使用具有描述性的名称

命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。

例如: includeSetupAndTeardownPages、includeSetupPages、、、、

函数参数

  • 最理想的参数数量是 0 (零参数函数),其次是 1 (单参数函数),再次是 2 (双参数函数),应尽量避免 3 (三参数函数)

  • 不要将布尔值作为函数的参数传递。因为这样就违反了单一原则。

  • 当参数较多时,可以考虑将参数写成对象的形式。

分隔指令与询问

函数要么做什么事,要么回答什么事,二者不可得兼。

抽离 try/catch 块

try/catch 代码块丑陋不堪。它们搞乱了代码的结构,把错误处理与正常流程混为一谈。

最好把 try/catch 代码块的主体部分抽离出来,另外形成函数。

Good Code:

function delete (page){
  try {
    deletePageAndAllReferences(page);
  } catch (error) {
    logError(error);
  }
}

function deletePageAndAllReferences(page) {
  deletePage(page);
  CustomElementRegistry.deleteReference(page.name);
}

function logError(error) {
  console.log(error);
}

错误处理就是一件事

函数应该只做一件事,错误处理就是一件事。因此,处理错误的函数不应该做其他的事。

并且 catch/finally 代码块后面也不应该有其他内容。

别重复自己

重复可能是软件中一切邪恶的根源。许多原则与实践规则都是为控制与消除重复而创建的。

因此不要书写重复的代码。

如何写出好的函数

先写完,在优化。没人能一边写的很完美。

注释

我们在改变代码的时候并会每次都会更新注释。因此时间越长注释越不精确。

尽管程序员要负责将注释保持在可维护、有关联、精确的高度。但是更加需要花力气用在写清楚代码上面,这样可以直接保证无需编写注释。

真实只在一处地方存在--代码。只有代码能忠实的得告诉你它做的事情。虽然有时需要注释,但还是得花心思减少注释。

注释不能美化糟糕的代码

写注释常见的动机之一就是有糟糕的代码存在。

带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样得多。

与其花时间写注释,不如花时间整理糟糕的代码。

用代码来阐述

Bad Code:

// Check to see if the employee is eligible for full benefits
if (employee.flags & HOURLY_FLAG && employee.age > 40) {
  // TODO
}

Good Code:

if (employee.isEligibleForFullBenefits()) {
  // TODO
}

好注释

有些注释是必需的,也是有利的。不过还是要记住,唯一真正好的做法是你想办法不去写注释。

当编写现有的代码必需存在注释的时候,切注释有一大段,最好的添加注释的方式指向一份标准许可或其他外部分档。

Bad Code:

//Return an instance of the Responder being tested
responderInstance();

这类注释有时管用,但更好的方式是尽量利用函数名称传达信息。

Good Code:

responderBeingTested();

有时,注释把某些晦涩难明的参数或返回值的意义翻译为某种可读形式,也是有用的。通常,更好的方法是尽量让参数或返回值本身就足够清楚。

但如果参数或返回值是某个标准库的一部分,或是你不能修改代码,帮助阐述其含义的代码就会有用。

有时,用于警示其他程序员可能会出现某种后果的注释也是有用的。

有时,有理由用 // TODO 形式在源代码中放置要做的工作列表。但是时候一定要及时的消灭要 TODO 的事情。它不是在系统中留下糟糕代码的接口。

坏注释

大多数注释都属此类。通常,怀注释都是糟糕的代码的支撑或借口,或者对错误决策的修正,基本上等于程序员自说自话。

如果只是因为你觉得应该或者因为过程需要就添加注释,那就是无谓之举。如果你决定写注释,就要花必要的时间确保写出最好的注释。

不要书写多余的注释。

不要书写误导性的注释。

所谓每个函数都要用注释或每个变量都要有注释的规矩全然是愚蠢可笑的。这类注释徒然让代码变得散乱,满口胡言,令人迷惑不解。

用整理代码的决心来替代创造废话的冲动,这样你就会发现自己将成为更加优秀、更加快乐的程序员。

能用函数或变量时就别用注释

Bad Code:

// does the module from the global list <mod> depend on the
// subsystem we are oart of?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())) {
  // TODO
}

Good Code:

moduleDependees = smodule.getDependSubsystems();
ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem)) {
  // TODO
}

位置标记,尽量少用标记栏,只在特别有价值的时候用。如果滥用标记栏,就会沉没在背景噪音中而被忽略。

注释掉的代码要及时的删除掉,如果代码依然还在那,就会给读者造成误解。最后的结果就是别人也不敢删除这个问题。从而导致非常的讨厌。

在 JS 代码中书写 HTML 注释是非常不合适的。应该避免这种情况的发生。

如果一定要写注释,请确保它描述了离它最近的代码,别再本地注释的上下文环境中给出系统级的信息。

别再注释中添加有趣的历史性话题或者无关的细节描述。

注释及其描述的代码之间的联系应该显而易见。如果你不嫌麻烦的要写注释,至少让读者能看到注释和代码,并且理解注释所谈何物。

短函数不需要太多描述。为只做一件事的短函数选个好名字,通常要比写函数头注释好。

格式

  • 关系密切的概念应该互相靠近,除非有很好的理由,否则就不要把关系密切的概念放到不同的文件中。

  • 变量声明 - 变量声明应该尽可能靠近其使用的位置。

  • 实体变量 - 实体变量应该在类的顶部声明。

  • 相关函数 - 若某个函数调用了另外一个,就应该把它们放到一起,而且调用者应该尽可能放在被调用者的上面。

  • 概念相关 - 概念相关的代码应该放到一起。代码的相关性越强,彼此之间的距离就该越短。

对象和数据结构

  • 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。

  • 面向对象代码便于在不改动既有函数的前提下添加新类。

德墨忒耳律

德墨忒耳律人为,类 C 的方法 f 只应该调用一下对象的方法:

  • C;

  • 由 f 创建的对象

  • 作为参数传递给 f 的对象

  • 由 C 的实体变量持有的对象

方法 不应 调用由任何函数返回的对象的方法。

错误处理

  • 当遇到错误时,最好抛出一个异常,而不是直接抛出返回码。

边界

类应该短小,根据不同的 权责 拆分不同的类。

类的名称应当描述其权责,实际上,命名正是帮助判读类的长度的第一个手段。

  • 单一权责原则

单一权责原则(SRP)认为,类或者模块应该有且只有一条 加以修改的理由

类只应该有一个权责 - 只有一条修改的理由。

系统应该由许多短小的类而不是少量巨大的类组成,每个小类封装一个权责,只有一个修改的原因,并且与少数其他类一起协同达成期望的系统行为。

  • 内聚

类应该只有少数实体变量。类中的每个方法都应该操作一个或多个这种变量。这就是高内聚的原则。

内聚性高,意味着类中的方法和变量互相依赖、互相结合成一个逻辑整体。

系统

将系统的构造与使用分开

软件系统应将起始过程和起始过程以后的运行时逻辑分离开,在起始过程中构建应用对象,也会存在互相纠缠的依赖关系。

单元测试就是将函数的所有逻辑分支都检查一遍。

依赖注入

依赖注入: 可以实现分离构造和使用。

控制反转是依赖管理中的一种应用手段。控制反转将第二权责从对象中拿出来,转移到另一个专注于此的对象中,从而遵循 单一权责原则

在依赖管理情境中,对象不应该负责实体化对自身的依赖,反之,它应该当将这份权责移交给其他”有权利“的机制,从而实现控制的反转。

迭进

Kent Beck 关于简单设计的4条原则:

  • 运行所有测试。
  • 不可重复。
  • 清晰表达程序员的意图。
  • 尽可能减少类和方法的数量。

在重构过程中要保证,提高内聚性、降低耦合度、切分关注面,模块化系统性关注面,缩小函数和类的尺寸,选用更好的名称。

总之要消除重复、保证表达力、尽可能减少类和方法的数量。

重复是拥有良好设计的系统大敌。它代表这额外的工作、额外的风险和额外且不必要的复杂度。

表达力

  • 可以通过选用好名称来增强表达力
  • 通过保证函数和类的尺寸短小来增强表达力
  • 通过采用标准命名法来增强表达力
  • 通过编写良好的单元测试来增强表达力

做到有表达力的最重要方式就是尝试。

尽管使类和函数的数量尽量少是很重要,但更重要的却是测试、消除重复和表达力。

优秀的软件设计,大都关乎分隔--创建合适的空间放置不同种类的代码。对关注面的分隔让代码更易于理解和维护。

总结

注释

  • 不恰当的信息

让注释传达本该更好地在源代码控制系统、问题追踪系统或任何其他记录系统中保存的信息,是不恰当的。注释只应该描述有关代码和设计的技术性信息。

  • 废弃的注释

过时、无关或不正确的注释就是废弃的注释。注释会很快过时。最好别编写将被废弃的注释。如果发现废弃的注释,最好尽快删除。

  • 冗余的注释

如果注释描述的是某种充分自我描述了的东西,那么这个注释就是多余的。

Bad Code:

 i++; // i 自增

另外的就是除函数签名之外什么也没说(或少说)的注释。

Bad Code:

 /**
 * @param {*} sellRequest
 * @return {*}
 */
function beginSellItem(sellRequest) {
  // TODO
}

注释应该谈及代码自身没提到的东西。

  • 糟糕的注释

值得编写的注释,也值得好好写。如果编写一条注释,就花时间保证写出最好的注释,字斟句酌,使用正确的语法和拼写,别闲扯,别画蛇添足,要保持简洁。

  • 注释掉的代码

看到注释掉的代码,就 删除 它!这种注释是代码的 厌物

函数

  • 过多的参数

函数的参数应该少。没有参数最好,一个次之、两个、三个在次之。三个以上的参数非常值得质疑,应该坚决避免。

  • 输出参数

输出参数违反直觉,因为读者期望参数用于输入而非输出。如果函数非要修改什么东西的状态,就修改它所在的对象的状态好了。

  • 标识参数

布尔值的参数宣告函数做了不止一件事情。它们令人迷惑,应该被消灭掉。

  • 死函数

永远不被调用的函数应该被丢弃掉。保留死函数纯属浪费。

一般性问题

  • 一个源文件中存在多种语言

现代编程环境允许在单个源文件中存在多种不同的语言。但是理想的源文件应该包括且只包括一种语言。应该尽力缩小源文件中额外语言的数量和范围。

  • 明显的行为未被实现

遵循 ”最小惊异原则“ ,函数或类应该实现其他程序员有理由期待的行为。也就是说,函数应该返回函数名所描述的值,或者做函数描述所描述的事情。

  • 不正确的边界行为

代码应该有正确的行为。没什么可以替代谨小慎微。每种边界条件、每种极端情形、每个异常都代表了某种可能搞乱优雅而直白的算法的东西。 别依赖直觉。追索每种边界条件,并编写测试。

  • 忽视安全

忽视安全相当危险。关闭某些编译器警告(或者全部警告!)可能有助于构建成功,但可能会陷于无穷无尽的调试中。

  • 重复

消除重复的代码,并且把它们的逻辑提取出来复用,这个是一个很重要的原则。

  • 在错误抽象层级上的代码

创建分离较高层级一般性概念与较低层级细节概念的抽象模型。有时我们创建抽象类来容纳较高层级概念,创建派生类来容纳较低级层级概念。这样做的时候,需要确保分离完整。

所有较低层级概念放到派生类中,所有较高层级概念放到基类中。

  • 基类依赖派生类

将概念分解到基类和派生类的最普遍的原因是,较高层级基类概念可以不依赖较低层级派生类概念。通常来说,基类对派生类应该一无所知。

  • 信息过多

设计良好的模块有着非常小的接口,让你事半功倍。设计低劣的模块有着广阔、深入的接口,会让你事倍功半。

优秀的软件开发人员要学会限制类或模块中暴露的接口数量。类中的方法越少越好,函数知道的变量越少越好,类拥有的实体变量越少越好。

  • 死代码

死代码就是不执行的代码。通常出现在不会发生的条件 if 语句中找到、在不会抛出异常的 try/catch 中找到、在不会被调用的小工具中找到、在永远不会发生的 switch/case 中找到。

如果找到了死代码就立马删除。

  • 垂直分隔

变量和函数应该在靠近被使用的地方定义。本地变量应该正好在其首次被使用的位置上面声明,垂直距离要短。本地变量不该在距离其使用之处几百行以外的位置声明。

私有函数应该刚好在其首次被使用的位置下面定义。

  • 前后不一致

从一而终。这可以追溯到最小惊异原则。小心选择约定,一旦选中,就小心持续遵循。

如果在特定函数中用名为 response 的变量来持有 HttpServletResponse 对象,则在其他用到 HttpServletResponse 对象的函数中也用同样的变量名。

如此简单的前后一致,一旦坚决贯彻,就能让代码更加易于阅读和修改。

  • 混淆视听

没有用到的变量、从不调用的函数、没有信息量的注释、等等,这些都是应该移除的废物。保持源文件整洁,组织良好,不被搞乱。

  • 人为耦合

不互相依赖的东西不该耦合。

一般来说,人为耦合是指两个没有直接目的的模块之间的耦合。其根源是将变量、常量、或函数不恰当地放在临时方便的位置。

  • 特性依恋

类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其他类中的变量和函数。

当方法通过某个其他对象的访问器和修改器来操作该对象内部数据时,它就依恋该对象所属类的范围。它期望自己在那个类里面,这样就能直接访问它操作的变量。

  • 选择算子参数

通俗来说,算子参数就是函数根据不同的参数在函数中执行不同的逻辑,产出不同的结果。这是一种偷懒的行为!应该禁止!

选择算子不一定是 boolean 类型,也可以是枚举元素、整数或任何一种用于选择函数行为的参数。使用多个函数,通常优先于向单个函数传递某些代码来选择函数的行为。

  • 晦涩的意图

代码要尽可能具有表达力。

  • 位置错误的权责

代码应该放在读者自然而然期待它所在的地方。

例如: PI 常量应该出现在声明三角函数的地方。

  • 不恰当的静态方法

通常应该倾向于选用非静态方法。

  • 使用解释性变量

让程序可读的最有力方法之一就是将计算过程打散成在用有意义的单词命名的变量中放置的中间值。

Good Code:

const match = headerPattern.matcher(line);
if (match.find()) {
  const key = match.group(1);
  const value = match.group(2);
  Headers.put(key.toLowerCase(), value);
}
  • 函数名称应该表达其行为

  • 把逻辑依赖改为物理依赖

如果某个模块依赖另一个模块,依赖就该是物理上的而不是逻辑上的。依赖者模块不应该对被依赖者模块有假定(换言之,逻辑依赖),他应当明确地询问后者全部信息。

通俗的讲,就是在写模块时不应该和业务耦合,应该以写通用的思想去写模块。

  • 使用多态替代 IF/Else 或 Switch/Case

  • 遵循标准约定

  • 用命名常量替代魔术数

例如:

 const AGE = 25;
  • 准确

  • 封装条件

如果没有 if 或 while 语句的上下文,布尔逻辑就难以理解。应该把解释了条件意图的函数抽离出来。

Bad Code:

if (timer.hasExpired() && !timer.isRecurrent()) {
  // TODO
}

Good Code:

if (shouldBeDeleted(timer)) {
  // TODO
}
  • 避免否定性条件

否定式要比肯定式难明白一些。所以,尽可能将条件表示为肯定形式。

Good Code:

if (buffer.shouldCompact()) {
  // TODO
}

Bad Code:

if (!buffer.shouldNotCompact()) {
  // TODO
}
  • 函数只该做一件事

  • 掩蔽时序耦合

常常有必要使用时序耦合,但我们不应该掩蔽它。排列函数参数,好让它们被调用的次序显而易见。

Bad Code:

function dive() {
  saturateGradient();
  reticulateSplines();
  diveForMoog();
  // ...
}

Good Code:

function dive() {
  const gradient = saturateGradient();
  const splines = reticulateSplines(gradient);
  diveForMoog(splines);
  // ...
}

这样就可以通过创建顺序队列暴露了时序耦合。每个函数都产生出下一个函数所需的结果。

虽然增加了一些复杂度,但和真正的时序复杂性比起来还是值得的。

  • 封装边界条件

边界条件难以追踪。把处理边界条件的代码集中到一起,不要散落于代码中。

Bad Code:

if (level + 1 < tags.length) {
  parts = new parseFloat(body, tags, level + 1, offset + endTag);
  body = null;
}

Goode Code:

const nextLevel = level + 1;
if (nextLevel < tags.length) {
  parts = new parseFloat(body, tags, nextLevel, offset + endTag);
  body = null;
}
  • 函数应该只在一个抽象层级上

  • 在较高层级放置可配置数据

如果有一个已知并该在较高抽象层级的默认常量或配置值,不要将它埋藏到较低层级的函数中。把它作为较高层级函数调用较低层级函数时的一个参数。

位于较高层级的配置性常量易于修改,它们向下贯穿应用程序。应用程序的较低层级并不拥有这些常量的值。

名称

  • 采用描述性名称

不要太快起名。确认名称具有描述性。事物的意义随着软件的演化而变化,所以要经常性地重新估量名称是否恰当。

Bad Code:

function x() {
  let q = 0;
  let z = 0;
  for (let kk = 0; kk < 10; kk++) {
    if (l[z] == 10) {
      q += 10 + (l[z + 1] + l[z + 2]);
      z += 1;
    } else if (l[z] + l[z + 1] == 10) {
      q += 10 + l[z + 2];
      z += 2;
    } else {
      q += l[z] + l[z + 1];
      z += 2;
    }
  }
  return q;
}

Good Code:

function score() {
  let score = 0;
  let frame = 0;
  for (let frameNumber = 0; frameNumber < 10; frameNumber++) {
    if (isStrike(frame)) {
      score += 10 + nextTwoBallsForStrike(frame);
      frame += 1;
    } else if (isSpare(frame)) {
      score += 10 + nextBallsFrame(frame);
      frame += 2;
    } else {
      score += twoBallsInFrame(frame);
      frame += 2;
    }
  }
  return score;
}
  • 名称应与抽象层级相符

不要起沟通实现的名称,而起反映类或函数抽象层级的名称。

  • 尽可能使用标准命名法

如果名称基于即存约定或用法,就比较易于理解。

  • 为较大作用范围选用较长名称

名称的长度应与作用范围的广泛度相关。对于较小的作用范围,可以用很短的名称,而对于较大的作用范围,就该用较长的名称。

例如:类似 i、j 等之类的变量名对于作用范围在5行之内的情形没有问题。在 for 循环中。

  • 避免编码

不应在名称中包括类型或作用域范围信息。

  • 名称应该说明副作用

名称应该说明函数、变量或类的一切信息。不要用名称掩蔽副作用。

Bad Code:

function getOos() {
  if (m_oos == null) {
    m_oos = new objectOutputStream(m_socket.getOutputStream());
  }
  return m_oos;
}

该函数不止是获取一个 oos,如果 oos 不存在 还会创建一个 oos。因此,这就是名称没有说明副作用

Good Code:

function createOrReturnOos() {
  if (m_oos == null) {
    m_oos = new objectOutputStream(m_socket.getOutputStream());
  }
  return m_oos;
}