开发团队在开发过程中,由于每个人的开发习惯,以及对于技术的理解深浅程度不一,往往一个项目在开发过程中,代码的质量,代码的风格都不尽相似,所以有一份适合团队的代码规范是非常有必要的,而一个团队的代码规范,包含了开发常见的风格习惯以及一些常见代码细节的写法规范等,本篇就来浅谈一些代码规范涉及的技术细节和对这些部分的思考。

命名规范

相信经历过项目开发的人都知道,在开发过程中会涉及无数次的申明操作,这个过程中最让人头疼的就是给申明的文件(实例)起个名字了。名字需要准确的表达出背后代表的含义,并且还要通俗易懂,使得代码干净漂亮,否则不好的命名反而成为开发的阻碍,干扰维护者和开发者的思路。那么一个好的命名会给我们开发带来什么好处呢?

  1. 为标识符提供附加的信息,赋予标识符现实意义。帮助我们理顺编码的逻辑,减少阅读和理解代码的工作量;

  2. 使代码审核变得更有效率,专注于更重要的问题,而不是争论语法和命名规范这类小细节,提高开发效率;

  3. 提高代码的清晰度、可读性以及美观程度;

  4. 避免不同产品之间的命名冲突。

那么常见的命名方式有哪些呢?根据各大规范和Java框架主流的方案来说,一般分为四种:

驼峰命名法

驼峰命名法基本上是各大企业使用最多,也是各大规范首推的命名方式。其使用大小写混合的格式,单词之间不使用空格隔开或者连接字符连接的命名方式,因此发展处两种格式:大驼峰命名法(UpperCamelCase)和小驼峰命名法(lowerCamelCase)

这两种命名方式的区别主要在第一个单词的首字母上,大驼峰命名法则是首字母大写命名,而小驼峰则是首个单词的首字母小写,比如:firstName, toString等。在jdk中参照了谷歌制定的驼峰命名转换规则,用来细分不同情况下的驼峰转换:

1.从正常的表达形式开始,把短语转换成 ASCII 码,并且移除单引号,如“Müller’s algorithm”转换为“Muellers algorithm”

2.如果存在连接符号,就将连接符开始分割为两个单词,如果某个分割前某个单词已经是驼峰命名,也拆分为小写的两个单词,如:AdWords会转换为ad words,而non-current则转换为non current 等

3.将所有的字母转为小写字母,每个单词的首字母大写,就转换为了大驼峰命名/首字母小写则转为小驼峰

4.将所有的单词连接在一起,即为标示符命名

例如下面的转换案例:

蛇形命名法

蛇形命名法在Java中极少见到,一般为每个单词之间都通过‘’进行连接,例如‘outof’

串式命名法

串式命名法和蛇形规则一样,唯一区别是,每个单词之间通过'-'连接,例如'out-of'

匈牙利命名法

匈牙利命名法在Java早期的框架中开始出现,由一个或者多个小写字母开始,使用这些字母作为标示符,用来标记当前命名的变量的用途,例如:usName(表示是用户的名称),lAccountNum(表示是Long类型的长整数)等

而在jdk中,针对每一种类型的命名有特定的规范,针对每一种编码规范来组合使用在不同场景的命名中,如下:

总结下来,jdk命名遵循了三点:

1.命名有准确的意义,绝不使用单词缩写或者单词的部分,例如GoodsItem,绝不会命名为GdItem

2.严格遵守命名规范,决不允许一个规则内出现多个规范混用的情况,例如在一个命名中同时出现驼峰命名与蛇形命名等

3.尽量将可读性的命名放在前面,开发者的习惯一般都是从左到右开始阅读和编码,所以将能体现出想要的信息的内容优先放在前面,例如BeijingTime和TimeBeijing的区别

变量申明的时机

前面我们说过命名的规范,那么申明变量是否也需要规范呢?其实也需要,例如现在申明一个类型的变量的时候,往往有人喜欢一个类型的变量在一行内申明完毕,例如:

int size, length;

甚至于出现了一行申明了七八个属性的情况,或者是在一行内申明了好几个类型的变量,例如:

int size,entity[];//一行申明多个不同类型变量

看起来代码似乎节省了,但是对于开发和维护来说,其实反而更容易忽略错误,更重要的是申明类型是数组的时候不要把基本类型和[]分开,因为int[] 才是代表了一个类型的整体,分开申明容易被忽略,或者埋下隐患的错误,所以往往建议每一行仅申明一个变量,如下:

int size;

int[] entity;

在开发中往往还存在另外一个情况,就是方法内申明局部变量的时候,往往喜欢在方法开始的时候就创建或者申明该变量,但是使用的时机往往在n行代码以后,甚至于到后面这个申明的变量并没有使用到,由于间隔太远,也没有关注,后面就成了一个死变量,这种情况是很多见的,而反观jdk的规范中,可以看到都是在需要使用变量的时候创建,或者在需要使用的前几行代码申明再去创建,例如:

