爲何阿里巴巴禁止開發人員使用isSuccess做爲變量名

 

 

在平常開發中,咱們會常常要在類中定義布爾類型的變量,好比在給外部系統提供一個RPC接口的時候,咱們通常會定義一個字段表示本次請求是否成功的。json

關於這個」本次請求是否成功」的字段的定義,實際上是有不少種講究和坑的,稍有不慎就會掉入坑裏,做者在好久以前就遇到過相似的問題,本文就來圍繞這個簡單分析一下。到底該如何定一個布爾類型的成員變量。微信

通常狀況下,咱們能夠有如下四種方式來定義一個布爾類型的成員變量:app

boolean success
boolean isSuccess
Boolean success
Boolean isSuccess

以上四種定義形式,你平常開發中最經常使用的是哪一種呢?到底哪種纔是正確的使用姿式呢?框架

經過觀察咱們能夠發現,前兩種和後兩種的主要區別是變量的類型不一樣,前者使用的是boolean,後者使用的是Boolean。ide

另外,第一種和第三種在定義變量的時候,變量命名是success,而另外兩種使用isSuccess來命名的。工具

首先,咱們來分析一下,到底應該是用success來命名,仍是使用isSuccess更好一點。this


success 仍是 isSucces

到底應該是用success仍是isSuccess來給變量命名呢?從語義上面來說,兩種命名方式均可以講的通,而且也都沒有歧義。那麼還有什麼原則能夠參考來讓咱們作選擇呢。spa

在阿里巴巴Java開發手冊中關於這一點,有過一個『強制性』規定:設計

那麼,爲何會有這樣的規定呢?咱們看一下POJO中布爾類型變量不一樣的命名有什麼區別吧。3d

class Model1  {
   private Boolean isSuccess;
   public void setSuccess(Boolean success) {
       isSuccess = success;
   }
   public Boolean getSuccess() {
       return isSuccess;
   }
}

class Model2 {
   private Boolean success;
   public Boolean getSuccess() {
       return success;
   }
   public void setSuccess(Boolean success) {
       this.success = success;
   }
}

class Model3 {
   private boolean isSuccess;
   public boolean isSuccess() {
       return isSuccess;
   }
   public void setSuccess(boolean success) {
       isSuccess = success;
   }
}

class Model4 {
   private boolean success;
   public boolean isSuccess() {
       return success;
   }
   public void setSuccess(boolean success) {
       this.success = success;
   }
}

仔細觀察以上代碼,你會發現如下規律:

  • 基本類型自動生成的getter和setter方法,名稱都是isXXX()setXXX()形式的。

  • 包裝類型自動生成的getter和setter方法,名稱都是getXXX()setXXX()形式的。

 

既然,咱們已經達成一致共識使用基本類型boolean來定義成員變量了,那麼咱們再來具體看下Model3和Model4中的setter/getter有何區別。

咱們能夠發現,雖然Model3和Model4中的成員變量的名稱不一樣,一個是success,另一個是isSuccess,可是他們自動生成的getter和setter方法名稱都是isSuccesssetSuccess

 

Java Bean中關於setter/getter的規範

關於Java Bean中的getter/setter方法的定義實際上是有明確的規定的,根據JavaBeans(TM) Specification規定,若是是普通的參數,命名爲propertyName,須要經過如下方式定義其setter/getter:

public <PropertyType> get<PropertyName>();
public void set<PropertyName>(<PropertyType> a);

可是,布爾類型的變量propertyName則是另一套命名原則的:

public boolean is<PropertyName>();
public void set<PropertyName>(boolean m);

經過對照這份JavaBeans規範,咱們發現,在Model4中,變量名爲isSuccess,若是嚴格按照規範定義的話,他的getter方法應該叫isIsSuccess。可是不少IDE都會默認生成爲isSuccess。

那這樣作會帶來什麼問題呢。

