代码的规范,王垠,带你少走点弯路。

程序应该怎么写。

转载地址:http://www.cocoachina.com/programmer/20151125/14410.html?utm_source=tuicool&utm_medium=referral


1. 避免写太长的函数。如果发现函数太大了,就应该把它拆分成几个更小的。通常我写的函数长度都不超过50行,那正好是我的笔记本电脑屏幕所能容纳的代码的行数。这样我可以一目了然的看见一个函数,而不需要滚屏。50行并不是一个很大的限制,因为函数里面比较复杂的部分,往往早就被我提取出去,做成了更小的函数,然后从原来的函数里面调用。所以我写的函数大小一般远远不足50行。

有些人不喜欢使用小的函数,因为他们想避免函数调用的开销,结果他们写出几百行之大的函数。这是一种历史遗留的错觉。现代的编译器都能自动的把小的函数内联(inline)到调用它的地方,所以根本不产生函数调用,也就不会产生任何多余的开销。

同样的一些人,也爱使用宏(macro)来代替小函数,这也是一种历史遗留的错觉。在早期的C语言编译器里,只有macro是静态“内联”的,所以他们使用宏,其实是为了达到内联的目的。然而能否内联,其实并不是宏与函数的根本区别。宏与函数有着巨大的区别(这个我以后再讲),应该尽量避免使用宏。为了内联而使用宏,其实是滥用了宏,这会引起各种各样的麻烦,比如使程序难以理解,难以调试,容易出错等等。

2. 每个函数只做一件简单的事情。有些人喜欢制造一些“通用”的函数,既可以做这个又可以做那个,然后他们传递一个参数来“选择”这个函数所要做的事情。这种“复用”其实是有害的。如果一个函数可能做两种不一样的事情,最好就写成两个不同的函数,否则这个函数的逻辑就不会很清晰,容易出现错误。

避免使用continue和break。循环语句(for,while)里面出现return是没问题的,但是如果使用了continue或者break,就会让循环的逻辑和终止条件变得复杂,难以确保正确。如果只有一个continue或者break也许还好,但是如果你的循环语句里面出现了多个continue或者break,你就该考虑改写整个循环了。


出现continue或者break的原因,往往是对循环要执行的逻辑没有想得很清楚。因为如果你考虑周全了,应该是几乎不需要continue或者break的。改写循环的办法有多种,你也许可以把复杂的部分提取出来,做成函数调用,或者把它变成一个没有continue或者break的循环结构。

举一个例子。下面这段代码里面有一个continue:

1
2
3
4
5
6
7
8
List goodNames = new ArrayList<>();
for (String name: names) {
  if (name.contains("bad")) {
    continue;
  }
  goodNames.add(name);
  ...
}

它说:“如果name含有'bad'这个词,跳过后面的循环代码……” 注意,这是一种“负面”的描述,它不是在告诉你什么时候“做”一件事,而是在告诉你什么时候“不做”一件事。为了知道它到底在干什么,你必须搞清楚continue会导致哪些语句被跳过了,然后脑子里把逻辑反个向,你才能知道它到底想做什么。这就是为什么含有continue和break的循环不容易理解,它们依靠“控制流”来描述“不做什么”,“跳过什么”,结果到最后你也没搞清楚它到底“要做什么”。

其实,我们只需要把continue的条件反向,这段代码就可以很容易的被转换成等价的,不含continue的代码:

1
2
3
4
5
6
7
List goodNames = new ArrayList<>();
for (String name: names) {
  if (!name.contains("bad")) {
    goodNames.add(name);
    ...
  }
}

goodNames.add(name);和它之后的代码全部被放到了if里面,多了一层缩进,然而continue却没有了。你再读这段代码,就会发现更加清晰。因为它是一种更加“正面”地描述。它说:“在name不含有'bad'这个词的时候,把它加到goodNames的链表里面……”

再举一个例子:

1
2
3
4
5
6
7
8
9
10
public boolean hasBadName(List names) {
  boolean result = false;
  for (String name: names) {
      if (name.contains("bad")) {
          result = true;
          break;
      }
  }
  return result;
}

这个函数检查names链表里是否存在一个名字,包含“bad”这个词。它的循环里包含一个break语句。这个函数可以被改写成:

1
2
3
4
5
6
7
8
public boolean hasBadName(List names) {
  for (String name: names) {
      if (name.contains("bad")) {
          return true;
      }
  }
  return false;
}

改进后的代码,在name里面含有“bad”的时候,直接用return true返回,而不是对result变量赋值,break出去,最后才返回。如果循环结束了还没有return,那就返回false,表示没有找到这样的名字。使用return来代替break,这样break语句和result这个变量,都一并被消除掉了。

我曾经见过很多其他使用continue和break的例子,几乎无一例外的可以被消除掉,变换后的代码变得清晰很多。我的经验是,99%的break和continue,都可以通过替换成return语句,或者翻转if条件的方式来消除掉。剩下的1%含有复杂的逻辑,但也可以通过提取一个帮助函数来消除掉。修改之后的代码变得容易理解,容易确保正确。

另外,try { ... } catch里面,应该包含尽量少的代码。比如,如果foo和bar都可能产生异常A,你的代码应该尽可能写成:

1
2
3
4
5
6
try {
  foo();
catch (A e) {...}
try {
  bar();
catch (A e) {...}

而不是

1
2
3
4
try {
  foo();
  bar();
catch (A e) {...}

第一种写法能明确的分辨是哪一个函数出了问题,而第二种写法全都混在一起。明确的分辨是哪一个函数出了问题,有很多的好处。比如,如果你的catch代码里面包含log,它可以提供给你更加精确的错误信息,这样会大大地加速你的调试过程。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章