【转】编写可读代码的艺术(一)

给Buddiee布置的寒假小作业,此文为Review的产出之一,它是原文的Markdown版本,仅供参考。
原文链接:http://www.jianshu.com/p/62d4654043cc


万事开头难,好久没有正经写过什么东西了,but什么东西都是逼出来的,buddy给我布置了寒假小作业,so,写写试试看喽。 (PS:提(qiao)笔(jian)写(pan)真(so)简单(difficult))

正题是编写可读代码的艺术,那么问题来了:

什么是可读代码(有什么是不能让人瞅的么)?

在IT公司里,程序猿们与程序媛们天天对着电脑敲了多少个英文字母,多少个标点符号,我们生产的这些代码可不是一次性的筷子,不是为了特立独行,张扬个性;相反,我们coding出来的代码是要让团队里面的人很快读懂,明白自己做了什么工作,什么是需要进一步修改的,什么是待完成的工作。总之,代码是写给自己,尤其是写给别人的,越容易让别人读懂的代码才能说是可读代码。


什么是艺术(难道是一身细菌么)?

上百度百科一搜就会发现:艺术,原是才艺和技术的统称,词义很广,后慢慢加入各种优质思想而演化成一种对美,思想,境界的术语。正是如此,在程序员的世界里,如何写出让人快速理解,轻松维护,容易扩展的代码,才能说是专业的程序员,才能称之为一门艺术。

基于以上概述,本文将从四个部分来与大家一同分享如何才能让写代码写的具有艺术性。


表面层次的改进(命名、注释以及审美)

正所谓表面层次之意,从代码的表皮入手,养成好的命名习惯,良好的注释(虽然有些看法是写代码写到不用注释最好,如果你能够让你的代码达到这种境界亦可)以及代码的整洁性都能够很好的提高代码的可读性,下面我们就从这三点与君共勉之。


如何取名字?(把信息装到名字里,选择不会误解的名字)

我们每个人都有属于自己的名字,长辈给我们取名字的时候会考虑很多因素,有的具有时代性(跃进,国庆等),有的具有现实性(金鑫,石磊等)。同样,我们写代码的时候如何给一个变量,一个类,一个方法选取一个合适的名字具有重要的意义:

记得大一刚开学学c语言的时候,我舍友大刚写的一段代码类似如下:

void  JiaFa(int a,int b){
    return a + b;
}

相信大家看到这段代码后,跟我有一样的想法:简单易懂,是求和运算啊。俺也是这么想的,但是就目前来看,大部分的计算机语言都是用英语描述的,因此我们取名字的时候应该选取更专业的名字来描述这个方法的行为。

同样滴,我经常在写代码的时候有时候在遇到一些临时性变量,会使用类似flagtmpretval等名字,那么类似这种词语的名字在什么情况下具有合理性呢?

情形1:

int tmp1 = getHeight();
int tmp2 = getWidth();
int retval = temp1 + tmp2;
return retval;

情形2:

if(right < left) {
      tmp  = right;
      right = left;
      left = tmp;
}

两种情形下,我们均使用了tmp这个临时变量,但是在情形1中如果我们将tmp1tmp2换成heightwidth是不是更好呢。

综上两种情形,我们需要明白:类似flagtmpretval等这一类名字只应用于短期存在且作用域鱼油临时性,反之,我们应该使用具体的名字来代替抽象的名字,而不是使用这种空泛的名字。


为名字附带更多信息

...
long starTime=System.currentTimeMillis();
...
long endTime=System.currentTimeMillis();
long countTime=endTime-starTime;

