• 代码规范


    代码之丑(一)——让判断条件做真正的选择

    if (0 == retCode) {
        SendMsg("000", "Process Success", outResult);
    } else {
        SendMsg("000", "Process Failure", outResult);
    }

    看出来问题了吗?经过仔细的对比,我们发现,如此华丽的代码,if/else的执行语句真正的差异只在于一个参数。第一段代码,二者的差异只是发送的消息,第二段代码,差异在于最后那个参数。

    看破这个差异之后,新的写法就呼之欲出了,以第一段代码为例:
    Java代码  收藏代码

    String msg = (0 == retCode ? "Process Success" : "Process Failure");
    SendMsg("000", msg, outResult);

    由这段代码调整过程,我们得出一个简单的规则:

    让判断条件做真正的选择。
    对于前面调整的代码,判断条件真正判断的内容是消息的内容,而不是消息发送的过程。经过我们的调整,获取消息内容和发送消息的过程严格分离开来。

    消除了代码中的冗余,代码也更容易理解,同时,给未来留出了可扩展性。如果将来retCode还有更多的情形,我们只要调整消息获取的部分进行调整就好了。当然,封装成函数是一个更好的选择,这样代码就变成了:

    SendMsg("000", msgFromRetCode(retCode),outResult);

    代码之丑(二)——长长的条件

    这是一个长长的判断条件:

    Java代码

    if (strcmp(type, “DropGroup") == 0
        || strcmp(type, "CancelUserGroup") == 0
        || strcmp(type, "QFUserGroup") == 0
        || strcmp(type, "CancelQFUserGroup") == 0
        || strcmp(type, "QZUserGroup") == 0
        || strcmp(type, "CancelQZUserGroup") == 0
        || strcmp(type, "SQUserGroup") == 0
        || strcmp(type, "CancelSQUserGroup") == 0
        || strcmp(type, “UseGroup") == 0
        || strcmp(type, "CancelGroup") == 0)

    之所以注意到它,因为最后两个条件是在最新修改里面加入的,换句话说,这不是一次写就的代码。单就这一次而言,只改了两行,这是可以接受的。但这是遗留代码,每次可能只改了一两行,通常我们会不只一次踏入这片土地。经年累月,代码成了这个样子。

    为了让这段代码可以接受一些,我们不妨稍做封装:

     private boolean shouldExecute(String type) {
        return (strcmp(type, “DropGroup") == 0
        || strcmp(type, "CancelUserGroup") == 0
        || strcmp(type, "QFUserGroup") == 0
        || strcmp(type, "CancelQFUserGroup") == 0
        || strcmp(type, "QZUserGroup") == 0
        || strcmp(type, "CancelQZUserGroup") == 0
        || strcmp(type, "SQUserGroup") == 0
        || strcmp(type, "CancelSQUserGroup") == 0
        || strcmp(type, “UseGroup") == 0
        || strcmp(type, "CancelGroup") == 0);
      }

    Java代码

      if (shouldExecute(type)) {
        ...
      }
    

     现在,虽然条件依然还是很多,但比起原来庞大的函数,至少它已经被控制在一个相对较小的函数里了。

    虽然提取函数把这段代码混乱的条件分离开来,它还是可以继续改进的。比如,我们把判断的条件进一步提取:

    Java代码

    private boolean shouldExecute(String type) {
      String [] types= {
        "DropGroup",
        "CancelUserGroup",
        "QFUserGroup",
        "CancelQFUserGroup",
        "QZUserGroup",
        "CancelQZUserGroup",
        "SQUserGroup",
        "CancelSQUserGroup",
        "UseGroup",
        "CancelGroup"
      };
      int size = types.size;
      for (int i = 0; i < size; i++) {
        if (strcmp(type, types) == 0) {
          return true;
        }
      }
      return false;
    }

    这样的话,如果以后要加一个新的type,只要在数组中增加一个新的元素即可。

    代码之丑(三)——不受欢迎的大心脏

    Java代码

    ColdRule newRule = new ColdRule(); 
    newRule.SetOID(oldRule.GetOID()); 
    newRule.SetRegion(oldRule.GetRegion()); 
    newRule.SetRebateRuleID(oldRule.GetRebateRuleID()); 
    newRule.SetBeginCycle(oldRule.GetBeginCycle() + 1); 
    newRule.SetEndCycle(oldRule.GetEndCycle()); 
    newRule.SetMainAcctAmount(oldRule.GetMainAcctAmount()); 
    newRule.SetGiftAcctAmount(oldRule.GetGiftAcctAmoun t()); 
    newRule.SetValidDays(0); 
    newRule.SetGiftAcct(oldRule.GetGiftAcct()); 
    rules.Add(newRule); 

    就在我以为这一片代码就是完成给一个变量设值的时候,突然,在那个不起眼的角落里,这个变量得到了应用:它被加到了rules里面。什么叫峰回路转,这就是。

    既然它给了我们这么有趣的体验,必然先杀后快。下面重构了这个函数:

    Java代码

    ColdRule CreateNewRule(ColdRule& oldRule) { 
       ColdRule newRule = new ColdRule(); 
       newRule.SetOID(oldRule.GetOID()); 
       newRule.SetRegion(oldRule.GetRegion()); 
       newRule.SetRebateRuleID(oldRule.GetRebateRuleID()); 
       newRule.SetBeginCycle(oldRule.GetBeginCycle() + 1); 
       newRule.SetEndCycle(oldRule.GetEndCycle()); 
       newRule.SetMainAcctAmount(oldRule.GetMainAcctAmount()); 
       newRule.SetGiftAcctAmount(oldRule.GetGiftAcctAmount()); 
       newRule.SetValidDays(0); 
       newRule.SetGiftAcct(oldRule.GetGiftAcct()); 
       return newRule; 
    } 

    Java代码

    rules.Add(CreateNewRule(oldRule)); 

    把这一堆设值操作提取了出来,整个函数看上去一下子就清爽了。不是因为代码变少了,而是因为代码按照它职责进行了划分:创建的归创建,加入的归加入。之前的代码之所以让我不安,多重职责是罪魁祸首。一旦把这个函数提取出来,做完这步操作,我们就不难发现这个函数应该成为CodeRule类的一部分。

    代码之丑(四)——退让的缩进

    这是一个让我纠结了很久的话题:缩进。
    Java代码  收藏代码

     for (int j = 0; j < attributes.size(); j++) {
        Attr *attr = attributes.get(j);
        if (attr == NULL ) {
          continue;
        }
        int IsCallFunc = -1;
        if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) {
          if(strcmp(attr->attrID(), "CallFunc") == 0) {
            if(0 == strcmp(attr->attrValue(), "1")) {
              IsCallFunc = 1;
            } else if(0 == strcmp(attr->attrValue(), "0")) {
              IsCallFunc = 0;
            }
          }
        } else if (attr->status() == STATUS_DELETED) {
          IsCallFunc = 0;
        }
        ...
      }

    回到这段代码上,能出现多层缩进,for循环功不可没。出现这种循环,很多情况下,都是对一个集合进行处理,而循环里的内容,就是对集合里的每一个元素进行处理。这里也不例外。所以,我们先做一次提取:
    Java代码  收藏代码

     for (int j = 0; j < attributes.size(); j++) {
        processAttr(attributes.get(j));
      }
      void processAttr(Attr *attr) {
        if (attr == NULL ) {
          return;
        }
        int IsCallFunc = -1;
        if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) {
          if(strcmp(attr->attrID(), "CallFunc") == 0) {
            if(0 == strcmp(attr->attrValue(), "1")) {
              IsCallFunc = 1;
            } else if(0 == strcmp(attr->attrValue(), "0")) {
              IsCallFunc = 0;
            }
          }
        } else if (attr->status() == STATUS_DELETED) {
          IsCallFunc = 0;
        }
        ...
      }

    至此,我们去掉了一层缩进,而且因为这个提取,语义也变得很清晰:这个新函数只是处理集合里的一个元素。

    接下来,这个函数里面长长的代码是对IsCallFunc进行设值,后面省略的部分会根据这里求出的结果进行处理。所以,这里把processAttr进一步分拆:
    Java代码  收藏代码

    void processAttr(Attr *attr) {
      if (attr == NULL ) {
        return;
      }
      int IsCallFunc = isCallFunc(attr);
      ......
    }
    
    
    int isCallFunc(Attr *attr) {
      if(attr->status() == STATUS_NEW
      || attr->status() == STATUS_MODIFIED) {
        if(strcmp(attr->attrID(), "CallFunc") == 0) {
          if(0 == strcmp(attr->attrValue(), "1")) {
              return 1;
          } else if(0 == strcmp(attr->attrValue(), "0")) {
              return 0;
          }
        }
      } else if (attr->status() == STATUS_DELETED) {
        return 0;
      }
      return -1;
    }

    代码之丑(五)--无状态方法

    诸位Java程序员,想必大家对SimpleDateFormat并不陌生。不过,你是否知道,SimpleDateFormat不是线程安全的(thread safe)。这意味着,下面的代码是错误的:
    Java代码  收藏代码

    class Sample {
      private static final DateFormat format = new SimpleDateFormat("yyyy.MM.dd");
    
      public String getCurrentDateText() {
        return format.format(new Date());
      }
    }

    从功能的角度上看,单独执行这段代码是没有问题的,但放到多线程环境下,因为SimpleDateFormat不是线程安全的,这段代码就会出错。所以,要想让这段代码正确,我们只要稍做微调:

    Java代码  收藏代码

    public class Sample {
        public String getCurrentDateText() {
            return new SimpleDateFormat("yyyy.MM.dd").format(new Date());
        }
    }

    不知你是否注意到,这里的调整只是由原来的共享format这个变量,变成了每次调用这个方法时创建出一个新的SimpleDateFormat变量。

    作为一个专业程序员,我们当然知道,相比于共享一个变量的开销要比每次创建小。之所以我们必须这么做,是因为SimpleDateFormat不是线程安全的。但从SimpleDateFormat提供给我们的接口上来看,实在让人看不出它与线程安全有和相干。那接下来,我们就要打开JDK的源码,看一下其中的代码之丑。

    如果你手头没有JDK的源码,这里是个不错的参考。

    在format方法里,有这样一段代码:

    calendar.setTime(date);

    其中,calendar是DateFormat的protected字段。这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。

    想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:

    线程1调用format方法,改变了calendar这个字段。
    中断来了。
    线程2开始执行,它也改变了calendar。
    又中断了。
    线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。
    BANG!!! 稍微花点时间分析一下format的实现,我们便不难发现,用到calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
    这个问题背后隐藏着一个更为重要的问题:无状态。

    无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。

    写程序,我们要尽量编写无状态方法。

  • 相关阅读:
    第一章--linux基础
    深入浅出OOP(一): 多态和继承(早期绑定/编译时多态)
    LeetCode Letter Combinations of a Phone Number
    ios 仿android gallery控件
    android何如获取SIM卡提供国家代码(ISO)
    android 获取 imei号码
    overridePendingTransition的简介
    转 Android Activity之间动画完整版详解
    【android开发】使用PopupWindow实现页面点击顶部弹出下拉菜单
    Android 带你从源码的角度解析Scroller的滚动实现原理
  • 原文地址:https://www.cnblogs.com/zhaoding/p/6252571.html
Copyright © 2020-2023  润新知