为何强烈禁止开发人员使用isSuccess做为变量名

在平常开发中,咱们会常常要在类中定义布尔类型的变量,好比在给外部系统提供一个RPC接口的时候,咱们通常会定义一个字段表示本次请求是否成功的。java

关于这个"本次请求是否成功"的字段的定义,实际上是有不少种讲究和坑的,稍有不慎就会掉入坑里,做者在好久以前就遇到过相似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。编程

通常状况下,咱们能够有如下四种方式来定义一个布尔类型的成员变量:json

boolean success
boolean isSuccess
Boolean success
Boolean isSuccess

以上四种定义形式,你平常开发中最经常使用的是哪一种呢?到底哪种才是正确的使用姿式呢?安全

经过观察咱们能够发现,前两种和后两种的主要区别是变量的类型不一样,前者使用的是boolean,后者使用的是Boolean。oracle

另外,第一种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。app

首先,咱们来分析一下,到底应该是用success来命名,仍是使用isSuccess更好一点。框架

success 仍是 isSuccess

到底应该是用success仍是isSuccess来给变量命名呢?从语义上面来说,两种命名方式均可以讲的通,而且也都没有歧义。那么还有什么原则能够参考来让咱们作选择呢。ide

在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:工具

this

那么,为何会有这样的规定呢?咱们看一下POJO中布尔类型变量不一样的命名有什么区别吧。

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;
    }
}

以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现如下规律:

  • 基本类型自动生成的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。

那这样作会带来什么问题呢。

在通常状况下,实际上是没有影响的。可是有一种特殊状况就会有问题,那就是发生序列化的时候。

序列化带来的影响

关于序列化和反序列化请参考Java对象的序列化与反序列化。咱们这里拿比较经常使用的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 With Gson :{"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开发手册这条规定的评价:

因此,在定义POJO中的布尔类型的变量时,不要使用isSuccess这种形式,而要直接使用success!

Boolean仍是boolean?

前面咱们介绍完了在success和isSuccess之间如何选择,那么排除错误答案后,备选项还剩下:

boolean success
Boolean success

那么,到底应该是用Boolean仍是boolean来给定一个布尔类型的变量呢?

咱们知道,boolean是基本数据类型,而Boolean是包装类型。关于基本数据类型和包装类之间的关系和区别请参考一文读懂什么是Java中的自动拆装箱

那么,在定义一个成员变量的时候究竟是使用包装类型更好仍是使用基本数据类型呢?

咱们来看一段简单的代码

/**
 * @author Hollis
 */
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时的明确语义。

后来,做者单独和《阿里巴巴Java开发手册》、《码出高效》的做者——孤尽 单独1V1(qing) Battle(jiao)了一下。最终达成共识,仍是尽可能使用包装类型

可是,做者仍是想强调一个个人观点,尽可能避免在你的代码中出现不肯定的null值。

null何罪之有?

关于null值的使用,我在使用Optional避免NullPointerException9 Things about Null in Java等文中就介绍过。

null是很模棱两可的,不少时候会致使使人疑惑的的错误,很难去判断返回一个null表明着什么意思。

图灵奖得主Tony Hoare 曾经公开表达过null是一个糟糕的设计。

我把 null 引用称为本身的十亿美圆错误。它的发明是在1965 年,那时我用一个面向对象语言( ALGOL W )设计了第一个全面的引用类型系统。个人目的是确保全部引用的使用都是绝对安全的,编译器会自动进行检查。可是我未能抵御住诱惑,加入了Null引用,仅仅是由于实现起来很是容易。它致使了数不清的错误、漏洞和系统崩溃,可能在以后 40 年中形成了十亿美圆的损失。

当咱们在设计一个接口的时候,对于接口的返回值的定义,尽可能避免使用Boolean类型来定义。大多数状况下,别人使用咱们的接口返回值时可能用if(response.isSuccess){}else{}的方式,若是咱们因为忽略没有设置success字段的值,就可能致使NPE(java.lang.NullPointerException),这明显是咱们不但愿看到的。

因此,当咱们要定义一个布尔类型的成员变量时,尽可能选择boolean,而不是Boolean。固然,编程中并无绝对。

总结

本文围绕布尔类型的变量定义的类型和命名展开了介绍,最终咱们能够得出结论,在定义一个布尔类型的变量,尤为是一个给外部提供的接口返回值时,要使用success来命名,阿里巴巴Java开发手册建议使用封装类来定义POJO和RPC返回值中的变量。可是这不意味着能够随意的使用null,咱们仍是要尽可能避免出现对null的处理的。


原文连接 本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索