总的来看,上面这段代码很清晰,是用来计算某段程序的运行时间的,但是如果仔细想想,这里的时间单位是什么呢?是微秒,毫秒,还是秒呢。所以,在选取名字时我们应该为名字附带更多信息,让看到代码的人更加全秒的了解这个变量的意义。(可以考虑上面代码修改为:startTime_ms, endTime_ms, countTime_ms


名字应该有多长

如果你经常关注NBA,就会知道现在的雄鹿队里的老大(人家94年,身价上亿了,人比人,气死人),扬尼斯·阿德托昆博(Giannis Antetokounmpo),类似威少的新怪兽,他有个独特的外号就是“字母哥”,为什么这么说他呢,你仔细读读他的名字就知道了,名字太长,外国朋友都感觉不好读了。

同样的,我们提倡在取名字时应该为名字附带更多的信息,但是如果名字过长那就适得其反了,取名是为了更好的记住以便将来使用,那么什么时候可以选择简短的名字呢?

答案很简单,只要你保证别人看了以后明白这个简单名字的价值和意义就可以了,那么问题又来了,这个答案这么模棱另可,如何做到让别人很快理解呢?答案就是:在小的作用域里可以使用短的名字。

当你去短期度假时,你带的行李通常比长假少。同样,“作用域”小的标识符也不用带上太多信息。也就是说,因为所有的信息(变量的类型、它的初值,如何析构)都很容易看到,所以可以用很短的名字。

if(debug){
     map m;
     LookUpNamesNumbers(&m);
     Print(m);
}

尽管m这个名字并没有包含很多信息,但这不是个问题。因为读者已经有了需要理解这段代码的所有信息。

然而,假设m是一个全局变量中的类成员,如果你看到这个代码片段:

LooKUpNamesNumbers(&m);
Print(m);

这段代码就没有那么好读了,因为m的类型和目的都不明确。

因此如果一个标识符有较大的作用域,那么它的名字就要包含足够的信息以便含义更清楚。


选择不会误解的名字

记得在初中英语课上有两个单词是老师需要我们来分开理解的:except和besides,两个都有除...之外的意思,但是except是从整体中除去一部分,表示递减的概念,含义是否定的;而besides是在整体中加入一部分,表示递加的概念,含义是肯定的。

同样的道理,变量取名字时也很容易犯这样的错误:

results = Database.all_objects.filter("year <= 2011");

这是一段操作数据库结果的代码,但是我们知道现在具体的结果是什么呢?

  1. 年份小于或等于2011的对象?
  2. 年份不小于或等于2011你那的对象?

这里的问题是filter是个二义性单词,我们不清楚它的含义是“挑出”还是“减掉”,是“过滤掉呢”还是“选择出来”。因此,我们应该避免使用“filter”这样具有二义性的名字,防止引起歧义。

说起歧义,布尔变量只有true和false两种值,在给布尔值命名时也要特别注意名字的二义性,比如:

bool  read_password = true;

这会导致两种截然不同的解释:

  1. 我们需要读取密码。
  2. 已经读取了密码。

在这个例子中,最好避免使用read这个词,用user_is_authenticated这样的名字来代替会更好。

通常来讲,加上像ishascanshould这样的词,可以把布尔值变得更明确。

以上说了这么多命名的规范,总之还是为了成为更好的可读代码,用读者的角度来看自己的命名是否更合理,更简单易懂即可。


代码中的注释(该写什么样的注释,写出言简意赅的注释)

相信大家在平时写代码的时候一定写过注释,那么什么是注释呢?注释的目的又是什么呢?应该写什么样的注释呢?通常我们都会认为注释是为了解释代码做了什么,但是这只是其中很小的一部分,注释的目的是尽量帮助读者了解得和作者一样多。


为什么不需要注释

注释切记不要为那些从到代码本身就能快速推断的事实写注释,比如:

//The class defination for Account
class Accout{

      //Constructor
      public Accout(){
      }

      //Set the profit member to a new value
      void setProfit(double profit){
          ...
      }

     //Return the profit from this Accout
    double getProfit(){
         ...
     }
}

这些注释没有价值,因为它们没有提供任何新的信息,属于没有价值的注释,应该删掉。

同时,我们在写代码时应该注意不要为了注释而注释,不要给不好的名字注释(应该使用更好的名字来防止为了解释名字而写的注释),比如:

//Releases the handle for this key.This doesn't modify the actual registry.
void DeleteRegitstry(RegistryKey*  key);

DelteRegistry()这个名字听起来像是一个危险的函数(它会删除注册表?!)注释里的“它不会改动真正的注册表”是想澄清困惑。因此,可以去掉注释,将方法名字修改为:

void ReleaseRegistyHandle(Registry* key);

去掉注释的同时,也更好的描述了这个方法的行为,何乐而不为呢。

我们应该记住:好代码 > 坏代码+好注释


记录你的思想

  1. 注释中我们可以像看完电影后别人写影评一样,加入我们对这段代码的想法,为什么代码写成这样而不是那样的内在理由("指导性批注")
  2. 对于代码中存在的问题,使用像TODO(下一步需要做的),FIXME(已知的无法运行的代码),HACK(对一个问题不得不采用的比较粗糙的解决方案),XXX(危险!这里有重要的问题)等标记来提醒读者
  3. 对于代码中的一些常量,解释常量背后的故事,为什么是这个值

站在读者的立场上思考问题

  1. 预料到代码中哪些部分会让读者说:“啊?”并且给它们加上注释。
  2. 为普通读者意料之外的行为加上注释。
  3. 在文件/类的级别上使用“全局观”注释来解释所有的部分是如何一起工作的。
  4. 用注释来总接代码块,使读者不致迷失在细节中。

将更多的信息装入更小的空间里

  1. 让注释保持紧凑
  2. 避免使用不明确的代词
  3. 润色粗糙的句子
  4. 精确地描述函数的行为
  5. 用输入/输出例子来说明特别的情况
  6. 声明代码的意图
  7. 具名函数参数”的注释
  8. 采用信息含量高的词

代码整洁(审美)

爱美之心人皆有之,可能很多程序员都跟我一样,生活中呢很是糙,很时邋遢(怪不得单身的多),其实呢,在俺们内心还有很有讲究的,在写代码过程中,好的程序员都有着自己的洁癖。

“这个括号应该上下一致,在这边搁着”;
“这个空行要删掉”;
“快捷键快速格式化代码啊”;
“这一段要抽取出来,放在这里多乱啊”

等等等等,这些都是我在骚窝实习过程中切切实实遇到的,本来以为自己写代码挺有讲究的,谁知一山更比一山高,所有的这些都是为了更好的追求卓越,只有写出更好的代码才能对得起自己的职业,也是对读者的一种尊重。

下面就说一些简单的规范,共勉之:

1. 重新安排换行来保持一致和紧凑
2. 用方法来整理不规则的东西
3. 在需要时使用列对齐
4. 选一个有意思的顺序,始终一致地使用它
5. 把声明按块组织起来
6. 把代码分成“段落”
7. 个人风格与一致性

以上为我看《编写可读代码的艺术》第一部分的自己笔记,希望能提醒自己的同时与大家一同分享,后面将继续记录第二,三,四部分。

你可能感兴趣的:(【转】编写可读代码的艺术(一))