原文地址:https://mp.weixin.qq.com/s/-YQsD6jJuxewFjQtyX16UA

明代王阳明先生在《传习录》谈为学之道时说:

私欲日生,如地上尘,一日不扫,便又有一层。着实用功,便见道无终穷,愈探愈深,必使精白无一毫不彻方可。

代码中的 " 坏味道 ",如 " 私欲 " 如 " 灰尘 ",每天都在增加,一日不去清除,便会越累越多。如果用功去清除这些 " 坏味道 ",不仅能提高自己的编码水平,也能使代码变得 " 精白无一毫不彻 "。这里,整理了日常工作中的一些 " 坏味道 ",及清理方法,供大家参考。

让代码性能更高

需要 Map 的主键和取值时,应该迭代 entrySet()

当循环中只需要 Map 的主键时,迭代 keySet() 是正确的。但是,当需要主键和取值时,迭代 entrySet() 才是更高效的做法,比先迭代 keySet() 后再去 get 取值性能更佳。

反例:

复制代码
 
Map<String, String> map = ...;
 
for (String key : map.keySet()) {
 
String value = map.get(key);
 
...
 
}

正例:

复制代码
 
Map<String, String> map = ...;
 
for (Map.Entry<String, String> entry : map.entrySet()) {
 
String key = entry.getKey();
 
String value = entry.getValue();
 
...
 
}

应该使用 Collection.isEmpty() 检测空

使用 Collection.size() 来检测空逻辑上没有问题,但是使用 Collection.isEmpty() 使得代码更易读,并且可以获得更好的性能。任何 Collection.isEmpty() 实现的时间复杂度都是 O(1) ,但是某些 Collection.size() 实现的时间复杂度可能是 O(n) 。

反例:

复制代码
 
if (collection.size() == 0) {
 
...
 
}

正例:

复制代码
 
if (collection.isEmpty()) {
 
...
 
}

如果需要还需要检测 null ,可采用 CollectionUtils.isEmpty(collection) 和 CollectionUtils.isNotEmpty(collection)。

不要把集合对象传给自己

此外,由于某些方法要求参数在执行期间保持不变,因此将集合传递给自身可能会导致异常行为。

反例:

复制代码
 
List<String> list = new ArrayList<>();
 
list.add("Hello");
 
list.add("World");
 
if (list.containsAll(list)) { // 无意义, 总是返回 true
 
...
 
}
 
list.removeAll(list); // 性能差, 直接使用 clear()

集合初始化尽量指定大小

Java 的集合类用起来十分方便,但是看源码可知,集合也是有大小限制的。每次扩容的时间复杂度很有可能是 O(n) ,所以尽量指定可预知的集合大小,能减少集合的扩容次数。

反例:

复制代码
 
int[] arr = new int[]{1, 2, 3};
 
List<Integer> list = new ArrayList<>();
 
for (int i : arr) {
 
list.add(i);
 
}

正例:

复制代码
 
int[] arr = new int[]{1, 2, 3};
 
List<Integer> list = new ArrayList<>(arr.length);
 
for (int i : arr) {
 
list.add(i);
   
 
}

字符串拼接使用 StringBuilder

一般的字符串拼接在编译期 Java 会进行优化,但是在循环中字符串拼接, java 编译期无法做到优化,所以需要使用 StringBuilder 进行替换。

反例:

复制代码
 
String s = "";
 
for (int i = 0; i < 10; i++) {
 
s += i;
 
}

正例:

复制代码
 
String a = "a";
 
String b = "b";
 
String c = "c";
 
String s = a + b + c; // 没问题,java 编译器会进行优化
 
StringBuilder sb = new StringBuilder();
 
for (int i = 0; i < 10; i++) {
 
sb.append(i); // 循环中,java 编译器无法进行优化,所以要手动使用 StringBuilder
 
}

List 的随机访问

大家都知道数组和链表的区别:数组的随机访问效率更高。当调用方法获取到 List 后,如果想随机访问其中的数据,并不知道该数组内部实现是链表还是数组,怎么办呢?可以判断它是否实现 * RandomAccess * 接口。

正例:

复制代码
 
// 调用别人的服务获取到 list
 
List<Integer> list = otherService.getList();
 
if (list instanceof RandomAccess) {
 
// 内部数组实现,可以随机访问
 
System.out.println(list.get(list.size() - 1));
 
} else {
 
// 内部可能是链表实现,随机访问效率低
 
}

