04.工具类规范

3 阅读5分钟

工具类规范

1.介绍

在开始写代码之前,必须明确Utils的定义

工具类是指一组静态方法的集合,用于提供通用的,独立于业务状态的功能

2.核心特征

  • 无状态:工具类不应该持有业务数据或上下文状态
  • 通用性:它的逻辑应该是通用的,可以在不同模块甚至不同项目中复用
  • 被动性:它不主动发起业务流程,只在被调用时执行单一任务

3.绝对禁止的行为

  • 禁止注入Bean:工具类不应该通过@Autowired或SpringContext获取Service或Dao。如果你的Util需要查数据库,那它就不是Util,而是Service或Component
  • 禁止包含业务逻辑:例如OrderUtils.calculatePrice()如果包含了特定的促销规则,它应该属于领域模型或业务服务,而不是通用规则

4.编写规范

final类与私有构造器

介绍

这是工具类最基本的签名,使用者一眼就可以看出来这大概是一个工具类

  • final修饰:明确告知开发者,这个类不允许被继承。继承工具类通常是反模式的开始
  • private构造器:防止该类被实例化。工具类只有静态方法,实例化没有任何意义
示例
public final class IpUtils {
    // 1.私有构造器,防止实例化
    private IpUtils () {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }
    // 2.静态方法
    public static String getCurrentIp () {
        // ...
    }
}

只有静态方法和常量

介绍

工具类中不应包含非静态成员

  • 方法:全部为public static
  • 变量:只能是public static final的常量。绝不允许定义public static的可变变量(这是线程安全噩梦的根源)

参数的纯函数特性

介绍

工具类方法应当金量接近纯函数

  • 输出仅依赖于输入参数
  • 执行过程中不产生副作用(不修改全局变量,不修改入参状态)
反例
// 修改了入参list,不仅不安全,还可能导致调用方出现莫名其妙的bug
public static void filterList (List<String> list) {
    list.removeIf(String::isEmpt);
}
正例
// 返回一个新的List,保证输入参数不可变
public static List<Stirng> filterList (List<String> list) {
    if (list == null) return new ArrayList();
    return list.stream()
				.filter(StringUtils::isNotBlank)
				.collect(Collectors.toList);
}

细粒度拆分:拒绝CommonUtils

介绍

这是垃圾场形成的最大诱因。永远不要创建一个CommonUtils,GeneraUtils或BaseUtils的类

解决方案

按照功能领域拆分,比如下面的拆分方式:

  • 处理字符串:StringUtils
  • 处理日期:DateUtils
  • 处理集合:CollectionUtils
  • 处理文件:FileUtils
  • 处理加密:CodecUtils/DigestUtils
注意

如果一个类超过了500行,或者引入了不相关的依赖(比如StringUtils突然引入了Servlet-API),就说明需要拆分了

零依赖或轻量级依赖

介绍

工具类应当处于项目依赖树的叶子节点

  • 严谨依赖业务层:Utils不能依赖Domain,Service,Dao
  • 谨慎引入第三方重型库:如果你的StringUtils只是为了判断空字符串,却引入了整个Spring Context或Hadoop库,这是不可接受的

完善的空指针处理

介绍

工具类必须具备极高的健壮性,入参宽容,出参严谨

  • 入参:假设所有入参都可能是null
  • 出参:尽量不返回null。返回空集合,空字符串或Optional
示例
// 正例
public static String reverse (String str) {
    if (str == null) {
        return null;
    }
    return new StringBuilder(str).reverse().toString();
}

异常处理

介绍

工具类中的异常处理也要注意

  • 转换异常:不要让checked exception(如IOException)污染调用方代码,建议在工具类内部捕获,并转化为RuntimeException或自定义异常抛出
  • 静默处理:对于非关键性工具(如解析UserAgent),如果出错,可以打日志并返回默认值,而不是导致整个请求崩溃

5.命名规范

类命名

统一使用Utils或Helper后缀

  • Utils:通常指完全静态、通用的工具
  • Helper:有时用于包含特定上下文辅助逻辑的类(稍微宽容一点,但也建议用静态)

方法命名

  • isXxx/hasXxx:返回boolean,如isEmpt,isEmail
  • toXxx/parseXxx:类型转换,如toInt,parseDate
  • formatXxx:格式化,如formatMoney
  • getXxx:获取属性,如getFileExtension
  • wrapXxx/unwrapXxx:包装或解包

6.避免重复造轮子

介绍

在写任何工具类之前,先问自己:Apache Commons或Guava里面有了吗?

Java生态中最顶级的工具库,已经经过了全球开发者的千万次验证,能用就不要重复造轮子

推荐引入的标准库

  • Apache Commons Lang3:StringUtils,ArrayUtils,NumberUtils,Pair
  • Apache Commons Collections4:CollectionUtils,MapUtils
  • Apache Commons IO:FileUtils,IOUtils
  • Google Guava:Lists,Maps,ImmutableList
  • Hutool:国内非常流行的库,全能型工具库,适合不想引入太多细碎依赖的中小型项目

7.正确的封装

介绍

如果团队中决定统一使用org.apache.commons.lang3.StringUtils,是否需要自己在写一个StringUtils去继承他?

规范

  • 不要继承:Commons的类通常也是final的,无法继承
  • 可以组合/代理:如果你需要统一项目的特定逻辑(例如特殊的手机号脱敏),可以建立项目的ProjectStringUtils,里面调用Commons的方法,补充自己的方法
  • 直接使用:对于标准方法(如isEmpt),可以直接调用第三方库即可,不要再包一层,徒增认知成本

8.JDK21对Utils的冲击

介绍

随着JDK21的普及,曾经必须依赖的工具类才能实现的很多功能,现在已经是JDK的原生语法。继续在工具类中封装这些功能,不仅多余,还会增加认知负担

JDK各个版本的工具类方法

省略

9.模式匹配取代类型转换工具

介绍

JDK16的instanceof模式匹配和JDK21的Switch模式匹配,消灭了大量用于类型判断+强转的工具方法

老写法

// 以前可能写一个CastUtils
User user = CastUtils.castToUser(obj);

新写法

// 原生语法直接处理,无需封装
if (obj instanceof User user) {
	// 直接使用,无需强转
    user.getName();
}
// 或者使用Switch模式匹配
String result = switch (obj) {
    case User u -> "User:" + u.getName();
    case Order o -> "Order:" + o.getId();
    default -> "Unknown";
}