美文网首页编程语言
编写优雅代码的最佳实践

编写优雅代码的最佳实践

作者: 木可大大 | 来源:发表于2018-04-21 06:58 被阅读17次

    Robert Martin曾说过"在代码阅读中说脏话的频率是衡量代码质量额唯一标准"。同时,代码的写法应当使别人理解它所需的时间最小化,也就是说我们写的代码是给人看的而不是给机器看的。那么,如何编写优雅代码呢?可以从思想层面和具体技巧层面来优化代码,思想层面指的是遵循面向对象设计原则,本期介绍的是具体技巧。

    1. 代码总是越短越好吗?

    assert((!(bucket = findBucket(key))) || !bucket.isOccupied());
    

    上面这行代码虽然比较短,但是难以阅读。为了更好地阅读,我们做如下修改:

    bucket = findBucket(key);
    if(bucket != null){
      assert(!bucket.isOccupied());
    }
    

    减少代码行数是一个好目标,但是让阅读代码的事件最小化是个更好的目标

    2. 给重要语句添加注释

    // Fast version of "hash = (65599*hash) + c"
    hash = (hash << 6) + (hash << 16) - hash + c
    

    上面这行代码如果没有添加注释,我们根本不知道是什么意思,但是有了这行注释,我们就知道通过移位操作来提升性能。

    3. tmp的使用

    tmp是我们经常用的,譬如说两个变量置换,都已变成约定俗成了。

    tmp = right;
    right = left;
    left = tmp;
    
    String tmp = user.getName();
    tmp += " " + user.getPhoneNumber();
    tmp += " " + user.getEmail();
    template.set("user_info",tmp);
    

    4.i,j,k,iter,it:只用做索引或者循环迭代

    i,j,k,iter,it被用做索引或者循环迭代已成为业界规范了(i是index的缩写),例如:

    for(int i=0;i<100;i++){
      for(int j=0;j<100;j++){
        ......
      }
    }
    
    Iterator<String> iter = list.iterator();
    while(iter.hasNext()){
      ......
    }
    

    如果我们在其他地方使用i,j,k,那么就会增加阅读者的时间。

    5. 附带重要属性

    我们把命名当做一种注释的方式,让它承载更多的信息!


    image.png

    6. 名字需要多长?

    • 在小的作用域中使用简短的名字
    • 在作用域大的可以使用长名字
    if(debug){
      Map<String,Integer> m = new HashMap<>();
      lookUpNamesNumbers(m);
      print(m);
    }
    

    7. 不要使用容易误解的名字

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

    上面这行代码结果现在包含哪些信息?filter是把年份小于等于2011年的数据过滤掉?还是保留?

    8. 推荐用min和max来表示极限

    MAX_ITEMS_IN_CART = 10;
    
    if (shoppingCart.numOfItems()> MAX_ITEMS_IN_CART){
        error("Too many items in cart");
    }
    

    9. 推荐用begin和end来表示包含/排除范围

    image.png

    begin表示包含,end表示排除,在Java中典型的例子就是String.substring()

    
    String s = "Hello world";
    
    s.substring(2,5);-> "llo"
    
    

    10.与使用者的期望相匹配

    一般来说,getter方法就是获取一个字段的值,用户期待的是轻量级的方法,如果你要是在其中做了太多的计算,就应该考虑改名。

    
    public double getMeanPrice(){
    
    //遍历所有条目计算总价,然后计算平均价格
    
    }
    
    public double computeMeanPrice(){
    
    //遍历所有条目计算总价,然后计算平均价格
    
    }
    
    

    11.不要为那些从代码本身就能快速推断的事实写注释

    
    public  class Account {  
    
        // Constructor
    
       public  Account(){
    
       }
    
       // Set the profit member to a new value    
    
       void setProfit(double profit){
    
             …….
    
       }   
    
        // Return the profit from this Account    
    
        double getProfit(){
    
            ….
    
        }
    
    };
    
    

    12. 不要给不好的名字加注释--应该把名字改好

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

    乍一看我们会误认为这是一个删除注册表的函数,可是注释里澄清它不就改动真正的注册表。因此,我们可以用一个更加自我说明的名字,例如:

    void releaseRegistryHandle(registryKey key);
    

    13.为代码中的瑕疵写注释

    // TODO:采用更快算法或者当代码没有完成时
    // TODO(dustin):处理除JPEG以外的图像格式


    image.png

    14.为常量写注释

    // users thought 0.72 gave the best size/quality tradeoff
    image_quality = 0.72;
    
    // as long as it's >= 2*num_processors,that's good enough
    NUM_THREADS = 8;
    
    // impose a reasonable limit - no human can read that much anywhere
    const int MAX_RSS_SUBSCRIPTIONS = 1000;
    

    15. 站在读者的角度写注释

    struct Recoder {
        vector<float> data;
        ......
        void clear(){
            // 每个人读到这里都会问,为啥不直接调用data.clear()
            vector<float>().swap(data);
        }
    }
    

    如果有一个好的注释可以解答读者的疑问,将上述进行如下修改:强制Vector真正地把内存归还给内存分配器,详情请查阅STL swap trick。

    16. 公布可能的陷阱

    void sendMail(String to,String subject,String body);
    

    这个函数由于需要调用外部服务器发送邮件,可能会很耗时,有可能导致使用者的线程挂起。需要将这段描述放到注释中。

    17. 条件语句中参数的顺序

    image.png
    一般原则:将变量放在左边,常量放在右边。更宽泛地说,将比较稳定的变量放在右边,变化较大的放在左边。如 if ( length >= 10) 而不是 if ( 10 <= length)。但是,在非“大小”比较的情况下,上面的原则似乎不起作用,例如验证一个请求参数是否为某个特定值:if ( request.getParameterValue("name")).equals("Brandon")),此时将常量"Brandon"可以避免出现空指针的情况(上行的参数没有name或者值为空)。

    18. if/else语句块的顺序

    if/else书写规范:首先处理正逻辑而不是负逻辑,例如 if(ok),而不是if(!ok);其次处理掉简单的情况,这有利于让if和else处理代码在同一个屏幕内可见。

    19. 通过提早返回减少嵌套

    使用提前返回的机制,可以把函数的嵌套层级变浅。举个栗子,没有使用提前返回的代码:

    static bool checkUserAuthority()
            {
                bool a, b, c, d, e;
    
                if (a)
                {
                    if (b)
                    {
                        if (c)
                        {
                            if (d)
                            {
                                if (e)
                                {
                                    return true;
                                }
                            }
                        }
                    }
                }
    
                return false;
            }
    

    使用了提前返回的代码:

    static bool checkUserAuthority()
            {
                bool a, b, c, d, e;
    
                if (!a)
                    return false;
    
                if (!b)
                    return false;
    
                if (!c)
                    return false;
    
                if (!d)
                    return false;
    
                if (!e)
                    return false;
    
                return true;
           
            }
    

    20. 通过 "总结变量" 增加可读性

    if(request.user.id == document.owner_id){
        // user can edit this document ...
    }
    
    if(request.user.id != document.owner_id){
        // document is read-only...
    }
    

    通过观察,我们提取一个变量final boolean user_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...
    }
    
    

    21. 减少控制流变量

    在while、for等循环语句中,我们通常使用自定义的bool变量,来控制流转。

    boolean done = false;
    while(/* condition */ && !done){
        ...
        if(...){
            done = true;
            continue;
        }
    }
    

    以我们的经验,"控制流变量" 可以通过优化程序结构、逻辑来消除。

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

    22. 缩小变量的作用域

    void foo(){
        int i = 7;
    
        if(someCondition){
            // i is used only within this block
        }
    
    }
    
    void foo(){
        if(someCondition){
            int i = 7;
            // i is used only within this block
        }
    }
    

    23. 不要为了共享而把变量设置为类的字段

    public class LargeClass{
        String s;
        void method1(){
            s = ...
            method2();
        }
        void method2(){
            //使用s
        }
    }
    

    通过参数传递来实现数据共享

    public class LargeClass{
        void method1(){
            String s = ...
            method2(s);
        }
        void method2(String s){
            //使用s
        }
    }
    

    24. 不要把所有变量都定义在开头

    把所有变量定义在开头是C语言的风格,面向对象语言习惯将变量定义在离它开始使用的地方。

    public void foo(){
      boolean debug = false;
      String[] pvs;
      String pn;
      String pv;
      ...
    }
    

    除了上述建议之外,我们还可以参考阿里Java规范,关注微信号:"木可大大",发送"阿里Java规范"即可获得相关资料。

    相关文章

      网友评论

      • IT人故事会:经常看别人的分享.感谢别人的分享,感谢!关注了

      本文标题:编写优雅代码的最佳实践

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