public void test(String userName){

Account userAccount;

String groceryStoreName;

//中间一堆业务代码和操作

/*****

****

***/

//通过用户名获取userAccount

userAccount = AccountManager.getUserAccount(userName);

if(userAccount == null){

//为null的操作,抛异常

}

//再去获取名称

groceryStoreName = userAccount.getGroceryStoreName();

if(groceryStoreName == null){

//为null,抛异常

}

//后续一堆业务代码

}

但是我们看下规范后的写法:

public void test(String userName){

//中间一堆业务代码和操作

/*****

****

***/

//通过用户名获取userAccount

Account userAccount = AccountManager.getUserAccount(userName);

if(userAccount == null){

//为null的操作,抛异常

}

//再去获取名称

String groceryStoreName = userAccount.getGroceryStoreName();

if(groceryStoreName == null){

//为null,抛异常

}

//后续一堆业务代码

}

很明显的可以看出来,代码更清晰明了,也更有逻辑性。另外在申明类属性变量的时候,我们建议将变量申明在一起,分块存放,不建议在类中变量和方法混合在一起使用,例如:

另外在申明类变量的时候,切记不要忘记类变量如果是基础类型,会有默认值,如非必要,在类属性创建中建议使用包装类型,防止因默认值带来的数据不一致等问题,而在方法内创建局部变量的时候,由于基本类型变量没有默认值,需要手动申明值,反而建议使用基本类型,而不是使用包装类,这样同样也可以尽量避免无意的拆箱、装箱行为,在数十万次百万次的情况下,对于程序也会造成一定的影响。

if与大括号

if语句是我们开发中最常见的逻辑分支语句之一,同样的在java中if也会有一些简洁写法,例如逻辑业务仅有一行代码的时候,我们可以省去大括号,直接在if下一行编写业务代码,如下:

if(flag)

count ++;

//if以外的逻辑

user.setAge(10);

......

但是熟悉规范的都知道,无论是阿里规范还是jdk的规范,都不推荐使用简化代码,这是为什么呢?这让我想起了2014年苹果的ios系统爆出来的一个严重安全漏洞(“GoTo Fail 漏洞”),而这个漏洞就和大括号有关系,而对应漏洞的代码大概可以理解为这样:

if ((error = doSomething()) != 0)

goto fail;

//无论如何都是走到这里,下面再也触发不了了

goto fail;

if ((error= doMore()) != 0)

goto fail;

fail:

return error;

是不是看出来什么了?没错,如果前面的条件生效,就会跳转到fail的操作,返回error,但是如果不满足也会跳转到fail,那么也就是说后续的业务代码无论如何也触发不了了,其实了解这个问题的人其实大概可以猜出来,这里就是多写了一个goto fail;导致编译器认为了别的业务代码,但是假设我们加了大括号,这个问题就会迎刃而解,例如:

if ((error = doSomething()) != 0)

{

goto fail;

//无论如何都是走到这里,下面再也触发不了了

goto fail;

}

if ((error= doMore()) != 0)

goto fail;

fail:

return error;

其实这个时候就会发现即使是多写了一行代码,也不会影响整个业务的逻辑,减少了bug产生。看到这里我们似乎明白了,为什么各大规范都建议不省略大括号的写法了。

包装类与基本类型

做Java开发的都知道,Java中默认有八种基本类型,但是同样的也有八种对应的包装类型,很多时候企业开发和使用的时候对于包装类型和基本类型的使用并不规范,往往会导致一部分小的隐患的发生。前面我们有介绍建议在类属性申明的时候使用包装类型,而在方法内建议使用基本类型,这里我们可以再去思考两个开发的时候常用的使用场景:

1.判断两个数值类型的值是否相等

2.创建数值类型

看过阿里手册和JDK规范的应该知道,里面都有一条规范,明确指出基本数值类型的包装类型在比较的时候不允许使用==的方式,而是使用equals,这是为什么呢?我们来看看一个例子:

Integer a = 100, b = 100, c = 150, d = 150;

System.out.println(a == b);//true

System.out.println(c == d);//false

可以看到两个Integer类型的变量,值一样的情况下,==比较的结果居然是false?我们通过断点的方式知道 Integer var = ? 形式声明变量,会通过 java.lang.Integer#valueOf(int) 来构造 Inte ger 对象,我们来看看valueOf方法的源码:

public static Integer valueOf(int i) {

if (i >= IntegerCache.low && i <= IntegerCache.high)

return IntegerCache.cache[i + (-IntegerCache.low)];

return new Integer(i);

}

