用了几天时间将阿里巴巴Java开发手册第一章学习完成了,在学习的过程中,我发现这里面好多的规约都是上学时学过的Java基础里的知识,还有一些是老师强调的编程习惯。而第一章的内容也是之前上学的时候接触的比较多的一块知识点了,但是在学习过程中也学到了很多之前从来没有注意过的知识和编程习惯,下面就总结了一些我新学到的和编程时经常忽略的规约。
1、命名风格
常量命名必须全部大写,单词之间用下划线隔开,力求语意表达完整清楚,不要嫌名字长。(常量名需要大写这个是我们平时学习的时候也会遵守的规则,但是对于语意表达完整是我之前很少考虑的问题。由于学习时写的项目都是小项目,通常是一个人或者两三个人写的,也没有别人会去看,所以对于命名这块一般都是自己能看懂就行,经常缩写乱写,所以这块需要格外注意。)
抽象类命名使用Abstract或Base开头(我之前从未将抽象类与普通类做过命名上的区分,而抽象类作为基类,应该被给予明显的被继承标识,这样才能让开发人员不用进入该类就能知道他是一个抽象类)
POJO类中布尔类型变量都不要加is前缀,否则部分框架解析会引起序列化错(这个是前辈们的经验之谈了,虽然我没遇到过这类问题,但是遵守就好了嘛,这样也能避免出现方法名和属性名混乱的情况。)
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式 (这一点一定要牢牢牢的记住,乱七八糟的命名真的会让读你代码的人事倍功半,尤其是用拼音的方式,有些词真的拼不出来,我是深受其害过的😭)
2、常量定义
不允许任何魔法值(即未预先定义的常量)直接出现在代码里。(这类问题我以前会经常犯,现在知道了,直接用容易出现漏掉某个自符的可能,给系统带来不必要的bug)
在long或者Long赋值时,数值后使用大写的L,不能是小写的l,小写容易跟数字 1 混淆,造成误解。(这也是强制要求的,不仅仅是大小写要注意,如果返回值是Long型的时候,记得返回数字的时候后面也要加L,例如 return -1L;)
3、代码格式
左大括号前加空格且不换行,右大括号前换行,右大括号后面有else则不换行,如果是直接结束,则需要换行。这里特别需要注意的就是左大括号前需要加空格。
方法参数在定义和传入时,多个参数逗号后边必须加空格。
可以看到,所有自动生成的方法都是严格按照上述两条规则生成的,这也是我最近写代码结束后,回头看代码的时候都要注意改的地方,这是一个习惯的问题,需要养成。
4、OOP规约
所有整型包装类对象之间值的比较全部使用equals方法比较,这里值得注意的是Integer类型在-128~127之间存在缓存,在这个数值里的数字可以用==进行比较,一旦出了这个范围,就必须使用equals方法进行判断了,所以还是使用equals方法比较整型包装类对象的值更为妥当。
浮点数之间的等值比较,基本数据类型不能用==,包装数据类型不能用equals来判断,注意,这里说的是不能,不是不推荐。==和equals的判断方法是错误的,因为二进制无法精确表示大部分的十进制小数,所以小数的比较是存在误差的,应该指定一个误差范围,或者使用BigDecimal 来定义值。
所有的POJO类属性必须使用包装数据类型,RPC方法的返回值和参数必须使用包装数据类型,而局部变量使用基本数据类型。这也是我新学到的东西,之前都是随便写的,觉得反正包装类和基本类型是自动装箱和自动拆箱的嘛。
使用索引访问用String的split方法得到的数组是,需做最后一个分隔符后有无内容的检查,否则会有跑出IndexOutBoundsException的风险。这是我从来都没有注意到过的一个问题,看到这条的时候我还特意去试了一下,真的会抛出异常,代码如下,可以自己去试试:String str = "a,b,c,,";
String[] ary = str.split(",");
System.out.println(ary.length);
类方法定义的顺序依次是公有或保护>大于私有>getter/setter方法。但是如果一个类有多个构造方法或者重名方法需要按顺序放在一起,可以越过第一句话的优先级。这也是为了提高代码的阅读效率,将最容易被人阅读的方法放在最上面。
5、集合处理
ArrayList的 subList不可以强转成ArrayList,否则会抛出类型转换异常。因为subList返回是的ArrayList的内部类SubList,而不是ArrayList,对于subList子列表的所有操作最终会反映到原列表中。
在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。防止NPE是程序员的基本素养,要对可能出现空指针的场景进行判断,避免不必要的麻烦。
泛型通配符<?extendsT>来接收返回的数据,此写法的泛型集合不能使用add方法,而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。
在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof 判断,避免抛出 ClassCastException 异常。对于泛型这块的知识,学习的时候并没发现还有出现异常的这些情况,就觉得挺简单的,一扫而过了。看到这儿才发现还有这些需要特别处理的情况,看来不论是简单的知识还是复杂的知识,都有需要特别注意的知识点呀。
不要再foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果有并发操作,需要对Iterator对象加锁。
6、并发处理
并发处理这块是我在学习的时候接触的比较少的地方,只看过一些知识点,实操比较少。那提到并发,我最先想到的就是安全问题。下面也列了几个需要重点注意的地方。
SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。加锁虽然能保证一定的安全,但同时也代表了一部分的系统开销,而且还容易出现死锁现象,所以能用无锁数据结构就不要用锁,能锁区块就不要锁整个方法体,能用对象锁就不要用类锁,使加锁代码块工作量尽可能的小。
并发加载同一记录的时候,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。
资金相关的金融敏感信息,使用悲观锁策略。乐观锁在获得锁的同时已经完成了更新操作,校验逻辑容易出现漏洞,另外,乐观锁对冲突的解决策略有较为复杂的要求,处理不当容易造成系统压力或数据异常,所以资金相关的金融敏感信息不建议使用乐观锁更新。
使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果。子线程抛出的异常堆栈不能再主线程try catch到。划重点咯,这是一个容易记错但在考试的时候容易碰到的知识点。
避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一个seed导致性能下降。在JDK1.7之后,可以直接使用API ThreadLocalRandom,在JDK1.7之前,需要编码保证每个线程持有一个实例。
7、控制语句
在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码都没有。注意,break退出的是switch块,return退出的是方法体。而且每个switch块内必须有一个default,当里面没有代码的时候是容易被开发人员忽略的,一定要有嗷。
当switch括号内的变量类型为String并且此变量为外部参数的时候,必须先进行非空判断。否则一旦出现空值,程序将抛出异常,所以记得要特殊处理,switch块也是在JDK1.7之后才开始支持String类型的
在高并发场景中,避免使用“等于”判断作为中断或推出的条件,如果并发控制没有处理好,容易产生等值哦安段被击穿的情况,应使用大于或小于的区间判断条件来代替。
表达异常的分支时,少用if- else方式可以改写成if后接着写else的业务逻辑代码。如果非要写,那必须不超过3层。超过3层的if- else逻辑判断代码可以使用卫语句、策略模式状态模式等来实现。(卫语句就是代码逻辑先考虑失败、异常、中断、退出等直接返回的情况,一方法多个出口的方式,解决代码中判断分支嵌套的问题,这是逆向思维的体现。)
循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try- catch操作等。
不要再其他表达式(尤其是条件表达式)中,插入赋值语句。例如:
return (sync = fair) ? new FairSync() : new NonfairSync(); 这样很容易让别人误认为是==操作。而理解错代码的意思(我第一次读这句代码的时候就真的给看成是==操作了😂,踩过的实坑)
8、注释规约
类、类属性、类方法的注释必须使用Javadoc规范,使用/** 实体 */格式,不得使用//的方式。在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;在 IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。
所有的类必须添加创建者和创建日期。
所有的枚举字段必须要有注释,说明每个数据项的用途。
代码修改时,注释也要修改。尤其是参数、返回值、异常、核心逻辑等。如果没有及时修改,注释就失去原本存在的意义了。
对于注释代码也是有一定规则的,如果是无用的代码应该删除而不是注释掉。注释代码的时候一定要在上面写明为什么要注释这段代码。注释是给自己看的,即使间隔很长时间,也能清晰理解当时的思路,注释也是给继任者看的,使其能够快速接手自己的工作。所以在注释里面一定要准确反映设计思想和代码逻辑,描述业务的含义,使别的程序员能迅速了解代码背后的信息。
这块的知识虽然不是跟技术水平有很大联系的,确是团队协作极其重要的一部分,不写注释或者注释不写明白是非常过分的事情,这会让其他读你代码的人耗时耗力,严重影响效率。对自己来说,如果时间长了也是很容易忘记当时写代码时的思路。注释一定要写,非常重要的🧐
9、其它
后台输送给页面上的变量必须加$!{var}--中间的感叹号。如果 var 等于 null 或者不存在,那么${var}会直接显示在页面上。
Math.random()这个方法返回的是double类型,取值范围时[0,1),(能够取到0值,别忘了做除零异常),如果想要获取整数类型的随机数,不要将数值方法10倍或者若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。所以我从学的时候开始就一直用的都是错误的方式🌚。
日期格式化时:yyyy表示当天所在的年;YYYY是当天所在的周属于的年份,一周从周日开始周六结束,如果本周跨年,那YYYY返回的就是下一年;M表示月份;m表示分钟;H是24小时制;h是12小时制。表示日期和时间的正确格式为:
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
任何数据结构的构造和初始化,都应指定大小,避免数据结构无限增长吃光内存。
总结
在学习这章的过程中,我最大的感触就是学习和实践的区别,这个实践不是我学习的时候写过多少项目,而是企业项目与个人项目的区别。企业里的项目要考虑性能,要做很多的非空判断,要写注释,要考虑别人看我的代码的情况等等的问题。学生是对自己负责,职场人是对公司、对团队负责。
以上就是我认为Java开发手册第一章编程规约中值得注意的点和容易出现错误的地方,如有不妥的地方还请大佬指教。