从“赋值判断地狱”到规则驱动设计
在业务快速演进的系统中,我们经常会遇到这样一类代码:
**大量 if 判断 + 重复的对象构建逻辑 + 强耦合的业务条件**。 短期能跑,长期必炸。
本文通过一个真实的 Java 代码重构案例,分享如何将 “赋值判断地狱”重构为**规则驱动(Rule-based)的清晰结构,提高代码的可读性、扩展性和可维护性**。
一、问题背景:Assignment 构建逻辑高度膨胀
原始代码的目标很简单:
根据 <font size=4.5 color=blut>
AdInfo</font> 中不同的定向条件,生成一组<font size=4.5 color=blut>Assignment</font> 对象。
但实现方式却是经典的「一路 if 到底」。
<font color=blut size=4.8> 重构前代码特点</font>
- if-else 数量巨大
- 每个分支都在重复构建 Assignment
- 条件判断 + 赋值逻辑强耦合
- 新增一个定向条件 → 必须改主流程方法
- 可读性差,不利于代码 review
重构前代码:
这种代码在业务初期还能接受,但一旦规则达到 10+、20+,就会迅速失控。
public class IndexItemUtils {
public static Set<Assignment> getKeyList(AdInfo adInfo) {
Set<Assignment> assignments = new HashSet<>();
if (adInfo.getType() == DeliveryMedia.PACKAGE.getCode() && StringUtils.isNotBlank(adInfo.getPackage_name())) {
assignments.add(Assignment.builder()
.name("package_name")
.values(IndexUtils.convertStrToSet(adInfo.getPackage_name()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
}
if (adInfo.getCity_mode() == DirectionalMode.INCLUDED.getCode() && StringUtils.isNotBlank(adInfo.getCities())) {
assignments.add(Assignment.builder()
.name("cities")
.values(IndexUtils.convertStrToSet(adInfo.getCities()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
}
if (adInfo.getCity_mode() == DirectionalMode.EXCLUDED.getCode() && StringUtils.isNotBlank(adInfo.getCities())) {
assignments.add(Assignment.builder()
.name("city_exclude")
.values(IndexUtils.convertStrToSet(adInfo.getCities()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(false)
.build());
}
if (!adInfo.getOs_type().equals("COMMON") && StringUtils.isNotBlank(adInfo.getOs_type())) {
assignments.add(Assignment.builder()
.name("os_type")
.values(IndexUtils.convertStrToSet(adInfo.getOs_type()))
.operatorEnum(OperatorEnum.EQUALITY)
.includeBase(true)
.build());
}
if (StringUtils.isNotBlank(adInfo.getOs_version())) {
//1=大于,2=大于等于,3=小于,4=小于等于,5等于
assignments.add(Assignment.builder()
.name("os_version")
.values(IndexUtils.convertStrToSet(adInfo.getOs_version()))
.operatorEnum(OperatorEnum.VERSIONING)
.includeBase(true)
.versionComparison(VersionComparison.fromCode(adInfo.getOs_version_condition()))
.build());
}
if (adInfo.getNetwork_mode() == DirectionalMode.INCLUDED.getCode() && StringUtils.isNotBlank(adInfo.getNetwork_type())) {
assignments.add(Assignment.builder()
.name("network_type")
.values(IndexUtils.convertStrToSet(adInfo.getNetwork_type()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
}
if (adInfo.getBrand_mode() == DirectionalMode.INCLUDED.getCode() && StringUtils.isNotBlank(adInfo.getBrand_include())) {
assignments.add(Assignment.builder()
.name("brand_include")
.values(IndexUtils.convertStrToSet(adInfo.getBrand_include()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
}
if (adInfo.getCrowd_mode() == DirectionalMode.INCLUDED.getCode() && StringUtils.isNotBlank(adInfo.getCrowd_include())) {
assignments.add(Assignment.builder()
.name("crowd_include")
.values(IndexUtils.convertStrToSet(adInfo.getCrowd_include()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
}
if (adInfo.getCrowd_mode() == DirectionalMode.INCLUDED.getCode() && StringUtils.isNotBlank(adInfo.getCrowd_exclude())) {
assignments.add(Assignment.builder()
.name("crowd_exclude")
.values(IndexUtils.convertStrToSet(adInfo.getCrowd_exclude()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(false)
.build());
}
if (StringUtils.isNotBlank(adInfo.getInstall_pkg_include())) {
assignments.add(Assignment.builder()
.name("install_pkg_include")
.values(IndexUtils.convertStrToSet(adInfo.getInstall_pkg_include()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
}
if (StringUtils.isNotBlank(adInfo.getInstall_pkg_exclude())) {
assignments.add(Assignment.builder()
.name("install_pkg_exclude")
.values(IndexUtils.convertStrToSet(adInfo.getInstall_pkg_exclude()))
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(false)
.build());
}
if (adInfo.getType() == DeliveryMedia.CHANNEL_PACKAGE.getCode() && StringUtils.isNotBlank(adInfo.getChannel()) && StringUtils.isNotBlank(adInfo.getChannel_package())) {
Set<String> chanelSet = IndexUtils.convertStrToSet(adInfo.getChannel());
Set<String> packageSet = IndexUtils.convertStrToSet(adInfo.getChannel_package());
if (!CollectionUtils.isEmpty(chanelSet) && !CollectionUtils.isEmpty(packageSet)) {
assignments.add(Assignment.builder()
.name("channel")
.values(chanelSet)
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
assignments.add(Assignment.builder()
.name("package_name")
.values(packageSet)
.operatorEnum(OperatorEnum.CONTAINS_AT_LEAST_ONE)
.includeBase(true)
.build());
}
}
return assignments.stream()
.filter(assignment -> !CollectionUtils.isEmpty(assignment.getValues()))
.collect(Collectors.toSet());
}
}
二、重构目标
在动手重构前,我们先明确目标:
✅ 消除大量 if 判断 ✅ 消除重复的 Assignment 构建代码 ✅ 将「判断逻辑」与「构建逻辑」解耦 ✅ 新增规则时,不修改主流程 ✅ 让代码像配置一样可读
三、核心思路:规则抽象(Rule Abstraction)
<font color=blut size=4.8> 抽象出「规则」这个概念</font>
我们发现,每一个 if 分支本质上都在回答 4 个问题:
- 字段名是什么?
- 原始值是什么?
- 在什么条件下生效?
- 如何构建 Assignment?
于是,引入一个统一的规则模型:
每一个规则描述「在什么条件下,用什么方式,生成一个 Assignment」。AssignmentRule 将判断和构建逻辑完全封装
public class AssignmentRule {
private final String name;
private final String rawValue; // 原始字符串,空则无效
private final Supplier<Boolean> condition;
private final OperatorEnum operatorEnum;
private final boolean includeBase;
public AssignmentRule(AssignmentFieldEnum assignmentFieldEnum, String rawValue,
Supplier<Boolean> condition,
OperatorEnum operatorEnum,
boolean includeBase) {
this.name = assignmentFieldEnum.getFieldName();
this.rawValue = rawValue;
this.condition = condition;
this.operatorEnum = operatorEnum;
this.includeBase = includeBase;
}
public boolean isValid() {
return StringUtils.isNotBlank(rawValue) && condition.get();
}
public Assignment build() {
Set<String> valueSet = IndexUtils.convertStrToSet(rawValue);
if (CollectionUtils.isEmpty(valueSet)) {
return null;
}
return Assignment.builder()
.name(name)
.values(valueSet)
.operatorEnum(operatorEnum)
.includeBase(includeBase)
.build();
}
}
<font color=blut size=4.8>关键设计点</font>
- **Supplier
condition** - 将 if 判断「函数化」
- 让规则具备自解释能力
- 统一 build 逻辑
- 所有 Assignment 构建方式一致
- 天然支持扩展
- 新增规则 ≈ 新增一行配置
四、重构后的设计
<font color=blut size=4.8>主流程:规则列表 + 流式处理</font>
重构后,<font color=blut size=4.5>getKeyList</font> 的核心逻辑非常清晰:
public class IndexItemUtils {
public static Set<Assignment> getKeyList(AdInfo adInfo) {
List<AssignmentRule> rules = Arrays.asList(
new AssignmentRule(AssignmentFieldEnum.CHANNEL, adInfo.getChannel(),
() -> true,
OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.PACKAGE_NAME, adInfo.getPackage_name(),
() -> adInfo.getType() == DeliveryMedia.INCLUDE_PACKAGE.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.PACKAGE_NAME, adInfo.getPackage_name(),
() -> adInfo.getType() == DeliveryMedia.EXCLUDE_PACKAGE.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, false),
new AssignmentRule(AssignmentFieldEnum.PACKAGE_NAME, adInfo.getChannel_package(),
() -> adInfo.getType() == DeliveryMedia.INCLUDE_CHANNEL_PACKAGE.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.PACKAGE_NAME, adInfo.getChannel_package(),
() -> adInfo.getType() == DeliveryMedia.EXCLUDE_CHANNEL_PACKAGE.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, false),
new AssignmentRule(AssignmentFieldEnum.CITIES, adInfo.getCities(),
() -> adInfo.getCity_mode() == DirectionalMode.INCLUDED.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.CITY_EXCLUDE, adInfo.getCities(),
() -> adInfo.getCity_mode() == DirectionalMode.EXCLUDED.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, false),
new AssignmentRule(AssignmentFieldEnum.OS_TYPE, adInfo.getOs_type(),
() -> !Objects.equals(adInfo.getOs_type(), "COMMON"),
OperatorEnum.EQUALITY, true),
new AssignmentRule(AssignmentFieldEnum.NETWORK_TYPE, adInfo.getNetwork_type(),
() -> adInfo.getNetwork_mode() == DirectionalMode.INCLUDED.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.BRAND_INCLUDE, adInfo.getBrand_include(),
() -> adInfo.getBrand_mode() == DirectionalMode.INCLUDED.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.CROWD_INCLUDE, adInfo.getCrowd_include(),
() -> adInfo.getCrowd_mode() == DirectionalMode.INCLUDED.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.CROWD_EXCLUDE, adInfo.getCrowd_exclude(),
() -> adInfo.getCrowd_mode() == DirectionalMode.EXCLUDED.getCode(),
OperatorEnum.CONTAINS_AT_LEAST_ONE, false),
new AssignmentRule(AssignmentFieldEnum.INSTALL_PKG_INCLUDE, adInfo.getInstall_pkg_include(),
() -> true, OperatorEnum.CONTAINS_AT_LEAST_ONE, true),
new AssignmentRule(AssignmentFieldEnum.INSTALL_PKG_EXCLUDE, adInfo.getInstall_pkg_exclude(),
() -> true, OperatorEnum.CONTAINS_AT_LEAST_ONE, false)
);
Set<Assignment> assignments = new HashSet<>();
handleOsVersion(adInfo, assignments);
rules.stream()
.filter(AssignmentRule::isValid)
.map(AssignmentRule::build)
.filter(Objects::nonNull)
.forEach(assignments::add);
return assignments;
}
}
- 规则是否有效
- 构建 Assignment
- 收集结果
五、重构收益分析
📈 **可读性**
- 从 200+ 行 if 判断
- 变成一眼可扫的规则列表
🔧 **可维护性**
- 修改规则 → 只改规则
- 不再破坏主流程
🚀 **扩展性**
- 新增定向字段? 不用再 copy 一整段 if
🧪 **可测试性**
- <font size=4.5 color=blut>
AssignmentRule</font> 可单测 - 条件判断和构建逻辑都能独立验证
六、适用场景总结
这种「规则驱动重构」非常适合:
- 广告 / 推荐 / 风控等**规则密集型系统**
- 有大量「条件 + 构建对象」逻辑的代码
- 经常新增、调整业务判断的模块
七、结语
好的重构,不是炫技,而是**让代码更像业务本身**。
当你发现:
“我只是想加一个条件,却要改 5 个地方”
那基本就说明:**是时候把 if-else 抽象成规则了。**
希望这个案例能对你在实际工程中的重构决策有所启发。