频繁调用 Collection.contains 方法请使用 Set

在 Java 集合类库中,List 的 contains 方法普遍时间复杂度是 O(n) ,如果在代码中需要频繁调用 contains 方法查找数据,可以先将 list 转换成 HashSet 实现,将 O(n) 的时间复杂度降为 O(1) 。

反例:

复制代码
 
ArrayList<Integer> list = otherService.getList();
 
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
 
// 时间复杂度 O(n)
 
list.contains(i);
 
}

正例:

复制代码
 
ArrayList<Integer> list = otherService.getList();
 
Set<Integer> set = new HashSet(list);
 
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
 
// 时间复杂度 O(1)
 
set.contains(i);
 
}

让代码更优雅

长整型常量后添加大写 L

在使用长整型常量值时,后面需要添加 L ,必须是大写的 L ,不能是小写的 l ,小写 l 容易跟数字 1 混淆而造成误解。

反例:

复制代码
 
long value = 1l;
 
long max = Math.max(1L, 5);

正例:

复制代码
 
long value = 1L;
 
long max = Math.max(1L, 5L);

不要使用魔法值

当你编写一段代码时,使用魔法值可能看起来很明确,但在调试时它们却不显得那么明确了。这就是为什么需要把魔法值定义为可读取常量的原因。但是,-1、0 和 1 不被视为魔法值。

反例:

复制代码
 
for (int i = 0; i < 100; i++){
 
...
 
}
 
if (a == 100) {
 
...
 
}

正例:

复制代码
 
private static final int MAX_COUNT = 100;
 
for (int i = 0; i < MAX_COUNT; i++){
 
...
 
}
 
if (count == MAX_COUNT) {
 
...
 
}

不要使用集合实现来赋值静态成员变量

对于集合类型的静态成员变量,不要使用集合实现来赋值,应该使用静态代码块赋值。

反例:

复制代码
 
private static Map<String, Integer> map = new HashMap<String, Integer>() {
 
{
 
put("a", 1);
 
put("b", 2);
 
}
 
};
   
 
private static List<String> list = new ArrayList<String>() {
 
{
 
add("a");
 
add("b");
 
}
 
};

正例:

复制代码
 
private static Map<String, Integer> map = new HashMap<>();
 
static {
 
map.put("a", 1);
 
map.put("b", 2);
 
};
   
 
private static List<String> list = new ArrayList<>();
 
static {
 
list.add("a");
 
list.add("b");
 
};

建议使用 try-with-resources 语句

Java 7 中引入了 try-with-resources 语句,该语句能保证将相关资源关闭,优于原来的 try-catch-finally 语句,并且使程序代码更安全更简洁。

反例:

复制代码
 
private void handle(String fileName) {
 
BufferedReader reader = null;
 
try {
 
String line;
 
reader = new BufferedReader(new FileReader(fileName));
 
while ((line = reader.readLine()) != null) {
 
...
 
}
 
} catch (Exception e) {
 
...
 
} finally {
 
if (reader != null) {
 
try {
 
reader.close();
 
} catch (IOException e) {
 
...
 
}
 
}
 
}
 
}

正例:

复制代码
 
private void handle(String fileName) {
 
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
 
String line;
 
while ((line = reader.readLine()) != null) {
 
...
 
}
 
} catch (Exception e) {
 
...
 
}
 
}

删除未使用的私有方法和字段

删除未使用的私有方法和字段,使代码更简洁更易维护。若有需要再使用,可以从历史提交中找回。

反例:

复制代码
 
public class DoubleDemo1 {
 
private int unusedField = 100;
 
private void unusedMethod() {
 
...
 
}
 
public int sum(int a, int b) {
 
return a + b;
 
}
 
}

正例:

复制代码
 
public class DoubleDemo1 {
 
public int sum(int a, int b) {
 
return a + b;
 
}
 
}

删除未使用的局部变量

删除未使用的局部变量,使代码更简洁更易维护。

反例:

复制代码
 
public int sum(int a, int b) {
 
int c = 100;
 
return a + b;
 
}

正例:

复制代码
 
public int sum(int a, int b) {
 
return a + b;
 
}

删除未使用的方法参数

未使用的方法参数具有误导性,删除未使用的方法参数,使代码更简洁更易维护。但是,由于重写方法是基于父类或接口的方法定义,即便有未使用的方法参数,也是不能删除的。

反例:

复制代码
 
public int sum(int a, int b, int c) {
 
return a + b;
 
}

正例:

