背景:我们常见的"丑陋"写法
在处理第三方 API 响应时,由于不同服务返回的 Token 字段名不统一,很多开发者会写出这样的代码:
❌ 反面教材:嵌套 if-else 地狱
privateStringparseToken(StringresponseText){if(StringUtils.isBlank(responseText)){returnnull;}try{JSONObjectjson=JSON.parseObject(responseText);// 第一层尝试Stringtoken=json.getString("access_token");if(StringUtils.isNotBlank(token)){returntoken;}// 第二层尝试token=json.getString("accessToken");if(StringUtils.isNotBlank(token)){returntoken;}// 第三层尝试token=json.getString("token");if(StringUtils.isNotBlank(token)){returntoken;}// 第四层尝试:从 data 中找JSONObjectdata=json.getJSONObject("data");if(data!=null){token=data.getString("access_token");if(StringUtils.isNotBlank(token)){returntoken;}token=data.getString("accessToken");if(StringUtils.isNotBlank(token)){returntoken;}token=data.getString("token");if(StringUtils.isNotBlank(token)){returntoken;}}returnnull;}catch(Exceptione){// 解析失败,返回原始文本returnresponseText.trim();}}问题:
- 😫重复代码多:每个字段都要写一遍
if (StringUtils.isNotBlank(token)) - 😫嵌套层次深:if-else 套了 4 层,可读性差
- 😫扩展困难:如果要支持新字段名,需要复制粘贴更多 if
- 😫维护痛苦:逻辑分散,容易漏改
✅ 优雅方案:Utils 实现
privateStringparseToken(StringresponseText){if(StringUtils.isBlank(responseText)){returnnull;}try{JSONObjectjson=JSON.parseObject(responseText);// 第一步:从根节点查找Stringvalue=firstNotBlank(json.getString("access_token"),json.getString("accessToken"),json.getString("token"));if(StringUtils.isNotBlank(value)){returnvalue;}// 第二步:从 data 嵌套对象中查找JSONObjectdata=json.getJSONObject("data");returndata==null?null:firstNotBlank(data.getString("access_token"),data.getString("accessToken"),data.getString("token"));}catch(Exceptionignored){returnresponseText.trim();}}// 核心工具方法:返回第一个非空字符串privateStringfirstNotBlank(String...values){for(Stringvalue:values){if(StringUtils.isNotBlank(value)){returnvalue;}}returnnull;}巧妙之处赏析
1️⃣ 可变参数 + 循环:消除重复代码
privateStringfirstNotBlank(String...values){for(Stringvalue:values){if(StringUtils.isNotBlank(value)){returnvalue;// 找到第一个非空就返回}}returnnull;}妙处:
- 🎯一行顶六行:用 5 行代码替代了 6 个 if-else 分支
- 🎯语义清晰:方法名
firstNotBlank直接表达了意图 - 🎯易于扩展:新增字段只需在调用处加一个参数
对比:
// 丑陋写法:6 行 × 3 次 = 18 行if(StringUtils.isNotBlank(a))returna;if(StringUtils.isNotBlank(b))returnb;if(StringUtils.isNotBlank(c))returnc;// 优雅写法:1 行returnfirstNotBlank(a,b,c);2️⃣ 分层查找:逻辑清晰
// 第一层:根节点Stringvalue=firstNotBlank(json.getString("access_token"),json.getString("accessToken"),json.getString("token"));if(StringUtils.isNotBlank(value)){returnvalue;// 找到就返回}// 第二层:data 嵌套对象JSONObjectdata=json.getJSONObject("data");returndata==null?null:firstNotBlank(data.getString("access_token"),data.getString("accessToken"),data.getString("token"));妙处:
- 🎯职责分离:每一层只负责自己的查找逻辑
- 🎯短路优化:第一层找到就直接返回,不会执行第二层
- 🎯三元表达式:
data == null ? null : ...简洁处理空指针
3️⃣ 容错设计:异常兜底
try{// 尝试解析 JSONJSONObjectjson=JSON.parseObject(responseText);// ... 提取逻辑}catch(Exceptionignored){// 解析失败,返回原始文本(可能是纯文本 Token)returnresponseText.trim();}妙处:
- 🎯兼容性强:支持 JSON 和纯文本两种格式
- 🎯永不崩溃:即使解析失败也能返回可用结果
- 🎯静默降级:不影响主流程
典型场景:
// 场景1:标准 JSON 响应{"access_token":"eyJhbG..."}// 场景2:纯文本响应eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...设计模式解读
策略模式的简化版
这段代码本质上是一个简化的策略模式:
策略1: 从根节点找 access_token/accessToken/token ↓ 失败 策略2: 从 data 节点找 access_token/accessToken/token ↓ 失败 策略3: 返回原始文本(兜底)与传统策略模式的区别:
- ❌ 传统:需要定义接口、多个实现类、工厂方法
- ✅ 简化:用方法调用链 + 短路逻辑实现
责任链模式的影子
firstNotBlank(a, b, c) → 检查 a,非空则返回 → 检查 b,非空则返回 → 检查 c,非空则返回 → 都为空,返回 null这实际上是一个微型的责任链,每个参数依次尝试,直到成功。
扩展性演示
需求1:支持新的字段名jwt_token
丑陋写法:需要加 2 个 if 分支(根节点 + data 节点)
// 根节点token=json.getString("jwt_token");if(StringUtils.isNotBlank(token)){returntoken;}// data 节点token=data.getString("jwt_token");if(StringUtils.isNotBlank(token)){returntoken;}优雅写法:只需在调用处加一个参数
Stringvalue=firstNotBlank(json.getString("access_token"),json.getString("accessToken"),json.getString("token"),json.getString("jwt_token")// ← 新增这一行);需求2:支持更深的嵌套层级data.result.token
优雅写法:继续分层
// 第一层:根节点Stringvalue=firstNotBlank(...);if(StringUtils.isNotBlank(value))returnvalue;// 第二层:data 节点JSONObjectdata=json.getJSONObject("data");if(data!=null){value=firstNotBlank(...);if(StringUtils.isNotBlank(value))returnvalue;// 第三层:data.result 节点JSONObjectresult=data.getJSONObject("result");if(result!=null){returnfirstNotBlank(result.getString("access_token"),result.getString("accessToken"),result.getString("token"));}}returnnull;关键:每一层的逻辑保持一致,都是firstNotBlank(...),易于理解和维护。
性能分析
时间复杂度
| 操作 | 复杂度 | 说明 |
|---|---|---|
firstNotBlank(a, b, c) | O(1) | 最多检查 3 个值 |
json.getString() | O(1) | HashMap 查找 |
| 整体逻辑 | O(1) | 固定次数的查找 |
结论:性能优异,无循环遍历大集合。
内存占用
// 只创建了少量临时对象JSONObjectjson=JSON.parseObject(responseText);// 1 个对象JSONObjectdata=json.getJSONObject("data");// 可能 1 个对象对比丑陋写法:内存占用相同,但代码行数减少 60%。
最佳实践总结
✅ 值得学习的技巧
提取通用工具方法
privateStringfirstNotBlank(String...values){...}- 可复用到其他场景
- 单元测试容易编写
使用可变参数简化调用
firstNotBlank(a,b,c,d,e)// 灵活传入任意数量参数分层查找 + 短路返回
if(found)returnvalue;// 提前退出,避免无效计算异常兜底保证健壮性
catch(Exceptionignored){returnfallbackValue;// 优雅降级}
⚠️ 改进建议
1. 添加日志记录
catch(Exceptione){log.warn("Token 响应解析失败,返回原始文本: {}",e.getMessage());returnresponseText.trim();}原因:生产环境问题排查需要日志。
2. 提取常量
privatestaticfinalString[]TOKEN_FIELDS={"access_token","accessToken","token"};privateStringfindToken(JSONObjectjson){returnfirstNotBlank(json.getString(TOKEN_FIELDS[0]),json.getString(TOKEN_FIELDS[1]),json.getString(TOKEN_FIELDS[2]));}原因:避免魔法字符串,便于统一管理。
3. 支持配置化
// 从配置文件读取支持的字段名@Value("${token.fields:access_token,accessToken,token}")privateString[]tokenFields;privateStringfindToken(JSONObjectjson){String[]values=Arrays.stream(tokenFields).map(json::getString).toArray(String[]::new);returnfirstNotBlank(values);}原因:不同环境可能需要不同的字段名。
对比总结
| 维度 | 丑陋写法 | 优雅写法 |
|---|---|---|
| 代码行数 | ~40 行 | ~15 行 |
| 嵌套层次 | 4 层 if-else | 2 层逻辑 |
| 可扩展性 | 每加一个字段 +6 行 | 每加一个字段 +1 参数 |
| 可读性 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 可维护性 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 复用性 | 无法复用 | firstNotBlank可复用 |
结语
这段代码的精髓在于:
用简单的抽象(
firstNotBlank)消除重复,用分层逻辑保持清晰,用异常兜底保证健壮。
它告诉我们:优雅的代码不一定是高深的设计模式,而是用最合适的方式解决实际问题。
下次遇到类似的"挨个尝试"场景时,记得:
- 提取通用方法
- 使用可变参数
- 分层 + 短路返回
- 异常兜底
这样写出来的代码,既简洁又健壮!🎉