什么是魔法值和解决方法

​ 根据阿里规约,要避免魔法值。其实我不是很清楚魔法值的概念,特此了解一下,也记录一下我的代码不规范。

魔法值是什么

​ 魔法值指的是代码中没有任何定义,直接像魔法一样凭空出现的值,可以是数字、字符串等,意义必须通过阅读其他代码才能推断出来,这样给后期维护或者其他人员阅读代码,带来了极大不便。

问题一:魔法值会严重影响代码的 可读性可维护性

例如:

1
2
3
4
5
if( flag = 5 ){
a = 2;
}else{
a = 3;
}

当其他人员阅读时就会引起深深的疑问:

为什么flag=5时做这个?flag=5背后隐藏着什么秘密?flag=5意味着什么? 如果时间久,原开发人员也要重新看代码才能明白当时的深意,这就大大增加了二次开发和维护时的难度。

​ 如果你的记忆力很好,自己一个做项目的时候追求点效率倒也无所谓了。但要是和其他同学一起做项目、维护代码,写带有魔法值的代码无疑会让他人难以理解,如果再不写注释,可能他们连杀了你的心都会有了。

问题二:魔法值会影响开发的 效率和准确性

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 错误提示
const ERROR_MESSAGE = "操作失败";

// 删除资源
const result = deleteResource();
if(!result) {
alert(ERROR_MESSAGE);
}
// 修改资源
const result = updateResource();
if(!result) {
alert(ERROR_MESSAGE);
}

...

​ 在该文件中,不止一次使用到了 ERROR_MESSAGE 这一常量。如果不去定义常量,而是直接用魔法字符串,那么每次要弹出 “操作失败” 时,都要重复去打这几个字,浪费时间的同时,还存在打错字的风险。而如果使用预定义的常量,就能很轻松地利用开发工具提供的代码提示和补全功能。

问题三:魔法值会影响代码的 易修改性

​ 假如说同一个魔法字符串在代码中多次出现,那当我要修改字符串文案时,就要人工一个个地去找到这个字符串进行修改,即使可以用开发工具提供的搜索和全局替换功能,但也要去检查一遍,非常麻烦。

1
2
3
alert("你操作失败了");
alert("你操作失败了");
alert("你操作失败了");

​ 而如果将字符串定义为常量,只用在定义处修改它的值即可。此时的常量名,倒像是魔法值的一个指针了。

1
2
3
4
const ERROR_MESSAGE = "你操作失败了"
alert("ERROR_MESSAGE");
alert("ERROR_MESSAGE");
alert("ERROR_MESSAGE");

我的错误

原代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//判断工程类型 1-海塘 2-水闸 3-泵站
switch (projBaseSaveOrUpdateVo.getProjectType()){
case "1":
//新增或修改海塘信息
Seawall seawall = BeanUtils.toBean(Seawall.class,projBaseSaveOrUpdateVo);
flag = flag && seawallService.saveOrUpdate(seawall);
break;
case "2":
//新增或修改水闸信息
Sluice sluice = BeanUtils.toBean(Sluice.class,projBaseSaveOrUpdateVo);
flag = flag && sluiceService.saveOrUpdate(sluice);
break;
case "3":
//新增或修改泵站信息
Pust pust = BeanUtils.toBean(Pust.class,projBaseSaveOrUpdateVo);
flag = flag && pustService.saveOrUpdate(pust);
break;
default:
}

这里的 case “1” 、case “2” 、 case “3”,在阅读代码前,没人清楚其代表意义 。于是这里就出现了三个魔法值。

解决方法

方法一:静态常量(不推荐)

修改后代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//判断工程类型 1-海塘 2-水闸 3-泵站
switch (projBaseSaveOrUpdateVo.getProjectType()){
case SEAWALL:
//新增或修改海塘信息
Seawall seawall = BeanUtils.toBean(Seawall.class,projBaseSaveOrUpdateVo);
flag = flag && seawallService.saveOrUpdate(seawall);
break;
case SLUICE:
//新增或修改水闸信息
Sluice sluice = BeanUtils.toBean(Sluice.class,projBaseSaveOrUpdateVo);
flag = flag && sluiceService.saveOrUpdate(sluice);
break;
case PUST:
//新增或修改泵站信息
Pust pust = BeanUtils.toBean(Pust.class,projBaseSaveOrUpdateVo);
flag = flag && pustService.saveOrUpdate(pust);
break;
default:
}