复制代码
 
public int sum(int a, int b) {
 
return a + b;
 
}

删除表达式的多余括号

对应表达式中的多余括号,有人认为有助于代码阅读,也有人认为完全没有必要。对于一个熟悉 Java 语法的人来说,表达式中的多余括号反而会让代码显得更繁琐。

反例:

复制代码
 
return (x);
 
return (x + 2);
 
int x = (y * 3) + 1;
 
int m = (n * 4 + 2);

正例:

复制代码
 
return x;
 
return x + 2;
 
int x = y * 3 + 1;
 
int m = n * 4 + 2;

工具类应该屏蔽构造函数

工具类是一堆静态字段和函数的集合,不应该被实例化。但是,Java 为每个没有明确定义构造函数的类添加了一个隐式公有构造函数。所以,为了避免 Java " 小白 " 使用有误,应该显式定义私有构造函数来屏蔽这个隐式公有构造函数。

反例:

复制代码
 
public class MathUtils {
 
public static final double PI = 3.1415926D;
 
public static int sum(int a, int b) {
 
return a + b;
 
}
 
}

正例:

复制代码
 
public class MathUtils {
 
public static final double PI = 3.1415926D;
 
private MathUtils() {}
 
public static int sum(int a, int b) {
 
return a + b;
 
}
 
}

删除多余的异常捕获并抛出

用 catch 语句捕获异常后,什么也不进行处理,就让异常重新抛出,这跟不捕获异常的效果一样,可以删除这块代码或添加别的处理。

反例:

复制代码
 
private static String readFile(String fileName) throws IOException {
 
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
 
String line;
 
StringBuilder builder = new StringBuilder();
 
while ((line = reader.readLine()) != null) {
 
builder.append(line);
 
}
 
return builder.toString();
 
} catch (Exception e) {
 
throw e;
 
}
 
}

正例:

复制代码
 
private static String readFile(String fileName) throws IOException {
 
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
 
String line;
 
StringBuilder builder = new StringBuilder();
 
while ((line = reader.readLine()) != null) {
 
builder.append(line);
 
}
 
return builder.toString();
 
}
 
}

公有静态常量应该通过类访问

虽然通过类的实例访问公有静态常量是允许的,但是容易让人它误认为每个类的实例都有一个公有静态常量。所以,公有静态常量应该直接通过类访问。

反例:

复制代码
 
public class User {
 
public static final String CONST_NAME = "name";
 
...
 
}
   
 
User user = new User();
 
String nameKey = user.CONST_NAME;

正例:

复制代码
 
public class User {
 
public static final String CONST_NAME = "name";
 
...
 
}
   
 
String nameKey = User.CONST_NAME;

不要用 NullPointerException 判断空

空指针异常应该用代码规避(比如检测不为空),而不是用捕获异常的方式处理。

反例:

复制代码
 
public String getUserName(User user) {
 
try {
 
return user.getName();
 
} catch (NullPointerException e) {
 
return null;
 
}
 
}

正例:

复制代码
 
public String getUserName(User user) {
 
if (Objects.isNull(user)) {
 
return null;
 
}
 
return user.getName();
 
}

使用 String.valueOf(value) 代替 ""+value

当要把其它对象或类型转化为字符串时,使用 String.valueOf(value) 比 ""+value 的效率更高。

反例:

复制代码
 
int i = 1;
 
String s = "" + i;

正例:

复制代码
 
int i = 1;
 
String s = String.valueOf(i);

过时代码添加 @Deprecated 注解

当一段代码过时,但为了兼容又无法直接删除,不希望以后有人再使用它时,可以添加 @Deprecated 注解进行标记。在文档注释中添加 @deprecated 来进行解释,并提供可替代方案

正例:

复制代码
 
/**
 
* 保存
 
*
 
* @deprecated 此方法效率较低,请使用{@link newSave()}方法替换它
 
*/
 
@Deprecated
 
public void save(){
 
// do something
 
}

让代码远离 bug

禁止使用构造方法 BigDecimal(double)

BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

反例:

复制代码
 
BigDecimal value = new BigDecimal(0.1D); // 0.100000000000000005551115...

正例:

复制代码
 
BigDecimal value = BigDecimal.valueOf(0.1D);; // 0.1

返回空数组和空集合而不是 null

返回 null ,需要调用方强制检测 null ,否则就会抛出空指针异常。返回空数组或空集合,有效地避免了调用方因为未检测 null 而抛出空指针异常,还可以删除调用方检测 null 的语句使代码更简洁。