在通常狀況下,實際上是沒有影響的。可是有一種特殊狀況就會有問題,那就是發生序列化的時候。

 

序列化帶來的影響

咱們這裏拿比較經常使用的JSON序列化來舉例,看看經常使用的fastJson、jackson和Gson之間有何區別:

public class BooleanMainTest {

   public static void main(String[] args) throws IOException {
       //定一個Model3類型
       Model3 model3 = new Model3();
       model3.setSuccess(true);

       //使用fastjson(1.2.16)序列化model3成字符串並輸出
       System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));

       //使用Gson(2.8.5)序列化model3成字符串並輸出
       Gson gson =new Gson();
       System.out.println("Serializable Result With Gson :"+gson.toJson(model3));

       //使用jackson(2.9.7)序列化model3成字符串並輸出
       ObjectMapper om = new ObjectMapper();
       System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
   }
}

class Model3 implements Serializable {

   private static final long serialVersionUID = 1836697963736227954L;
   private boolean isSuccess;
   public boolean isSuccess() {
       return isSuccess;
   }
   public void setSuccess(boolean success) {
       isSuccess = success;
   }
   public String getHollis(){
       return "hollischuang";
   }
}

以上代碼的Model3中,只有一個成員變量即isSuccess,三個方法,分別是IDE幫咱們自動生成的isSuccess和setSuccess,另一個是做者本身增長的一個符合getter命名規範的方法。

以上代碼輸出結果:

Serializable Result With fastjson :{"hollis":"hollischuang","success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true,"hollis":"hollischuang"}

在fastjson和jackson的結果中,原來類中的isSuccess字段被序列化成success,而且其中還包含hollis值。而Gson中只有isSuccess字段。

咱們能夠得出結論:fastjson和jackson在把對象序列化成json字符串的時候,是經過反射遍歷出該類中的全部getter方法,獲得getHollis和isSuccess,而後根據JavaBeans規則,他會認爲這是兩個屬性hollis和success的值。直接序列化成json:

{「hollis」:」hollischuang」,」success」:true}

可是Gson並非這麼作的,他是經過反射遍歷該類中的全部屬性,並把其值序列化成json:

{「isSuccess」:true}

能夠看到,因爲不一樣的序列化工具,在進行序列化的時候使用到的策略是不同的,因此,對於同一個類的同一個對象的序列化結果多是不一樣的。

前面提到的關於對getHollis的序列化只是爲了說明fastjson、jackson和Gson之間的序列化策略的不一樣,咱們暫且把他放到一邊,咱們把他從Model3中刪除後,從新執行下以上代碼,獲得結果:

Serializable Result With fastjson :{"success":true}
Serializable Result WithGson :{"isSuccess":true}
Serializable Result With jackson :{"success":true}

如今,不一樣的序列化框架獲得的json內容並不相同,若是對於同一個對象,我使用fastjson進行序列化,再使用Gson反序列化會發生什麼?

public class BooleanMainTest {
   public static void main(String[] args) throws IOException {
       Model3 model3 = new Model3();
       model3.setSuccess(true);
       Gson gson =new Gson();
       System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));
   }
}

class Model3 implements Serializable {
   private static final long serialVersionUID = 1836697963736227954L;
   private boolean isSuccess;
   public boolean isSuccess() {
       return isSuccess;
   }
   public void setSuccess(boolean success) {
       isSuccess = success;
   }
   @Override
   public String toString() {
       return new StringJoiner(", ", Model3.class.getSimpleName() + "[","]")
           .add("isSuccess=" + isSuccess)
           .toString();
   }
}

以上代碼,輸出結果:

Model3[isSuccess=false]

這和咱們預期的結果徹底相反,緣由是由於JSON框架經過掃描全部的getter後發現有一個isSuccess方法,而後根據JavaBeans的規範,解析出變量名爲success,把model對象序列化城字符串後內容爲{"success":true}

