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


    前言

    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
Copyright © 2020-2023  润新知