定义常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 海塘
*/
public static final String SEAWALL = "1";

/**
* 水闸
*/
public static final String SLUICE = "2";

/**
* 泵站
*/
public static final String PUST = "3";

直接在类中定义一个常量,通过常量名就可以基本清楚此处的基本逻辑和代表意义。这种方法对于魔法值的使用范围只在一个类的范围内的情况下比较方便。

方法二:接口中定义

写在接口中的话好处就是,继承这个接口的实现类都可以使用这些常量。

方法三:定义在实体类

修改后代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//判断工程类型 1-海塘 2-水闸 3-泵站
switch (projBaseSaveOrUpdateVo.getProjectType()){
case ProjBase.SEAWALL:
//新增或修改海塘信息
Seawall seawall = BeanUtils.toBean(Seawall.class,projBaseSaveOrUpdateVo);
flag = flag && seawallService.saveOrUpdate(seawall);
break;
case ProjBase.SLUICE:
//新增或修改水闸信息
Sluice sluice = BeanUtils.toBean(Sluice.class,projBaseSaveOrUpdateVo);
flag = flag && sluiceService.saveOrUpdate(sluice);
break;
case ProjBase.PUST:
//新增或修改泵站信息
Pust pust = BeanUtils.toBean(Pust.class,projBaseSaveOrUpdateVo);
flag = flag && pustService.saveOrUpdate(pust);
break;
default:
}

实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ProjBase implements Serializable {

/**
* 海塘
*/
public static final String SEAWALL = "1";

/**
* 水闸
*/
public static final String SLUICE = "2";

/**
* 泵站
*/
public static final String PUST = "3";

这样写是比较推荐的,你可以在任意想用的地方使用类名打点的方式使用这些常量。如果要写的常量比较多也可以直接建一个专门用来存放静态常量的类。

方法四 使用枚举类 enum

枚举类是另一种更规范的消除魔法值的方法,使用时需要定义一个枚举类,并为类定义属性和构造方法等。枚举类能够约束静态常量的定义规范,提供统一格式的静态常量值,在统一异常等内容中广泛使用。

消除魔法值的必要性

​ 在使用静态常量或枚举类来替代代码中的魔法值时,我们发现大多数地方的代码并没有简化,反而更加复杂了,而我们原来的使用方式也没有不妥,因此会引发一种思考:大量重复使用的常量抽取定义是应该的,但是仅在类中使用一次或在方法中局部使用的字符串和数值,定义静态常量或枚举类是否有必要呢?

  • 这个问题可以说见仁见智了,代码的规范对于参与人数较多、模块精细化的大项目是十分有必要的,因为每个人都有自己的开发风格,将不同的个人风格杂揉到一个项目中,产生的那叫四不像。
  • 当然,对于个人开发的小项目,只要保证业务逻辑数据的正确性,对于开发规范也没那么重要,但是不得不说遵循规范的开发在后期维护时那叫一个流畅!

总结

在工作中学习,不断提升自己,通过解决代码中魔法值规范提醒的问题,发现:

  • 开发中要不断提升自己的代码规范意识,项目中一大半的bug都是由于代码不规范造成的
  • 善用各种代码规范工具,如阿里巴巴Java开发规范插件,代码质量扫描工具等,发现并解决问题、学习积累知识

参考链接

什么是魔法值和解决方法_Lmm萌萌的博客-CSDN博客

别让【魔法值】毁了你的代码! - 知乎 (zhihu.com)

Java 魔法值处理的四种方法_integer = 1 魔法值问题_Theman_6的博客-CSDN博客

用魔法打败魔法——解决Java代码中的魔法值 - 掘金 (juejin.cn)