一. 什么是魔法值
魔法值通常是指在编写代码时凭空出现的数字或字符串,如果没有注释,无法直接判断其代表的含义,必须通过分析代码上下文才能明白。
魔法值会严重降低代码可读性与可维护性。在一个周期相对较长的项目中,很可能后续在回顾之前编写的代码时忘记这类魔法值代表的含义,如果没有注释或批注,将严重影响开发进度。个人项目亦是如此,更何况别人的项目代码。
例如:
gender.setGender(0); // 设置性别为女
gender.setGender(1); // 设置性别为男
statu.setStatus(0); //表示状态正常
statu.setStatus(1); //表示状态异常
……
阿里巴巴 Java 开发规范手册中明确指出:不允许任何魔法值(即未经定义的常量)直接出现在代码中。
二. 魔法值替换
1. 常量
使用 public static final 修饰的成员变量,必须有初始化值,且一旦被初始化,执行过程中值将不可变。通常使用英文单词全部大写,多个单词之间使用下划线连接。
// 标识用户性别
public class UserGender {
// 0 代表女性
public static final Integer USER_GENDER_FEMALE = 0;
// 1 代表男性
public static final Integer USER_GENDER_MALE = 1;
}
// 标识用户状态常量
public class UserStatus {
// 0 代表正常
public static final Integer USER_NORMAL = 0;
// 1 代表异常
public static final Integer USER_ABNORMAL = 1;
// 2 代表禁止
public static final Integer USER_PROHIBIT = 2;
}
在使用常量时,Java 会在编译阶段自动进行替换,将常量替换成真实的字面量。
// 使用常量 UserGender.USER_GENDER_MALE
System.out.println(UserGender.USER_GENDER_MALE);
// java会在编译阶段替换为字面量 1
System.out.println(1);
在业务中具体使用时可以显著提高代码可读性,如果想要修改值,在 UserGender 类中修改即可,所有引用处都会生效。
// 使用魔法值
user.setGender(0);
// 使用常量
user.setGender(UserGender.USER_GENDER_FEMALE);
2. 枚举类
通过枚举类,开发者可以固定要接收的常量数值,适合需要约束接收值的场景。
对于相对简单的场景,直接使用枚举示例:
// 协议状态枚举
public enum ProtocolMessageStatusEnum {
OK, // 请求成功
BAD_REQUEST, // 请求失败
BAD_RESPONSE; // 响应失败
}
使用时直接调用,但只能获取事先罗列的枚举类实例:
public class Test {
public static void main(String[] args) {
System.out.println(ProtocolMessageStatusEnum.OK);
System.out.println(ProtocolMessageStatusEnum.BAD_REQUEST);
System.out.println(ProtocolMessageStatusEnum.BAD_RESPONSE);
}
}
在相对复杂的场景中,可以给每个实例增加一个或多个值。
例如在 RPC 项目实现自定义协议部分,曾创建响应状态的枚举类:
import lombok.Getter;
/**
* 协议消息的状态枚举
*/
@Getter
public enum ProtocolMessageStatusEnum {
OK("ok", 20),
BAD_REQUEST("badRequest", 40),
BAD_RESPONSE("badResponse", 50);
// 可以有多个字段
private final String text;
private final int value;
// 必须提供对应参数构造器
ProtocolMessageStatusEnum(String text, int value){
this.text = text;
this.value = value;
}
/**
* 根据 value 获取枚举
*
* @param value
* @return
*/
// 开放获取实例值权限
public static ProtocolMessageStatusEnum getEnumByValue(int value){
for (ProtocolMessageStatusEnum anEnum : ProtocolMessageStatusEnum.values()){
if (anEnum.value == value){
return anEnum;
}
}
return null;
}
}
在使用时可以直接调用,并且多了自定义方法,getEnumByValue:
header.setStatus((byte) ProtocolMessageStatusEnum.OK.getValue());
……
eader.setSerializer((byte) ProtocolMessageSerializerEnum.getEnumByValue( …… );
当我们将枚举类应用在方法参数列表中,那就有很强的约束性。此外,如果对于一些方法只能接受数值,还可以通过 values 获取所有值,然后判断数值的正确性。
public class App {
public static void main(String[] args) {
setStatus(UserStatusEnum.NORMAL);
}
public static void setStatus(UserStatusEnum userStatus){
// 判断参数是否为对于枚举实例
if(isTrueValue(status)){
User user = new User();
user.setStatus(status);
} else {
Syetem.out.println("非法参数");
}
// 判断参数是否为对于枚举实例
public static boolean isTrueValue(Integer num) {
UserStatusEnum[] values = UserStatusEnum.values();
for (UserStatusEnum statusEnum : values) {
Integer value = statusEnum.getValue();
if (value.equals(num)) {
return true;
}
}
return false;
}
}