衡量软件质量:
功能性,即软件是否满足了客户业务要求;
可用性,即衡量用户使用软件需要付出多大的努力;
可靠性,即软件是否能够一直处在一个稳定的状态上满足可用性;
高效性,即衡量软件正常运行需要耗费多少物理资源;
可维护性,即衡量对已经完成的软件进行调整需要多大的努力;
可移植性,即衡量软件是否能够方便地部署到不同的运行环境中;
提高代码质量需要注意的点:
避免重复代码,减少复制+写成函数里方便调用
变量名,要容易理解
单元测试,用一些边界值+null值来测试
用命名常量来代表一个特定数字,而不是直接嵌入
局部变量尽量用途单一
如果能用短小函数描述,则使用子函数替代注释本身
保证一定的单元测试覆盖率,这个也可以在 CI 上做,低于一定覆盖率不让通过
Code Review
静态检测、动态检测
方法参数列表,不宜过长;如果多个相关的参数可以封到一个类里面
重构函数(写leetcode时训练):
(1)、过长 (2)、嵌套层数过深。
(3)、自然分块,需要使用注释描述该程序块
(4)、判断条件过于复杂 (5)、参数过于复杂
最后:如果产品需求不停的改or上线时间紧迫,天王老子来了也的写烂代码(太赶了=>没有资格优雅;天天变or复用少=>不需要优雅高效)
前言
优秀的代码:简洁清晰、可维护、可测试、可移植
风格一致性 原则:尽量风格统一
命名
少用缩写
有集合含义的属性,包含复数形式
编码
源文件 编码格式 必须是UTF-8, ASCII 水平空格字符(0x20, 即空格)是唯一允许出现的空白字符;制表符不用于缩进。
Java缩写词,也用驼峰:supportsIpv6OnIos
命名风格
- 大驼峰:类、接口、注解
- 小驼峰:方法、局部变量
- 命名全大写:静态常量、枚举值
注释
文件头注释:版权信息、日期、功能说明
方法等注释:
Javadoc用于每一个public或protected修饰的元素
@param @return @throws 顺序来
不用的import直接删除,不要注掉
// TODO(<author-name>): 补充xx内容 // FIXME:修复xx缺陷
排版格式
避免文件过长,净含量不超过2000行(非空非注释)
==》横向拆分:根据职责;
==》纵向拆分:根据层次
- 每行不超过一个语句
- 每行限长120(英文)窄字符==60汉字字符(也叫全角字符、东亚字符)
- 行位、空行不能有空格
- 不要用C风格数组声明:
- 方括号构成类型的一部分,是:
String[] args
,而不是String args[]
变量和类型
浮点数不能为循环因子,精度问题会导致 (float) 2000000000 == 2000000050为true
浮点数在一个范围很广的值域上提供了很好的近似,但是它不能产生精确的结果。涉及精确的数值计算(货币、金融等),建议使用int, long, BigDecimal等
浮点型数据判断相等不能直接使用==
Math.abs(foo - bar) < EPSILON
//float时用:1e-6f //double时用:1e-6d
count = count++; //count并没有加
float f1 = 1.0f - 0.9f; float f2 = 0.9f - 0.8f; if (f1 == f2) { // 预期进入此代码快,执行其它业务逻辑 // 但事实上 fl == f2 的结果为 false } Float flt1 = Float.valueOf(f1); Float flt2 = Float.valueOf(f2); if (flt1.equals(flt2)) { // 预期进入此代码快,执行其它业务逻辑 // 但事实上 equals 的结果为 false }
做浮点运算前把整数转换为浮点数
不好的例子:
short ns = 533; int ni = 6789; long nl = 4664382371590123456L; float f1 = ns / 7; // f1 is 76.0 (truncated) double d1 = ni / 30; // d1 is 226.0 (truncated) double d2 = nl * 2; // d2 is -9.1179793305293046E18 // because of integer overflow
推荐例子:
short ns = 533; int ni = 6789; long nl = 4664382371590123456L; float f1 = ns / 7.0f; // f1 is 76.14286 double d1 = ni / 30.; // d1 is 226.3 double d2 = (double) nl * 2; // d2 is 9.328764743180247E18
方法
方法设计的精髓:
方法是可组合、可重用的代码最小单位
建议:
- 方法净长度50行内
- 嵌套不超过4层
- 方法的参数不超过5个
- 拆分函数
- 相关参数组合封成类
接口
在接口定义中
- 属性已默认具有public static final修饰词
- 方法已默认具有public abstract修饰词
因此在代码中不要再次提供这些修饰词。
异常
- 对可容错处理的情况使用受检异常(checked exception),
- 对编程错误使用运行时异常(runtime exception)。
- 只针对真正异常的情况才使用exception机制。
尽量抛出详细的异常 来提供信息,而不是 基类Exception
日志
尽量使用slf4j而不是sout
不好的例子:使用控制台打印
start = System.currentTimeMillis(); // 其他加载数据的代码 System.out.println ("items loaded, use " + (System.currentTimeMillis() - start) + "ms.");
推荐例子:采用日志工具(例如slf4j+logback)
start = System.currentTimeMillis(); // 其他加载数据的代码 LOGGER.info("items loaded, use {}ms." , (System.currentTimeMillis() - start));
此外,日志应该分等级
说明:如果日志不分等级,则定位问题时,无法快速有效屏蔽大量低级别信息,给快速定位带来难度。
日志可分为以下级别:trace(有的也叫verbose)、debug、info、warning、error、fatal。
推荐与具体实现有关的日志记录trace或debug级,一般的业务处理日志用info级,不影响业务进行的错误用warning级,例如用户输入参数错误,
而error或fatal级,只记录系统逻辑出错、异常或者重要的错误信息,常常向运维系统报警。
建议生产环境不输出trace或debug日志;有选择地输出info日志;输出warning、error、fatal日志。
编程实践
JUC多线程并发(简写)
Java 8使用stream做隐式的自动并行化,替代显式的循环。
对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体; 能用对象锁,就不要用类锁。
避免在锁代码块中调用 RPC方法。
运算和表达式
括号:用括号明确表达式的操作顺序,避免过分依赖默认优先级。(已经是这样了)
表达式的比较,原则:“左变量 右常量”,而不是反过来。
控制语句
对于if-else if(后续可能有多个else if)类型的条件判断,最后应该包含一个else分支
这样会保证全集。
序列化
除非必须使用的第三方接口要求必须实现Serializable接口,否则请选用其它方式代替。
泛型
静态工具方法尤其适合于泛型化。
优先使用泛型集合,而不是数组:
private final T[] someArray; // 泛型数组,不建议 private final List<T> lists; // 泛型列表List
不要使用已标注为@deprecated的方法
说明:标注为@deprecated的方法,是由于各种原因被JDK废弃的方法,为了保持兼容性而没有删除,新写的代码应避免使用这些方法,而应该使用JDK推荐的代替方法。
Optional
说明:Optional表示两种情形,一种是存在值,一种缺失值。
Java 8使用Optional代替null作为返回值或者可能的缺失值;
禁止对optional对象赋值为null
性能 && 资源管理
将集合转为数组:
JDK 11后使用:
List<String> xs = ...; String[] sa = xs.toArray(String[]::new);
数组复制:
说明:在将一个数组对象复制成另外一个数组对象时,不要自己使用循环复制,可以使用Java提供的System.arraycopy()功能来复制数据对象,这样做可以避免出错,而且效率会更高。
不好的例子:
int[] src = { 1, 2, 3, 4, 5 }; int[] dest = new int[5]; for (int i = 0; i < 5; i++) { dest[i] = src[i]; }
推荐例子:
int[] src = { 1, 2, 3, 4, 5 }; int[] dest = new int[5]; System.arraycopy(src, 0, dest, 0, 5);
初始化(空间)
初始化集合时,如果已知或可以预测元素数量,则给出初始化大小;不能预测的情况下使用默认大小。
说明:在预知集合容量范围时,最好指定其初始化容量,否则就保持其默认的DEFAULT_INITIAL_CAPACITY,比如ArrayList(默认10)、StringBuilder(默认16)、StringBuffer(默认16)、HashMap(默认16)、HashSet(默认16)、XxxBlockingQueue(array的要手工指定,linked默认Integer.MAX_VALUE)等等。
可移植性
不要在代码中硬编码" "和" "作为换行符号
国际化
不要依赖平台默认的字符编码方式,使用UTF-8
中文Windows下,默认的编码为GBK,英文linux下,默认编码为ISO-8859-1。依赖平台默认值意味着同样的程序在不同的平台上可能产生不同的结果。因此,Java源文件,资源文件,配置文件必须用UTF-8编码。 如果涉及与外界的数据交互(如:序列化、跨进程通讯),建议转换为UTF-8。
参考:
谷歌风格指南 代码整洁之道 Effective Java
Java编程思想 Java解惑 核心网编程军规