• [读书笔记]: 可读代码的艺术



    本文地址:https://www.cnblogs.com/hchengmx/p/14826739.html

    本文为 《编写可读代码的艺术》
    英译《The Art of Readable Code》的读书笔记。

    整本书的关键思想是:

    让别人理解 你这段代码 所需的时间 “最小化”

    Code should be written to minimize the time it would take for someone else to understand it.

    第一部分 表面层次的改进

    从“表面层次”改进代码,这一部分不涉及 代码的重构。主要包括:

    1. 选择好的 方法名、函数名;
    2. 写好的注释
    3. 代码的整洁性。

    1. 选择准确的变量名

    1.1 准确的动词

    案例1: def getPage(url) 这个函数名

    用get就不明显,是从 数据库中得到一个网页呢?还是从 互联网上得到一个网页?要是从互联网中得到一个页面,可以考虑用 fetchPage() 或者 downloadPage;

    案例2: 一段操作数据库结果的代码

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

    这里的filter指代的是 “挑出”,还是减少呢,从代码中其实看不出来的。

    一些常用词及其替代词汇

    send: deliver、dispatch、announce、distribute、route
    find: search、extract、locate、discover
    start: launch、create、begin、open
    make:create、set up、build、generate、compose、add、new
    

    1.2 准确的变量

    案例1:类 BinaryTree 定义如下:

    class BinaryTree{
        int size;
    }
    

    这里的 Size就很不明显,是期望返回的是 树的高度,节点数,还是树在内存中所占的空间? 对应应该用更专业的词,Height, NumNodes 或者 MemoryBytes。

    几点变量命名建议:

    • 推荐用 min 和 max来表示(包含)极限
    • 推荐用 first 和 last 来表示包含的范围
    • 表示 布尔值的时候,加上 is、has、can、should

    2. 避免使用 temp / retval / result

    不要用 temp 或者 retval 这样的词。因为这种包括不了更多的信息。(temp这个名字只应用于 短期存在 且 临时性 为其存在因素的变量)

    3. 避免在迭代器中使用 i/j/k

    因为要是用了多重循环,就很难发现错误。

    if(clubs[i].members[k] == users[j]) -> if(clubs[ci].members[mi] == users[ui])

    4. 为度量变量加上单位

    如果变量是一个度量的话(比如 时间长度、字节数),那么最好把名字带上它的单位

    var start = (new Date()).getTime() -> var start_ms = (new Date()).getTime();

    before after
    satrt(int delay) delay -> delay_desc
    createCache(int six) size -> size_mb
    throttleDownload(float limit) limit -> max_kbps
    truncate(text, max_length) maxLength -> max_chars

    5. 代码美学

    选用列对齐,可以比较整洁,也可以更直接的发现拼写错误

    details  = request.POST.get('detail')
    location = request.POST.get('location')
    phone    = equest.POST.get('phone')
    email    = request.POST.get('email')
    url      = request.POST.get('url')
    

    把代码分成 "段落"。

    class FrontendServer {
        public:
            FrontServer();
            ~FrontServer();
    
            //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);
    }
    

    6. 注释

    KEY IDEA

    Comments should have a high information-to-space ratio

    • 不要添加不必要的注释(从代码就能读出来的含义)

    6.1 不要为了注释而注释

    // Find the Node in the givin subtree, with the given name, using the given depth
    Node* FindNodeInSubtree(Node* subtree, string name, int depth)
    

    添加细节:

    // Find a Node with given 'name' or return NULL.
    // If depth <=0, only 'subtree' is inspected.
    // If depth == N, only 'subtree' and N levels below are inspected.
    Node* FindNodeInSubtree(Node* subtree, string name, int depth)
    

    6.2 用注释记录你的想法

    标注代码瑕疵

    TODO: Stuff I haven’t gotten around to yet
    FIXME: Known-broken code here
    HACK: Admittedly inelegant solution to a problem
    XXX: Danger! major problem here
    

    每个团队都有自己的风格,比如 TODO:只用于重要的问题,也可以用 todo: 或者 mayber-later: 来表示重要的缺陷

    6.3 给常量添加注释

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

    6.4 写出言简意赅的注释

    让注释保持紧凑

    // The int is the CategoryType
    // The first float in the inner pari in the 'score',
    // the second is the 'weight'
    typedef hash_map<int, pair<float, float>> ScoreMap;
    

    6.5 不要用代词

    会导致含义不明。

    案例1;
    // Insert the data into the cache, but check if it's too big first.

    那这里的it指的是数据呢?还是缓存呢?

    6.6 举例来说明特别的情况

    //...
    // Example: Stripe("abba/a/ba", "ab") returns "/a/"
    String Stripe(String src, String chars) { ... }

    第二部分 简化逻辑

    关键思想:把条件、循环以及其他对控制流的改变做得越“自然”越好。运用一种方式使读者不用停下来重读你的代码。

    Key Idea: Make all your conditionals, loops, and other changes to control flow as "natural" as possible - written in a way that doesn't make the reader stop and reread your code.

    7. 把控制流变得易读

    7.1 if

    比如我们常用的习惯是 if(length >= 10), while(bytes_actual > bytes_expected)

    建议:
    比较式的左侧:不断变化的
    比较式的右侧:常量

    7.2 if else

    原则

    • 先处理 正逻辑,再处理负逻辑。比如 用 if(debug) 而不是 if(!debug)

    用 三目运算符,替代简单的 if else

    time_str += (hour >=12) ? "pm" :"am";
    

    7.3 return

    public boolean Contains(String str, String subStr){
        if(str == null || substr == null) return false;
        if(substr.equals("")) return true;
    }
    

    7.4 减少嵌套

    嵌套会 大大增加读者的思维负担。

    if(user_result == SUCCESS) {
        if(premission_result != SUCESS){
            reply.WriteErrors("error reading permissions");
            reply.Done();
            return;
        }
        reply.WriteErrors("");
        else{
            reply.WriteErrors(user_result);
        }
        reply.Done();
    }
    

    以上的代码很有可能是 以前的需求是只需要判断一下 "user_result",现在增加了新的需求,还要判断 "premission_result"。

    可以改为

    if(user_result != SUCCESS){
        reply.WriteError(user_result);
        reply.Done();
        return;
    }
    
    if(permission_result != SUCCESS){
        reply.WriteError(permission_result);
        reply.Done();
        return;
    }
    
    reply.WriteErrors("");
    reply.Done();
    

    8. 拆分超长的表达式

    8.1 抽离变量

    用变量代替 表达式 里面的东西

    final boolean users_owns_document = (request.user.id == document.owner.id);
    
    if(user_owns_document) {
        // user can edit this document...
    }
    
    if(!user_owns_document) {
        // document is read-only...
    }
    

    8.2 德摩根定律

    !(a||b) 等于 !a&&!b,!(a&&b) 等于 !a||!b

    后者比前者的可读性更好。

    9. 变量与可读性

    9.1 减少变量

    可以考虑删除的变量:

    1. 临时变量,也就是 tmp 变量;
    2. 控制流变量,也就是
    boolean done = false;
    
    while(!done){
        if(...){
            done = true;
            continue;
        }
    }
    

    应该考虑改成

    while( /* condition*/){
        ...
        if(...){
            break;
        }
    }
    

    9.2 减少变量的作用域

    要是一个变量,只有一个方法可以用到,就应该把这个变量变为局部变量。

    class LargeClass{
        string str_;
    
        void Method1(){
            str_ = ...;
            Method2();
        }
    
        void Method1(){
            // Uses str_
        }
    } 
    

    就可以改成

    class LargeClass{
    
        void Method1(){
            string str_ = ...;
            Method2();
        }
    
        void Method1(){
            // Uses str_
        }
    } 
    

    要是一个变量用在if里面,可以适当进行简练。

    if(Payment* info = database.ReadPaymentInfo()){
        cout << "Use pard: " << info ->amount() <<endl;
    }
    

    第三部分 重新组织代码

    10. 抽取不相关的子问题

    基本原则:

    1. 看看某个函数或代码块,问问你自己:这段代码高层次的目标是什么?
    2. 对于每一行代码,问一下:它是直接为了目标而工作吗?这段代码高层次的目标是什么?
    3. 如果足够的行数在解决不相关的子问题,就应该抽取代码到独立的函数中。

    11. 一次只做一件事

    假如有一个博客上的投票插件,用户可以给一条评论 按“上” 或 “下”。每条评论的总分为所有投票的和:“up” 代表 +1,“Down”代表分数 -1。

    当用户按了一个按钮,会调用以下的JS代码。

    vote_changed(old_vote, new_bote);

    下面这个函数实现了这个功能,其中用了大量的代码来判断 old_value 和 new_value 的组合情况。

    var vote_changed = function(old_vote, new vote){
        vat scroe = get_score();
    
        if(new_vote != old_vote){
            if(new_vote === 'up'){
                score += (old_vote === 'Down' ? 2:1);
            } else if(new_vote === 'Down'){
                score -= (old_vote === 'Up'? 2:1);
            } else if(new_vote === ''){
                score += (old_vote === 'Up'?-1:1);
            }
        }
        set_score(score);
    }
    

    我们可以考虑把这个问题分成两个任务

    1. 把投票解析为 数字值;
    2. 更新分数;
    var vote_value = function(vote){
        if(vote === 'Up'){
            return +1;
        }
        if(vote === 'Down'){
            return -1;
        }
        return 0;
    }
    
    var vote_changed = function(old_vote, new_vote){
        var score = get_score();
    
        score -= vote_value(old_vote); //remove the old vote
        score += vote_value(new_vote); //add the new vote
    
        set_score(score);
    }
    

    12. 把想法变成代码

    想象对着一个同事,用“自然语言”来描述代码要做什么。

    以下代码用来决定 是否授权用户看到这个页面,要是没有,就告诉用户没有授权。

    $is_admin = is_admin_request();
    if($document){
        if(!$is_admin && ($document['username'] != $_SESSION['username']){
            return not_authorized();
        })else{
            if(!$is_admin){
                return not_authorized();
            }
        }
    
        //continue rendering the page ...
    }
    

    首先从自然语言描述着逻辑:
    授权你有两种方式:

    1. 你是管理员;
    2. 你拥有当前文档;

    否则,无法授权给你

    if(is_admin_request()){
        //authorized
    }else if($document && ($(document['username'] == $_SESSION['username']))){
        //authorized
    }else{
        return not_authorized();
    }
    

    13. 少写代码

    • 不要费力做不需要的feature
    • 创建更多更好的“工具代码”,来减少重复代码;
    • 让你的项目保持分开的子项目状态;
    • 熟悉你周边的库;
  • 相关阅读:
    ADO.NET Entity Framework如何:通过每种类型多个实体集定义模型(实体框架)
    ADO.NET Entity Framework EDM 生成器 (EdmGen.exe)
    编程之美的求阶乘结果末尾0的个数
    JS 自动提交表单时 报“对象不支持此属性”错误
    php168商务系统品牌无法生成的解决办法
    如何从Access 2000中表删除重复记录
    服务器IUSR_机器名账号找不到怎么办?
    SQL2005 重建全文索引步骤 恢复数据时用到
    PHP页面无法输出XML的解决方法
    bytes2BSTR 解决ajax中ajax.responseBody 乱码问题
  • 原文地址:https://www.cnblogs.com/hchengmx/p/14826739.html
Copyright © 2020-2023  润新知