《代码整洁之道》读书分享

459 阅读5分钟

Clean Code

代码整洁之道读书笔记 Robert C.Martin 著,整理了部分原书中的内容,将部分例子以JavaScript翻译出来方便理解记忆

最重要的!命名技巧

1.1 名副其实的命名

花点时间取一个好名字,能省下来的时间比花掉的多,比如时间字段,应当选择指明了计量对象和计量单位的名称:

var elapsedTimeInDays
var daysSinceCreation
var daysSinceModification
var fileAgeInDays

再看如下代码

function getTime() {
  let list1 = []
  for(let i:array in lint1){
    if(i[0] === 4) {
      list1.add(x)
    }
    return list1
  }
} 

很难看出来上面代码做了什么事,也没有复杂的表达式,空格和缩进中规中矩,只有两三个变量,没有任何其他多的解释或者方法。

1.2 避免误导

必须避免留下掩藏代码本意的错误线索,应当避免与本意相悖的词,列如hp、aix、sco都不应该作为变量名,以为是简写其实没什么卵用。
如不要使用accountList来只称一组账号,除非他真的是List类型,否则就会引起错误的判断,所以用accountGroup或者bunchOfAccounts甚至直接用accounts都会好一些。
提防使用不同之处较小的名称。
以同样的方是拼写出同样的概念才是信息,拼写前后不一致就是误导。

1.3 区分的意义

如同一作用范围内两样不同的东西不能重名,你可能会随手改掉其中一个的名称,有时干脆就以错误的拼写充数,结果可能就会出现更正拼写错误后导致编译器出错的情况。即使能够运行,仅仅是添加数字系列或者是废话是远远不够的,如果名称不同,意思应该也不同才对。例如:

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

如果参数名改为source和destination,这样就会好很多。


另外废话是另外一种没有意义的区分,假如有一个Product对象,同时还有一个ProductInfo和ProductData对象,虽然名称不同,但是意思却没有多大的差别,就像a,an,the一样是意义混淆不清的废话。 如

getActiveAccount()
getActiveAccounts()
getActiveAccountInfo()

这三个函数,如果没有明确的约定就完全不知道调用的时候应该调用哪个。

1.4 名称可读

此可读不是计算机windows中的可读,而是真真正正能读出来,作为中国人,大部分的过于专业的单词名词等都没法读出来,因此在命名的时候就应该考虑出来是否可读并且解释,这样的变量、方法也更容易维护,第一眼看上去就能把他默读出来。
特别是不要使用一些自造词。

class DtaRcrd102 {
  var genumdhms
  var modymdhms
  var pszqint = '102'
}

class Customer {
  let generationTimeStamp
  let modificationTimeStamp
  let recordId = '102'
}

下边看起来就容易很多,很直白的理解了时间戳等

1.5 名称可搜索

单个字母名称和数值常量有问题的时候,很难在一大片文字中找出来,比如找MAX_CLASS_PER_STUDENT很容易,但是找数字7就很麻烦,特别是一长串常量数字,又被人修改过,找起来就会很难,从而逃过搜索造成错误。
取名建议:切以为单字母名称仅用于短方法中的本地变量。名称长短应与其作用域大小相对应。若变量或者常量在代码中多次使用,则应该赋予其便于搜索的名称。

for(let j = 0,j<34;j++>) {
  s += (t[j] *4)/5
}
//和
let realDaysPerIdealDay = 4
const WORK_DAYS_PER_WEEK = 5
let sum = 0
for(let j=0;j<NUMBER_OF_TASKS>;j++) {
  let realTaskDays = taskEstimate[j] + realDaysPerIdealDay
  let realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK)
  sum += realTaskWeeks
}

1.6 成员前缀

应当把类和方法等做的足够小,消除对成员前缀的需求,即尽量不使用成员前缀

1.7 避免思维映射

不应当让阅读代码的人把命名翻译为他们所熟知的名称。

1.8 类名

类名和对象应该是名词或者名词短语,如Customer,WikiPage,Account,AddressParser,避免使用Manager,Processor,Data或者Info这样的类名。
!!!类名不应当是动词

1.9 方法名

方法名应当是动词或者动词短语,如postPayMent,deletePage或者save。属性访问器,修改器和断言等应该根据其值来命名,并依标准加上get,set和is前缀。

var name = employe.getName()
customer.setName("mike")
if(paycheck.isPosted())...

重载构造器时,使用描述了参数的静态工厂方法名,例如:

Complex fulcrumPoint = Complex.FromRealNumber(23,0)
//通常好于
Complex fulcrumPoint = new Complex(23,0)

1.10 每一个概念对应一个词

给每一个抽象概念选一个词,并且一以贯之。 例如使用fetch,retrieve和get来给多个类中的同种方法命名,但是怎么记得住哪个类中是哪一个方法呢,这样会浪费大把的时间去浏览各个文件头和前面的代码。

1.11 别用双关语

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

1.12 使用解决方案领域的名称

用小白的话说,就是用易于理解的单词去替换晦涩难懂的专业性词汇,比如那些计算机科学术语,算法名,模式名,数学术语等,依据问题所涉猎的领域来命名会造成不必要的麻烦,取一个技术性的名称是最靠谱的做法。

1.13 添加有意义的语境

很少能有名称是能自我说明的,反之,你需要用有良好命名的类函数或者命名空间来放置名称,给阅读者提供语境。 给名称添加前缀是最次之的办法。 如下边的代码,函数名仅仅给出了部分语境,算法提供了剩下的部分。整个读完后,才知道number,verb和pluraModifier这三个变量是“测估”信息的一部分,不幸的是这些都得推断出来,第一眼看到这个方法的时候是完全不知道在说什么的

function printGuessStatistics(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 = Integer.toString(count)
    verb = 'are'
    pluralModifier = 's'
  }
  console.log(number,verb,pluralModifier)
}

上述的函数有点过长,变量的使用贯穿始终,为了干净利落可以这么写:

class GuessStatisticsMessage {
  let number
  let verb
  let pluralModifier

  function make(candidate,count) {
    creatPluralDependentMessageParts(count)
    let message = number + verb + pluralModifier
    return message
  }

  function creatPluralDependentMessageParts(count) {
    if(count === 0) {
      thereAreNoLetters()
    } else if (count === 1) {
      thereIsOneLetter()
    } else {
      thereAreManyLetters(count)
    }
  }

  function thereAreManyLetters () {
    //...
  }
  function thereIsOneLetter () {
    //...
  }
  function thereAreNoLetters () {
    //...
  }
}

函数的优化

2.1 短小

  1. 函数的长度 函数的第一规则是要短小,第二规则是还要更短小 ,函数最合适的行数在5行左右,这样看起来也方便,如果一个函数太长,显示器都塞不下,阅读起来将极其困难。
  2. if,else,while等语句,其中的代码块应该只有一行,这行大抵应该是一个函数的调用语句,这样不但能保持函数的短小,而且因为块内调用的函数拥有比较具有说明性的名称,从而增加了文档上的价值,这也意味着函数不应该大到足以容纳嵌套结构,所以函数的缩进层级不应该多于一层或者两层。

2.2 只做一件事

函数应该只做一件事,做好这件事,只做这一件事 问题在于很难知道那件事该做的事是什么,如果函数只是做了该函数下的同一抽象层上的步骤,则函数还是只做了一件事。编写函数毕竟是为了把大一些的概念拆分为另一个抽象层上的一系列步骤。 还有一个方法就是,要判断一个函数是否不止做了一件事,就是要看看能否再拆出来一个函数 ,该函数不仅是单纯的重新诠释其实现。

2.3 每一个函数一个抽象层级