反例:

复制代码
 
public static Result[] getResults() {
 
return null;
 
}
   
 
public static List<Result> getResultList() {
 
return null;
 
}
   
 
public static Map<String, Result> getResultMap() {
 
return null;
 
}
   
 
public static void main(String[] args) {
 
Result[] results = getResults();
 
if (results != null) {
 
for (Result result : results) {
 
...
 
}
 
}
   
 
List<Result> resultList = getResultList();
 
if (resultList != null) {
 
for (Result result : resultList) {
 
...
 
}
 
}
   
 
Map<String, Result> resultMap = getResultMap();
 
if (resultMap != null) {
 
for (Map.Entry<String, Result> resultEntry : resultMap) {
 
...
 
}
 
}
 
}

正例:

复制代码
 
public static Result[] getResults() {
 
return new Result[0];
 
}
   
 
public static List<Result> getResultList() {
 
return Collections.emptyList();
 
}
   
 
public static Map<String, Result> getResultMap() {
 
return Collections.emptyMap();
 
}
   
 
public static void main(String[] args) {
 
Result[] results = getResults();
 
for (Result result : results) {
 
...
 
}
   
 
List<Result> resultList = getResultList();
 
for (Result result : resultList) {
 
...
 
}
   
 
Map<String, Result> resultMap = getResultMap();
 
for (Map.Entry<String, Result> resultEntry : resultMap) {
 
...
 
}
 
}

优先使用常量或确定值来调用 equals 方法

对象的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals 方法。当然,使用 java.util.Objects.equals() 方法是最佳实践。

反例:

复制代码
 
public void isFinished(OrderStatus status) {
 
return status.equals(OrderStatus.FINISHED); // 可能抛空指针异常
 
}

正例:

复制代码
 
public void isFinished(OrderStatus status) {
 
return OrderStatus.FINISHED.equals(status);
 
}
   
 
public void isFinished(OrderStatus status) {
 
return Objects.equals(status, OrderStatus.FINISHED);
 
}
   

枚举的属性字段必须是私有不可变

枚举通常被当做常量使用,如果枚举中存在公共属性字段或设置字段方法,那么这些枚举常量的属性很容易被修改。理想情况下,枚举中的属性字段是私有的,并在私有构造函数中赋值,没有对应的 Setter 方法,最好加上 final 修饰符。

反例:

复制代码
 
public enum UserStatus {
 
DISABLED(0, " 禁用 "),
 
ENABLED(1, " 启用 ");
   
 
public int value;
 
private String description;
   
 
private UserStatus(int value, String description) {
 
this.value = value;
 
this.description = description;
 
}
   
 
public String getDescription() {
 
return description;
 
}
   
 
public void setDescription(String description) {
 
this.description = description;
 
}
 
}

正例:

复制代码
 
public enum UserStatus {
 
DISABLED(0, " 禁用 "),
 
ENABLED(1, " 启用 ");
   
 
private final int value;
 
private final String description;
   
 
private UserStatus(int value, String description) {
 
this.value = value;
 
this.description = description;
 
}
   
 
public int getValue() {
 
return value;
 
}
   
 
public String getDescription() {
 
return description;
 
}
 
}
   
   

小心 String.split(String regex)

字符串 String 的 split 方法,传入的分隔字符串是正则表达式!部分关键字(比如.| 等)需要转义

反例:

复制代码
 
"a.ab.abc".split("."); // 结果为 []
 
"a|ab|abc".split("|"); // 结果为 ["a", "|", "a", "b", "|", "a", "b", "c"]

正例:

复制代码
 
"a.ab.abc".split("\\."); // 结果为 ["a", "ab", "abc"]
 
"a|ab|abc".split("\\|"); // 结果为 ["a", "ab", "abc"]

总结

这篇文章,可以说是从事 Java 开发的经验总结,分享出来以供大家参考。希望能帮大家避免踩坑,让代码更加高效优雅。

转载于:https://www.cnblogs.com/davidwang456/articles/11561296.html