可以看到,会去判断value的值是否在IntegerCache的范围内,如果在,就会使用IntegerCache中缓存的实例,不存在才会创建新的Integer实例,这个缓存的值,默认是-128到127之间,并且是可以通过配置环境变量的方式动态改变的,这点可以从IntegerCache源码中看到:

privatestaticclassIntegerCache{

staticfinalint low = -128;

staticfinalint high;

staticfinalInteger cache[];

static{

// high value may be configured by property

int h = 127;

String integerCacheHighPropValue =

sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");

// 省略其它代码

}

// 省略其它代码

}

从这我们也可以看出问题2的答案,为什么很多规范都推荐构建实例的时候是Integer a = 5;的形式,而不是new Integer(5);的原因,可以减少实例的创建,复用缓存对象。接着我们再来看第一个问题,==比较和equals比较的区别在哪?我们知道==比较的是两个实例对象的内存地址,而equals则是比较的具体的实现,而基本类型的包装类实现实例如果不在缓存范围内,肯定不是同一个对象,逻辑上内存地址肯定是不一样的,所以==在超过缓存范围后,比较的结果并不准确,那么我们该如何比较呢?事实上,基本类型的包装类中都有获取具体value的方法,例如Integer中就有intValue的方法,获取具体的值,类型为基本类型,这样我们再去==比较就可以了,那么equlas方法为什么可以比较呢?我们就拿Long类型的equals方法的源码来看一下具体实现:

public boolean equals(Object obj) {

if (obj instanceof Long) {

return value == ((Long)obj).longValue();

}

return false;

}

可以看到这类包装类型的比较其实也就是我们上述说的获取具体value值以后再去==比较的操作。

空指针

空指针基本是每个Java开发人员最恶心的异常也是见过最多的异常之一,可能出现在各种业务代码和场景中,在阿里规范手册中,有很多针对空指针的规范和处理,如下:

【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:

  1. 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。反例:public int f () { return Integer 对象}, 如果为 null,自动解箱抛 NPE。

  2. 数据库的查询结果可能为 null。

  3. 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。

  4. 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。

  5. 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。

  6. 级联调用 obj.getA ().getB ().getC (); 一连串调用,易产生 NPE。

可见空指针出现的场景可能会有很多,而在开发中一些必要的检查,减少空指针是每个程序员都应该有的素质,但是有些不规范的操作或者疏忽可能会导致空指针的诞生,例如:

1.服务交互信息不规范的坑:一个经典的接口服务交互的场景下,往往有时候会将服务中的异常进行try-catch处理,返回的是一个固定的result封装实例,这种情况下,如果内部属性设置不规范很容易在调用方使用返回实例进行操作的时候因为疏忽导致空指针异常。

2.返回实例的坑:还有一些服务的代码编写过程中,部分开发人员有自己的个性写法,例如数据库查询某个数据的时候,如果查询不出结果集,并不是返回null,而是创建一个空的实例,进行返回,这一下可好,调用方无论怎么校验空指针都会在使用getxxx方法获取到的属性进行操作的时候报空指针异常,除非调用方将内部所有的get返回的结果都去进行一次空指针判断,或者根据某几个唯一属性确认实例是否为空等,但无论如何操作,都无法避免可能存在的大量的空指针。

3.自动拆箱装箱的坑:在企业开发的过程中,往往存在大量的实例转换操作,这个时候我们往往是通过工具类进行转换,但是有时候我们的实例是存在于两个工程内的,往往有时候因为是两个人定义的,同样名称的类变量,但是类型一个是基础类型,一个是包装类型,这个时候往往我们下意识会觉得java会自动拆箱装箱,所以没关系的,肯定会转换过去的,再或者基本类型有默认值的,肯定不会出现空指针,想法很美好,但是事实真的如此吗?我们看一个例子:

@Data

public class GoodCreateDTO {

private String title;

private Long price;

private Long count;

}

@Data

public class GoodCreateParam implements Serializable {

private static final long serialVersionUID = -560222124628416274L;

private String title;

private long price;

private long count;

}

这个时候我们潜意识中会认为外部接口的变量都是包装类型或者引用类型,所以我们在实现了类似如下的转换代码的时候就容易出现空指针操作:

public class GoodCreateConverter {

public static GoodCreateParam convertToParam(GoodCreateDTO goodCreateDTO) {

if (goodCreateDTO == null) {

return null;

}

GoodCreateParam goodCreateParam = new GoodCreateParam();

//赋值操作

goodCreateParam.setTitle(goodCreateDTO.getTitle());

goodCreateParam.setPrice(goodCreateDTO.getPrice());

goodCreateParam.setCount(goodCreateDTO.getCount());

return goodCreateParam;

}

}

