代碼規範

代碼之醜(一)——讓判斷條件作真正的選擇java

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)

之因此注意到它,由於最後兩個條件是在最新修改裏面加入的,換句話說,這不是一次寫就的代碼。單就這一次而言,只改了兩行,這是能夠接受的。但這是遺留代碼,每次可能只改了一兩行,一般咱們會不僅一次踏入這片土地。經年累月,代碼成了這個樣子。函數

爲了讓這段代碼能夠接受一些,咱們不妨稍作封裝:spa

 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)) {
    ...
  }

 如今,雖然條件依然仍是不少,但比起原來龐大的函數,至少它已經被控制在一個相對較小的函數裏了。設計

雖然提取函數把這段代碼混亂的條件分離開來,它仍是能夠繼續改進的。好比,咱們把判斷的條件進一步提取:code

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字段,因此,它是有狀態的。 寫程序,咱們要儘可能編寫無狀態方法。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息