消灭 Java 代码的“坏味道”【转】相关推荐

  1. 消灭 Java 代码的“坏味道”

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 导读 明代王阳明先生在<传习录>谈为学之道时说: 私欲 ...

  2. 第十三期:消灭 Java 代码的“坏味道”

    代码中的"坏味道",如"私欲"如"灰尘",每天都在增加,一日不去清除,便会越累越多.如果用功去清除这些"坏味道",不仅能 ...

  3. 教你消灭 Java 代码的“坏味道”

    导读 明代王阳明先生在<传习录>谈为学之道时说: 私欲日生,如地上尘,一日不扫,便又有一层.着实用功,便见道无终穷,愈探愈深,必使精白无一毫不彻方可. 代码中的"坏味道" ...

  4. 读书笔记之《重构》第三章—代码的坏味道

    第三章 代码的坏味道 这一章告诉我们,什么样的代码需要去重构,该用什么方法.寻找代码的坏味道,就是从代码中找出特定的结构,这些结构指出重构的可能性.寻找这些结构,需要学会判断,判断一个类里面有多少实例 ...

  5. 【重构】 代码的坏味道总结 Bad Smell (一) (重复代码 | 过长函数 | 过大的类 | 过长参数列 | 发散式变化 | 霰弹式修改)

    膜拜下 Martin Fowler 大神 , 开始学习 圣经 重构-改善既有代码设计 . 代码的坏味道就意味着需要重构, 对代码的坏味道了然于心是重构的比要前提; . 作者 : 万境绝尘 转载请注明出 ...

  6. 【重构之法】代码的坏味道

    代码的坏味道 坏味道意指代码中出现的可以被改进的地方.当你嗅到坏味道的时候,也就意味着重构的时机到了. 重构就是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修 ...

  7. 重构-改善既有的代码设计-------代码的坏味道

    重构-改善既有的代码设计 代码的坏味道 神秘命名(Mysterious Name) 给函数.变量.模块和类命名时,要使它能清晰地表明自己的功能和用法,使得写下的代码直观明了. 常用重构手法为重命名,包 ...

  8. Refactoring之——代码的坏味道(一)过长方法

    1 代码的坏味道 重构一书中提到了22种代码的坏味道,大致可以分为几类. 识别代码的坏味道,有助于发现代码的潜在问题,从而可以有的放矢的修改现有代码,使之不断完善. 1.1 Bloaters(臭鲱,暂 ...

  9. 代码的坏味道与重构技术

    一.前言 本文大部分内容.图片来自Martin Flower的<Refactoring>一书以及refactoringguru网站(一个很棒的网站),之前在博客发表过,这次属于整理后重新发 ...

最新文章

  1. RookeyFrame 删除 线下添加的model
  2. matlab gui创建,Matlab创建GUI
  3. Linq To Sql 练习
  4. Java面向对象(六)接口
  5. Spring Boot 之Spring data JPA简介
  6. php查询mysql返回大量数据结果集导致内存溢出的解决方法
  7. hive中的日期转换函数
  8. 网络安全系列之四十六 在IIS6中配置目录安全性
  9. dreamweaver 正则表达式为属性值加上双引号_IT兄弟连 HTML5教程 HTML5表单 新增的表单属性3...
  10. 《编写有效用例阅读笔记二》
  11. 3.5 RNN 层使用方法
  12. 用java编写宠物店管理系统_基于jsp的宠物店管理系统-JavaEE实现宠物店管理系统 - java项目源码...
  13. 解决输入框中文英文长度限制不同的情况
  14. 第4章-模板引擎Jade和Handlebars-4.2.Jade的语法和特性
  15. ps中怎么画直线和画参考线
  16. 表白php制作教程视频,表白视频制作神器,我们提供更加创新的方法制作表白视频...
  17. 大学毕业不用愁,一系列软件帮你轻松完成毕业论文
  18. 腾讯Android面试:Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么
  19. windows7安装打印机提示“本地打印后台处理程序服务没有运行”
  20. JAVA实现获取指定日期所在的周的所有日期

热门文章

  1. python迷宫问题的所有路径_python——迷宫问题总结
  2. 计算机三级考试题库网络,计算机三级网络试题及答案解析
  3. dbeaver导出表结构和数据_python中的哈希表数据结构
  4. python实现vlookup功能_干货一:怎么在python里面实现vlookup
  5. android:shareduserid获取资源,关于 android:sharedUserId=android.uid.system
  6. win10 jenkins svn android studio 自动化打包教程 以及一些问题的解决办法
  7. php fsockopen socket,基于php socket(fsockopen)的应用实例分析
  8. C++继承时的对象内存位置(一)有成员变量遮蔽时的内存分布
  9. linux 统计日志最多的ip,统计nginx日志里访问次数最多的前十个IP
  10. java post 打开新页面_JAVA后台POST/GET访问方法