编写可读代码艺术之表面层析

前言

4年前,我拒绝自己承认程序员,那时在8位MCU上用C语言处理ROM芯片时序问题。

20120831101943671

1年前,我不承认自己是一个程序员,那时我在处理工业相机返回的三维数据。

clip_image002

现在,我不得不承认乐于去成为自己是一个程序员、工程师,程序员几乎无所不能,虽然很苦逼。程序员就要干程序员事,这篇就算小铺开张吧,写的不好,多多原谅。

clip_image002[7]

如果说自己的编程历史,07年我在用C语言,09年我在用Verilog,11年我开始用C++。不管什么时候、什么语言,我对自己的基本要求是:

代码要有注释,代码架构要清晰。

但是在我看完《the art of readable code》 之后,我对“清晰代码”的认知可能发生了一下改变,这本书其实很罗嗦,因为讲的一些东西可能是每个程序员每天都在做的事情,但是和《clean code》一样,每次看完可能都会有一些收获。

clip_image002[9]

    说实话,我经历过几个公司都在要求代码在规定时间内完成什么功能,但是没有人去要求你或者告诉你应该怎么写代码之类的,可能你也是其中的一员,代码的可读性或者说清晰,只能自己来要求自己来完成。我用了一个周末来把这本书加上自己的一些见解搬到博客上来,就是希望有更多的人去思考一些代码可读性,让自己对周围的众屌丝更加的亲和。

      作者在书的开始就阐明在这本书的基本原则:代码的写法应当使别人理解它所需的时间最小化。声明一点:代码的可读性如果可以从机器和人两个角度来理解,机器当然优先,每个程序员都想把代码的效率提高上去,但是我们每天处理的很多事情其实主要是顶层逻辑,大多可能是类似于大型游戏这样的多人协同开发,这个时候牺牲一点顶层逻辑的效率而提高人的合作效率,明显是更高明的选择,这也就是本书或者类似的书出现的原因。

      这篇文章主要从表面层析进行介绍,后续会对结构方面阐述。作者对“避免使用像tmp和retval这样泛泛的名字“,”用具体的名字替换抽象的名字“,”为名字附带更多信息“,”利用名字的格式来传递含义“等等进行讨论,不过我个人理解为: 如何让名字传递更多的意义,让阅读者马上就能明白代码要传递的意义?可以分为两层来看,一层就是作者用很大篇章描述的字面意思,选用更专业、更有表现力、不容易被误解的、带有附加信息的名字,第二层就是名字的格式问题,类似于匈牙利标示法等等.

1 选择更专业、更具表现力的词汇,而不使用泛泛而谈的名字

比如,"Get"  这个词汇,只要是获取我们首先想到的就是这个词,下面的语句:

void  GetPage(url)

表面意思理解就是“获取页面”,但是"Get"除了获取之外,你看打不到更多的意思,有可能是从数据库,也有可能是从互联网?如果是从互联网你可以使用更专业的词汇:DownLoad,如果是 void DownLoadPage(), 阅读者是不是更清晰呢?Opencv中有一个从摄像机获取函数的名字 void GrabImage() ,   是不是比GetImage()更能表达从设备回去一帧图像的意思呢?

英文其实和中文一样,有很多同义词,但是每个词又有其独特的意义,比如下面作者列举出来的:

send -> deliver, dispatch, announce, distribute, route
find -> search, extract, locate, recover
start -> lanuch, create, begin, open
make -> create,set up, build, generate, compose, add, new

小伙伴们还是学好英语吧~~

2 给使用temp、retval等空泛名字一个理由

在c++、c#等类似的语言已经对变量有效范围作了很好的处理,所以很多工程师已经意识到在命名空间、类等全局中和在一个for循环中对变量的命名是不一样的,在小的循环中还是很喜欢用这种泛泛的名字,比如下面:

for( int i = 0 ;i < 10 ; ++i )
{
        int temp = i ;
}

在这个循环之外,i,temp已经没有作用域,所以没有太多的误解存在,但是类似于下面的多重循环可能就要考虑下变量的意义

for (int i = 0; i < clubs.size(); i++)
{
   for (int j = 0; j < clubs[i].members.size(); j++)
  {
        for (int k = 0; k < users.size(); k++) if (clubs[i].members[k] == users[j])
              cout << "user[" << j << "] is in club[" << i << "]" << endl;
   }
}

3 为名字附加更多的信息

名字应该更加直接的描述变量或者方法的作用,阅读后马上知道这个名字背后是要做什么,比如变量的作用域、变量作用等等,作者给出了几条建议:

(1) 增加重要细节,比如对于一些带有物理性质的变量,在变量后面加上单位更直观:

Start(int delay) --> delay → delay_secs                    //时间单位

 CreateCache(int size) --> size → size_mb               //字节单位

ThrottleDownload(float limit) --> limit → max_kbps   //下载速度单位

 Rotate(float angle) --> angle → degrees_cw          //角度单位

类似的,除了单位方面外,可以增加更多具体的细节:

password -> plaintext_password          //文本需要加密才能使用

 comment -> unescaped_comment      //注释需要转义之后才能显示

 html -> html_utf8                                 //html格式

data -> data_urlenc                            //数据输入格式

(2)附带其他属性,最有名的莫过于匈牙利命名法,通过类型前缀就可以给出变量的类型信息,虽然这个东西争议很大,包括微软现在在C#中也开始使用Pascal和Camel混合的命名方式,不过对于我这样从VC开始写程序的人而言,匈牙利还是印象深刻,比如下面:

m_

Data member of a class

一个类的数据成员

msg

message

消息

p

Pointer

指针

s

string

字符串型

(3) 对于名字的长短问题,作者指出:在比较小的作用域内,可以使用较短的变量名,在较大的作用域内使用的变量,最好用长一点的名字,编辑器的自动补全都可以很好的减少键盘输入。对于一些缩写前缀,尽量选择众所周知的(如str),一个判断标准是,当新成员加入时,是否可以无需他人帮助而明白前缀代表什么。

(4)利用名字的格式来传递含义,比如字母大小写、下划线等。这个在很早之前就用到,静态:s_Xxx,    全局:g_Xxx, 类变量m_Xxx,指针:pXxx;

4  使用不会误解的名字

作者这里的意图更偏向于程序员内部的默认规则,当你写程序的时候不应该去挑战这个规则,从而引发一些不必要的麻烦。

(1)命名最清楚的方式是在要限制的东西前面加上max_,min_

CART_TOO_BIG_LIMIT = 10
 if shopping_cart.num_items() >= CART_TOO_BIG_LIMIT:
 Error("Too many items in cart.")
 

// 替换
MAX_ITEMS_IN_CART = 10
 if shopping_cart.num_items() > MAX_ITEMS_IN_CART:
 Error("Too many items in cart.")

(2)用first,last表示包含的范围,用start,end表示包含排除范围

(3)bool值明确意义,建议加上ishascanshould这样的词汇,这个有又要说,我一般是使用is前缀,不过这里给出的几个词汇就更加具体

SpaceLeft() --> hasSpaceLeft()
bool disable_ssl = false --> bool use_ssl = true

(4)函数名应当不带歧义,并且符合大多数程序员的期望,比如get().size(),肯定是轻量级别的

5 审美(整体格式

(1)基本原则:使用一致的布局,让阅读者很快去适应这种风格;相似的代码看上去相似;相关代码分组,形成代码块

(2)重新安排换行来保持一致和紧凑,占用更多的行列,不见得是好事哈

(3)用方法来整理不规则的东西,比如使用函数替代大量的重读代码,让函数本身去处理主要负责的事情

(4)在需要的时候使用列对齐,比如同一个方法的连续调用,数组的初始化等等

(5)选用一个有意思的顺序,推荐原则:最重要到最不重要,按字母顺序,和HTML表单循序匹配等等

(6)把声明或者代码组织成段落,相似功能或者想法的放在一起

6 一致行

     最重要的:一致的风格比“正确”的风格更重要。我把这个单独列出为一条,相对而言,风格没有所谓的对与错,但是对于团队而言一致的风格更容易交流、沟通。

7 注释

      看完这章之前,我的代码基本是充满注释,习惯性了已经,但是这本书给我最大的冲击就是这里,无用的注释其实是徒劳,只会增加阅读者的反感和难度,我的注释经常就像下面的,所以作者说:好代码 > 坏代码 + 注释

// The class definition for Account 
class Account 
{ 
public: 
// Constructor 
Account(); 
// Set the profit member to a new value 
void SetProfit(double profit); 
// Return the profit from this Account 
double GetProfit(); 

};

(1)不要为不好的名字写注释,而是应该把名字改好

// Enforce limits on the Reply as stated in the Request, 
// such as the number of items returned, or total byte size, etc. 
void CleanReply(Request request, Reply reply);

上面这段注释的大部分都在解释clean是什么意思,那不如换个正确的名字:

// Make sure 'reply' meets the count/byte/etc. limits from the 'request' 
void EnforceLimitsFromRequest(Request request, Reply reply);

(2)加入“导演评论”,为什么代码写成这样而不是那样,比如你对算法效果、可能存在的改进方案

// Surprisingly, a binary tree was 40% faster than a hash table for this data. 
// The cost of computing a hash was more than the left/right comparisons. 
// This heuristic might miss a few words. That's OK; solving this 100% is hard. 
// This class is getting messy. Maybe we should create a 'ResourceNode' subclass to 
// help organize things.

(3)代码的瑕疵,代码将来如何改动 如TODO:xxxx

// TODO: use a faster algorithm 
// TODO(dustin): handle other image formats besides JPEG 
NUM_THREADS = 8 # as long as it's >= 2 * num_processors, that's good enough.

(4)给常量加注释,可能并不是常量本身,而是常量的值应用情形

// Impose a reasonable limit - no human can read that much anyway. 
const int MAX_RSS_SUBSCRIPTIONS = 1000;

(5)公布可能存在的陷进

// Calls an external service to deliver email. (Times out after 1 minute.) 
void SendEmail(string to, string subject, string body);

(6)为意料之外的行为加注释,比如代码实现的技巧

// Force vector to relinquish its memory (look up "STL swap trick") 
vector<float>().swap(data);

(7)全局观注释,比如为文件/ 类级别上使用,其实还有接口、类、类间交互等等

// This file contains helper functions that provide a more convenient interface to our 
// file system. It handles file permissions and other nitty-gritty details.

(8)总结性注释(比如在比较长的代码段中,为每段代码加上注释),不让读者迷失在细节中

void  GenerateUserReport(): 
# Acquire a lock for this user 
... 
# Read user's info from the database 
... 
# Write info to a file 
... 
# Release the lock for this user
细雨淅淅 标签:
原文地址:https://www.cnblogs.com/zsb517/p/4036333.html