但是如果在传递来的实例中,count不是必传参数,可能存在null的时候,这个时候我们使用getCount操作,由于获取的类型是包装类型,而我们需要赋值的是基本类型,这个时候就会触发自动拆箱装箱,null的拆箱就会报空指针异常!

往期精选

CHOICENESS

是兄弟,就来“kan”

java变量命名规则_浅谈JAVA开发规范与开发细节(上)相关推荐

  1. java null什么意思_浅谈java中null是什么,以及使用中要注意的事项

    1.null既不是对象也不是一种类型,它仅是一种特殊的值,你可以将其赋予任何引用类型,你也可以将null转化成任何类型,例如: Integer i=null; Float f=null; String ...

  2. java 中的排序_浅谈java中常见的排序

    浅谈java中常见的排序 学过java的人都知道,排序这一部分初次接触感觉还是有点难以理解,很多地方也会用到.然而,在java中常见的排序方法:冒泡排序,选择排序,插入排序等等.下面就让我们一起揭开他 ...

  3. java 对象之间转换_浅谈java对象之间相互转化的多种方式

    浅谈java对象之间相互转化的多种方式,对象,属性,参数,赋值,不支持 浅谈java对象之间相互转化的多种方式 易采站长站,站长之家为您整理了浅谈java对象之间相互转化的多种方式的相关内容. 第一种 ...

  4. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

  5. java编程double相乘_浅谈Java double 相乘的结果偏差小问题

    看下面的一段代码的运行结果: public class TestDouble { public static void main(String[] args) { double d =538.8; S ...

  6. java布尔类型比较器_浅谈Java中几种常见的比较器的实现方法

    在java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题. 通常对象之间的比较可以从两个方面去看: 第一个方面:对象的地址是否一样,也就是是否引用自同一个对象.这种方式可以直接使用& ...

  7. java定义byte数组_浅谈java的byte数组的不同写法

    (由于篇幅原因阐述的不够详细科学,不喜勿喷). 经常看到java中对byte数组的不同定义,粗略整理的一下: 一个字节(byte)=8位(bit),"byte数组"里面全部是&qu ...

  8. java变量命名规则_变量的概念和声明

    变量就是声明来保存数据的内存空间,在js中声明变量使用的是var关键字.而且在js中声明的变量可以保存任何类型的数据,就是没有指定某个变量只能保存对应的类型的数据,所以js被叫做弱类型语言. < ...

  9. java继承和引用_浅谈Java继承、传递

    一.继承 1. 继承的概念: 子类继承父类,表明子类是一种特殊的父类,并且具有父类所不具有的一些属性或方法. 2. 继承中的初始化顺序: 从类的结构上而言,其内部可以有如下四种常见形态:属性(包括类属 ...

最新文章

  1. Java IO流-File类
  2. spring mvc框架设计与实现
  3. 数据结构与算法 / 冒泡排序最坏情况下的时间复杂度解析
  4. tomcat 启动时内存溢出
  5. 在Python,Java和Kotlin中标记参数和重载
  6. 让 UV4 支持STC 单片机
  7. java 中括号中的语句,Java中是使用大括号括起来的语句块,用于完成一个相对独立的逻辑功能,这种语句被称作()。...
  8. 专科python应届生工资多少-Python这么火热,本科应届生薪资这么高?
  9. radiobutton 设置为不能点击_谷歌要求:安卓 11 相机默认不能设置为“美颜”模式...
  10. 【Centos】【Python】【Flask】阿里云上部署一个 flask 项目
  11. 尚硅谷springSecurity笔记
  12. c语言头文件和函数库,C语言的头文件和库文件(函数库)
  13. 面试官:如何实现单行/多行文本溢出的省略样式?
  14. 信号与系统(20)-拉普拉斯变换的性质
  15. 个人博客系统的设计与实现
  16. 指针的大小与什么有关
  17. Dreamweaver2019版安装教程
  18. 关于有偿提供拼图响应式后台的通知
  19. ht城市介绍人口数量Html,人口规模
  20. 别了,我的程序员生涯!

热门文章

  1. mysql数据库维护_维护MySQL数据库表
  2. 隐藏linux操作系统版本信息,linux centos 如何查看操作系统版本信息?
  3. 【STC15库函数上手笔记】7、PCA与PWM
  4. Linux下test命令使用
  5. 在Linux下编写C程序,怎么检查程序是否有内存泄漏?
  6. ubuntu server修改系统时区和系统时间
  7. el-input 输入框类型;只能输入数字的输入框;保留两位小数输入框;只能输入正整数和0的输入框;手机号正则校验;车牌号码正则校验
  8. [react] react是哪个公司开发的?
  9. Taro+react开发(5)--tora项目开发安装
  10. 前端学习(2964):element-ui的制作