美文网首页好多编程入门@IT·互联网程序员
《编写可读代码的艺术》读书笔记——代码审美

《编写可读代码的艺术》读书笔记——代码审美

作者: Misa527528 | 来源:发表于2016-08-10 09:32 被阅读1454次

    写代码就像读书时候写作文一样,虽然试卷上没有要求字迹要多工整、篇幅要多具备艺术气息,但是不可否认的是,字迹工整、篇幅合理的作文总能拿较高的分数,屡试不爽。而好的代码规范能够提高工作效率,降低查找代码和团队交接的时间。如何让代码看起来很“养眼”呢?确切地说有三条原则:

    • 使用一致的布局
    • 让相似的代码看上去相似
    • 把相关的代码行分组,形成代码块

    举个坏掉的栗子:

    class StatsKeeper{
    public:
    // A class for keeping track of a series of doubles
       void Add(double d);    // and methods for quick statistics about them
     private:  int count;    /* how many so   far
    */private:    double Average();
    list<double>
      past_items
             ;double maximum;
    };
    

    再举一个好的栗子:

    // A class for keeping track of a series of doubles
    // and methods for quick statistics about them
    class StatsKeeper{
      public:
        void Add(double d);
        double Average();
    
      private:
        list<double> past_items;
        int count;  // how many so far
    
        double minimum;
        double maximum;
    };
    

    嗯,看到这里你应该才发现第一个例子中少写了一个double minimum;,下面是一些具体操作的要点。

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

    有的公司编码规范中有明确规定列字数限制,例如Google是80或100,超过规定都必须自动换行。但除此之外,还有一些没有超过的但也期望程序员能选择换行的情况。举个例子:

    public class PerformanceTester{
      public static final TcpConnectionSimulator wifi =  new TcpConnectionSimulator(
        500, /* kbps */ 
        80, /*millisecs latency */  
        200, /* jitter */ 
        1 /* packet loss % */ );
    
      public static final  TcpConnectionSimulator t3_fiber = 
        new TcpConnectionSimulator(
          4500, /* kbps */ 
          10, /*millisecs latency */  
          0, /* jitter */ 
          0 /* packet loss % */ );
    
      public static final  TcpConnectionSimulator cell = new TcpConnectionSimulator(
        100, /* kbps */ 
        400, /*millisecs latency */  
        250, /* jitter */ 
        5 /* packet loss % */ );
    }
    

    虽然都是定义一个TcpConnectionSimulator对象,但t3_fiber乍一看和它的邻居并不一样,这显然违反了“让相似的代码看上去相似”原则。修改过后是这样的:

    public class PerformanceTester{
      public static final TcpConnectionSimulator wifi =  
        new TcpConnectionSimulator(
          500, /* kbps */ 
          80, /*millisecs latency */  
          200, /* jitter */ 
          1 /* packet loss % */ );
    
      public static final  TcpConnectionSimulator t3_fiber = 
        new TcpConnectionSimulator(
          4500, /* kbps */ 
          10, /*millisecs latency */  
          0, /* jitter */ 
          0 /* packet loss % */ );
    
      public static final  TcpConnectionSimulator cell = 
        new TcpConnectionSimulator(
          100, /* kbps */ 
          400, /*millisecs latency */  
          250, /* jitter */ 
          5 /* packet loss % */ );
    }
    

    当然也可以把注释搬到上面去,毕竟同样的注释写了三遍就要考虑“重构”了。

    public class PerformanceTester{
       // TcpConnectionSimulator(thoughput, latency, jetter, packet_loss)
       //                         [kbps]     [ms]     [ms]    [percent]
      public static final  TcpConnectionSimulator cell = 
        new TcpConnectionSimulator(500,80, 200, 1);
    
      public static final  TcpConnectionSimulator cell = 
        new TcpConnectionSimulator(4500, 10, 0, 0);
    
      public static final  TcpConnectionSimulator cell = 
        new TcpConnectionSimulator(100, 400, 250, 5);
    }
    

    2.在需要的时候使用列对齐#####

    整齐的边和列让读者可更轻松的浏览文本,有时候可以选择借用“列对齐”来让代码易读。例如:

    CherkFullName("Doug Adams" , "Mr. Douglas Adams"  , "");
    CherkFullName("Jake Brown" , "Mr. Jake Brown III" , "");
    CherkFullName("No Such Guy", ""                   , "no match found");
    CherkFullName("John"       , ""                   , "more than one result");
    

    就像Google的代码规范中并不提倡这样的写法一样,笔者也不建议使用这种风格,因为一旦方法中某个参数需要修改,那么你又要花很多时间去排序。但是下面的情况可适当使用:

    # Extract POST parameters to local variables
    details  = request.POST.get('details');
    location = request.POST.get('location');
    phone    = equest.POST.get('phone');
    email    = request.POST.get('email');
    url      = request.POST.get('url');
    

    Wait!第四行是不是少了点什么???


    3. 选一个有意义的顺序,始终一致地使用#####

    在很多时候,代码的顺序不会影响其正确性,例如上文五个变量的定义可以写成任意顺序。在这种情况下,不要随机排序,把他们按有意义的方式排序会有帮助。以下是一些排序的原则:

    • 让变量的顺序与对应的HTML表单中<input>字段的顺序相匹配。
    • 从“最重要”到“最不重要”排序。
    • 按字母顺序排序

    一旦你采用了某种排序规则,就要在代码中始终如一的遵循它。


    4.把声明按块组织起来#####

    由于大脑会很自然地按照分组和层次结构来思考,因此可以通过按块组织方式来使读者更快速的理解你的代码。
    举个例子:

    class FrontendServer{
      public:
        FrontendServer();
        void ViewProfile(HttpRequest* request);
        void OpenDatabase(String location, String user);
        void SaveProfile(HttpRequest* request);
        String ExtractQueryParam(HttpRequest* request, String param);
        void ReplyOk(HttpRequest* request, String html);
        void FindFriends(HttpRequest* request);
        void ReplyNotFound(HttpRequest* request, String error);
        void CloseDatabase(String location);
        ~FrontendServer();
    }
    

    作为读者,要想读懂代码,恐怕你会选择先把它们分成不同的组,所以写这段代码的程序员不妨先将它们按块组织在一起,并加上适当的注释,这样代码的可读性将大大提高。

    class FrontendServer{
      public:
        FrontendServer();
        ~FrontendServer();
    
        // Handlers
        void ViewProfile(HttpRequest* request);
        void SaveProfile(HttpRequest* request);
        void FindFriends(HttpRequest* request);
    
        // Request/Reply Utilities
        String ExtractQueryParam(HttpRequest* request, String param);
        void ReplyOk(HttpRequest* request, String html);
        void ReplyNotFound(HttpRequest* request, String error);
    
        //Database Helpers
        void OpenDatabase(String location, String user);
        void CloseDatabase(String location);
    }
    

    5. 把代码分成段落#####

    字面文字分成段落是由于以下几个原因:

    • 它是一种把相似的想法放在一起并与其他想法分开的方法。
    • 它提供了可见的“脚印”,如果没有它,会很容易找不到你读到哪里了。
    • 它便于段落之间的导航。

    出于同样的原因,代码也应该分段,并为每一个段落加上一条总结性的注释。

    def suggust_new_friends(user, emaii_password):
        # Get the user's friends' email addresses
        friends = user.friends();
        friend_emails = set(f.email for f in frends)
    
        # Import all email addresses from this user's email account.
        contacts = import_contacts(user.email, email_password)
        contact_emails = set(c.email for c in contracts)
    
        # Find matching users that they aren't aleady friends with.
        non_friend_emails = contact_email - friend_emails
        suggested_friends = User.object.select(email_in = non_friend_emails)
    
        # Display these lists on the page
        display['user'] = user
        display['friends'] = friends
        display['suggested_friends'] = suggested_friends
    
        return render("suggested_friends.html", display)
    

    6. 用方法来整理不规则的东西#####

    假设有一个个人数据库,它提供了下面这个函数:

    // Turn a partial_name like "Doug Adams " into "Mr. Douglas Adams".
    // If not possible, 'error' is filled with an explannation.
    String ExpandFullName(DatabaseConnection dc, String patial_name, String* error);
    

    并且提供一系列的例子来测试:

    DatabaseConnection database_connection;
    string error;
    assert(ExpandFullName(database_connection, "Doug Adams", &error) == "Mr. Douglas Adams");
    assert(error == "");
    assert(ExpandFullName(database_connection, "Jake Brown", &error) == "Mr. Jacob Brown III");
    assert(error == "");
    assert(ExpandFullName(database_connection, "No Such Guy", &error) == "");
    assert(error == "no match found");
    assert(database_connection, "John", &error) == "");
    assert(error == "more than one result");
    

    这段代码其实看起来并不“养眼”,因为它没有换行,也没有一致的风格。但更大的问题在于有很多重复的串,例如“assert(ExpandFullName(database_connection,…”, 其中还有很多“error”。要想改进这段代码,可以为它们添加一个辅助方法。就像:

    CherkFullName("Doug Adams", "Mr. Douglas Adams", "");
    CherkFullName("Jake Brown", "Mr. Jake Brown III", "");
    CherkFullName("No Such Guy", "", "no match found");
    CherkFullName("John", "", "more than one result");
    

    很明显这里有4个测试,每个使用了不同的参数,并把所有的“脏活”都放在CherkFullName()中:

    void CherkFullName(String partial_name,
                       String expected_full_name,
                       String expected_error) {
    //database_connection is now a class member
    String error;
    String full_name = ExpandFullName(database_connection, partial_name, &error);
    assert(error == expected_error); 
    assert(full_name == expected_full_name);
    }
    

    7. 个人风格与一致性#####

    最后想说的是一些个人的编程风格,例如大括号该放在哪里:

    public void function
    {
        …
    }
    

    还是:

    public void function {
        …
    }
    

    又比如说"Tabs versus Space" ,笔者的建议是按照团队定好的风格来写,只采取一种,至于是采取哪一种,那就见仁见智了。

    相关文章

      网友评论

        本文标题:《编写可读代码的艺术》读书笔记——代码审美

        本文链接:https://www.haomeiwen.com/subject/pmyhsttx.html