ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java 硬编码 switch 重构实战:策略模式替代 paperId==0/1/2/3(附完整代码)

Java 硬编码 switch 重构实战:策略模式替代 paperId==0/1/2/3(附完整代码) 老炮踩坑录 · F06· 翻车现场系列基于「企业融合评估系统」真实源码复盘关键词硬编码分支 · switch 派发 · 策略模式 · 开闭原则 · 魔法值 欢迎阅读个人主页知守观我的专栏老炮踩坑录当前内容策略模式引子2022项目复盘到申报评估模块的时候我给自己出了道题假如产品经理现在走进来说要加第 5 个评估模型我多久能干完我本来想一个模型而已加个表、加个菜单的事。顺着代码走读了四十分钟后背有点发凉。这个项目的评估模型一共四个用一个叫 paperid 的字段编号paperid模型0科技数字化生产诊断1工业互联网平台建设2互联网标杆建设3企业入云编号本身规规矩矩地定义在常量类里SysContants.java:143int UPLOAD_DIAGNOSIS_TYPE_SCCSD 0; int UPLOAD_DIAGNOSIS_TYPE_HLWPT 1; int UPLOAD_DIAGNOSIS_TYPE_WBGGC 2; int UPLOAD_DIAGNOSIS_TYPE_QYSY 3;看着挺像那么回事。问题在于这四个数字被拿去干什么了。案发现场的四张脸第一张脸String 版 switch。ReportController.java:89 统一查询试卷信息入参是 Stringswitch (paperid) { case 1: ReportMaturity reportMaturity maturityService.getReportMaturityById(rapplyid); return result.success(reportMaturity); case 2: ReportCapability reportCapability capabilityService.getReportCapabilityById(rapplyid); return result.success(reportCapability); case 3: ReportCloud reportCloud cloudService.getReportCloudById(rapplyid); return result.success(reportCloud); default: break; } return result.fail(没有查询到试卷信息);paperid0 没有 case静默落进 default。0 号模型的报告查不了前端只会收到一句没有查询到试卷信息跟系统里没这个模型 意思是一样的。第二张脸裸 int 版 if 链。EnterpriseRegistController.java:715 后台导出模型维度得分同一个编号体系这里连常量都懒得用if (paperid 0) { return exportPlustekDiagnosis(response); } else if (paperid 1) { return exportInternetPlatform(response, applyInfoData); } else if (paperid 2) { return exportExampleFactory(response, applyInfoData); } else if (paperid 3) { return exportEnterpriseCloud(response, applyInfoData); } return null;兜底返回 null。一个非法 paperid 请求导出接口返回 200 加空响应体前端的 Excel 解析器收到一坨空气。第三张脸白名单守卫。FileController.java:656 上传诊断附件四个常量组成白名单删除接口749 行抄了一份一模一样的if (paperid ! SysContants.UPLOAD_DIAGNOSIS_TYPE_SCCSD paperid ! SysContants.UPLOAD_DIAGNOSIS_TYPE_HLWPT paperid ! SysContants.UPLOAD_DIAGNOSIS_TYPE_WBGGC paperid ! SysContants.UPLOAD_DIAGNOSIS_TYPE_QYSY) { return result.fail(非法的 paperid); }第四张脸藏在数据层的魔法值。ApplyInfoServiceImpl.java:542ListApplyPapers papersList paperMapper.getApplyPapperListByIds(0, 1, 2, 3);Mapper XML 文件里也没放过三处-- ApplyPapersMapper.xml:32 apply_papers where status 1 and paperid ! 0 -- DiagnosisInfoMapper.xml:63 where enterpriseId #{enterpriseId} and paperid0 and status 1 -- SysBackDeclareListMapper.xml:39 and ati.paperid ! 0paperid ! 0翻成人话是除了精益诊断之外。分类规则被写成了 SQL 里的一个数字。第 5 个模型进来之后模拟一遍。假设新模型叫数据管理成熟度评估编号 4apply_papers 表里插一行菜单挂上企业能选了。然后企业选模型 4 ├─ 查报告 → ReportController 落 default → 没有查询到试卷信息 ├─ 传附件 → FileController 白名单没有 4 → 非法的 paperid ├─ 后台导出 → if 链走完 → return null ├─ 结果列表 → getApplyPapperListByIds(0,1,2,3) 查不到 → 列表里压根没有它 └─ 发起申报 → startDeclarFlow 的 else → 未找到申报项五个路口五种死法。三个安静地错一个明确拒绝一个直接消失。没有任何一个地方会在编译期提醒你 “这里还有一个分支没处理”。我粗粗一数七个文件起步。等复盘时逐个 grep 才发现这套常量在13 个 Java 文件里出现了 64 次光 ApplyInfoServiceImpl 一个文件就占了 21 次外加 3 处 XML。你没法保证自己找全了只能一个接口一个接口点过去点到哪个算哪个。慌就慌在这里。这债是怎么滚起来的看代码分层的痕迹0 号模型和 1/2/3 号明显不是一个时期的东西。0 是精益诊断接口、Mapper、SQL 里到处单独照顾它1/2/3 是第二批连报告结构Maturity/Capability/Cloud都是三胞胎。模型数量的增长顺序就这么被编号和分支化石记录了下来。第一批代码里写死 0第二批把 0/1/2/3 一起写死还顺手补了常量。常量解决了可读性没解决派发结构。更微妙的是这个项目已经有 apply_papers 表了——paperid、模型名、试卷 ID 全在表里列表类接口是数据驱动的菜单和目录加一行数据就行可一旦进入这个模型具体干什么立刻退回 switch。目录开放了行为关着。这就是半拉子状态最坑的地方你以为加个模型就是插一行数据插完才发现后面排着十几个分支等着你呢。改法分三层别上来就策略模式很多文章到这里就甩策略模式了。我先把丑话说前面paperid 在这个项目里的分支根本不在一个维度上。查报告是一个维度导出 Excel 表头是一个维度前置条件校验是一个维度跳转链接拼接又是一个维度。一个策略接口吞不掉所有这些硬吞只会造出一个上帝接口。第一层先让默认分支变响。这是十分钟能干完、收益最大的一步。所有 switch 和 if 链的 default禁止静默 break 和返回 nulldefault: throw new SystemException(ResultEnum.ATI_NOT_FOUND_APPLYITEM);再把支持的编号收成一个集中校验点替换 FileController 里抄了两遍的白名单public final class AssessmentTypes { private static final SetInteger SUPPORTED Collections.unmodifiableSet( new HashSet(Arrays.asList( UPLOAD_DIAGNOSIS_TYPE_SCCSD, UPLOAD_DIAGNOSIS_TYPE_HLWPT, UPLOAD_DIAGNOSIS_TYPE_WBGGC, UPLOAD_DIAGNOSIS_TYPE_QYSY))); ​ private AssessmentTypes() {} public static void checkSupported(int paperid) { if (!SUPPORTED.contains(paperid)) { throw new SystemException(ResultEnum.ATI_NOT_FOUND_APPLYITEM); } } }加模型时漏改一个路口立刻炸在明面上还给了你一份明确的待改清单。静默错误转成显式错误排查成本直接降一个量级。第二层真正多态的派发点用 Spring 注册表。查报告、导出这类 同一动作、不同实现的才轮到策略模式。定义接口每个模型一个实现public interface AssessmentReportStrategy { int paperid(); Object getReport(String rapplyid); } ​ Component public class MaturityReportStrategy implements AssessmentReportStrategy { ​ Autowired private ReportMaturityService maturityService; ​ Override public int paperid() { return SysContants.UPLOAD_DIAGNOSIS_TYPE_GYHLWPTJS; } ​ Override public Object getReport(String rapplyid) { return maturityService.getReportMaturityById(rapplyid); } }Controller 注入全部实现自己建索引RestController public class ReportController { ​ private final MapInteger, AssessmentReportStrategy registry; ​ public ReportController(ListAssessmentReportStrategy strategies) { this.registry strategies.stream() .collect(Collectors.toMap( AssessmentReportStrategy::paperid, Function.identity())); } ​ GetMapping(/searchPaperInfo) public Result searchPaperInfo(String paperid, String rapplyid) { AssessmentReportStrategy strategy registry.get(Integer.valueOf(paperid)); if (strategy null) { throw new SystemException(ResultEnum.ATI_NOT_FOUND_APPLYITEM); } return new Result().success(strategy.getReport(rapplyid)); } }新模型进来加一个ComponentSpring 自动收编Controller 零改动。0 号模型没有报告策略天然查不到错误信息明确不用再靠一个没人维护的 default 兜着。第三层剩下的分支该归数据归数据该归配置归配置。paperid ! 0这种 SQL 里的分类规则背后是模型分组这个概念——在 apply_papers 加个分组字段比在每条 SQL 里写排除条件稳。跳转链接的差异本来就配置在 yml 里继续用配置表达。前置条件校验是业务规则留在 Service但走第一层的集中校验别再复制常量白名单。说句可能挨骂的话switch 本身没那么罪大恶极。三年四个模型变更频率极低一个 switch 加一个会抛异常的 default可读性和成本都合适。真正的债是同一份编号逻辑复制了十几个副本、还配上一群哑巴默认分支。判断要不要上策略模式看的是分支会不会继续长 和 “分支有没有分散复制”而不是教科书第几页写了开闭原则。自查清单检查项怎么搜危险信号switch/if 链的 default 静默处理搜switch看 default 是否 break 或返回 null不抛异常、不记日志 新分支永远漏得悄无声息同一组编号在多处派发搜编号常量名统计文件数三五个文件以上 加一个值得上注册表魔法值写进 XMLmapper XML 里搜字段名配数字分类规则混进 SQL改编号等于拆炸弹目录已数据化、行为没数据化看有没有对应的配置表再看行为层有没有 switch半拉子开闭最有迷惑性新人最容易踩入参类型不统一同一个编号字段有的接口收 String 有的收 Integer“1” 和 1 混着 switch迟早有人传错老炮点评这颗债的利息长在找不全三个字上。硬编码分支最坑的时刻从来不是写代码当天是半年后一个看似简单的需求把所有副本同时激活而你根本不知道副本一共有几份。复盘下来我给自己立的规矩也简单默认分支必须响编号清单必须唯一真正多态的派发才配进注册表。剩下的老老实实用表和配置表达。工具会过时框架会换代“一组编号散落在十几个文件里、每个路口都安静地处理未知值” 这个味道我在哪种技术栈里见了都得绕着走。下期预告《OBS 同名文件互相覆盖客户拿错了别人的诊断评估报告》上期讲的是用户数据串号——B 登录看到了 A 的企业信息。下期讲一个更离谱的文件也送错了人。项目里用华为云 OBS 存文件文件名直接用企业名报告类型。结果两家企业重名后上传的直接把先上传的覆盖了。A 客户下载诊断报告打开一看——是 B 公司的。下期讲这个坑是怎么发现的以及文件名唯一性的三种解决方案。如果你也在用 OBS 或类似对象存储下期这篇得看。如果本文对你有帮助欢迎 点赞 ⭐ 收藏 关注作者 留言你的每一次互动都是我继续更新的动力我们下一篇见我是老炮18 年 Java 老兵仍在一线。关注我不错过每一篇真实案例少踩坑。
返回列表