根據{"success":true}這個json串,Gson框架在經過解析後,經過反射尋找Model類中的success屬性,可是Model類中只有isSuccess屬性,因此,最終反序列化後的Model類的對象中,isSuccess則會使用默認值false。

可是,一旦以上代碼發生在生產環境,這絕對是一個致命的問題。

因此,做爲開發者,咱們應該想辦法儘可能避免這種問題的發生,對於POJO的設計者來講,只須要作簡單的一件事就能夠解決這個問題了,那就是把isSuccess改成success。

這樣,該類裏面的成員變量時success,getter方法是isSuccess,這是徹底符合JavaBeans規範的。不管哪一種序列化框架,執行結果都同樣。就從源頭避免了這個問題。

引用一下R大關於阿里巴巴Java開發手冊中這條規定的評價

(https://www.zhihu.com/question/55642203):

因此,在定義POJO中的布爾類型的變量時,不要使用isSuccess這種形式,而要直接使用success!

 

 Boolean仍是boolean?  

前面咱們介紹完了在success和isSuccess之間如何選擇,那麼排除錯誤答案後,備選項還剩下:

boolean success
Boolean success

那麼,到底應該是用Boolean仍是boolean來給定一個布爾類型的變量呢?

咱們知道,boolean是基本數據類型,而Boolean是包裝類型。

那麼,在定義一個成員變量的時候究竟是使用包裝類型更好仍是使用基本數據類型呢?

咱們來看一段簡單的代碼

 

public class BooleanMainTest {
   public static void main(String[] args) {
       Model model1 = new Model();
       System.out.println("default model : " + model1);
   }
}

class Model {
   /**
    * 定一個Boolean類型的success成員變量
    */
   private Boolean success;
   /**
    * 定一個boolean類型的failure成員變量
    */
   private boolean failure;

   /**
    * 覆蓋toString方法,使用Java 8 的StringJoiner
    */
   @Override
   public String toString() {
       return new StringJoiner(", ", Model.class.getSimpleName() + "[","]")
           .add("success=" + success)
           .add("failure=" + failure)
           .toString();
   }
}

以上代碼輸出結果爲:

default model : Model[success=null, failure=false]

能夠看到,當咱們沒有設置Model對象的字段的值的時候,Boolean類型的變量會設置默認值爲null,而boolean類型的變量會設置默認值爲false

即對象的默認值是null,boolean基本數據類型的默認值是false

在阿里巴巴Java開發手冊中,對於POJO中如何選擇變量的類型也有着一些規定:

這裏建議咱們使用包裝類型,緣由是什麼呢?

 

舉一個扣費的例子,咱們作一個扣費系統,扣費時須要從外部的訂價系統中讀取一個費率的值,咱們預期該接口的返回值中會包含一個浮點型的費率字段。當咱們取到這個值得時候就使用公式:金額*費率=費用 進行計算,計算結果進行劃扣。

 

若是因爲計費系統異常,他可能會返回個默認值,若是這個字段是Double類型的話,該默認值爲null,若是該字段是double類型的話,該默認值爲0.0。

 

若是扣費系統對於該費率返回值沒作特殊處理的話,拿到null值進行計算會直接報錯,阻斷程序。拿到0.0可能就直接進行計算,得出接口爲0後進行扣費了。這種異常狀況就沒法被感知。

 

這種使用包裝類型定義變量的方式,經過異常來阻斷程序,進而能夠被識別到這種線上問題。若是使用基本數據類型的話,系統可能不會報錯,進而認爲無異常。

 

以上,就是建議在POJO和RPC的返回值中使用包裝類型的緣由。

 

在以前那篇文章的解析中,做者的觀點是,對於布爾類型的變量,我認爲能夠和其餘類型區分開來,做者並不認爲使用null進而致使NPE是一種最好的實踐。由於布爾類型只有true/false兩種值,咱們徹底能夠和外部調用方約定好當返回值爲false時的明確語義。

本文來自微信公衆號:Hollis

相關文章
相關標籤/搜索