1. 什么是 HardCode?
HardCode(硬编码) = 把本该可配置的值,直接写死在代码里。
// 这些都是 HardCode 👇
// 🔴 安全类
String password = "Admin@123"; // 密码写死
String accessKey = "AKID1234567890"; // 云服务密钥写死
String jdbcUrl = "jdbc:mysql://root:123456@..."; // 连接串含密码
// 🔴 环境类
String url = "http://192.168.1.100:8080/api"; // IP地址写死
String domain = "https://api.prod.company.com"; // 域名写死
String path = "D:/data/export/"; // 路径写死
// 🟡 业务逻辑类
if (status == 4) { ... } // 魔法数字
if (type.equals("PREMIUM")) { ... } // 魔法字符串(多处重复)
String errorMsg = "操作失败,请重试"; // 提示语写死(不支持多语言)
// 🟡 运维参数类
if (retryCount > 3) { ... } // 重试次数写死
Thread.sleep(5000); // 等待时间写死
new ThreadPoolExecutor(10, 50, ...); // 线程池参数写死
// 🟢 其他
String dateFormat = "yyyy-MM-dd HH:mm:ss"; // 日期格式多处重复
String regex = "^1[3-9]\\d{9}$"; // 正则表达式写死
List<String> whitelist = Arrays.asList("admin", "test"); // 白名单写死
正确做法:这些值应该从配置文件、环境变量或配置中心读取,而不是写在代码里。
2. 为什么要治理?
| 问题 | 后果 |
|---|---|
| 密码/密钥写死 | 代码泄露 = 安全事件,可能被监管处罚 |
| IP/端口写死 | 每次换环境都要改代码、重新编译、重新发布 |
| 路径写死 | 换台服务器就跑不起来 |
| 数值写死 | 线上要调参数?对不起,只能发版 |
| 提示语写死 | 产品要改文案?要出海多语言?改代码发版 |
| 白名单写死 | 加个白名单用户?改代码发版 |
一句话:HardCode 让你的代码"只能在特定环境跑",任何变更都要改代码。
3. 做什么?(问题分级)
按严重程度分三级,从上往下治理:
| 级别 | 类型 | 示例 | 处置方式 |
|---|---|---|---|
| 🔴 P0 | 敏感凭证 | 密码、AK/SK、Token、私钥 | 立即整改,阻断代码合并 |
| 🔴 P0 | 网络地址 | IP、端口、域名、完整URL | 立即整改,阻断代码合并 |
| 🟡 P1 | 文件路径 | /home/admin/logs、D:\data | 版本发布前清零 |
| 🟡 P1 | 魔法数值 | if (status == 4) | 版本发布前清零 |
| 🟡 P1 | 魔法字符串 | "PREMIUM"、"SUCCESS" 多处重复 | 版本发布前清零 |
| 🟢 P2 | 业务阈值 | 超时、重试、线程池参数 | 纳入技术债,排期优化 |
| 🟢 P2 | 提示文案 | 错误提示、操作提示 | 纳入技术债,排期优化 |
| 🟢 P2 | 业务白名单 | 用户白名单、IP白名单 | 纳入技术债,排期优化 |
| 🟢 P2 | 通用常量 | 日期格式、正则表达式 | 纳入技术债,排期优化 |
4. 怎么做?(整改方案)
4.1 敏感凭证 → 配置中心/Secrets
// ❌ 错误
private String password = "Admin@123";
// ✅ 正确:从配置注入
@Value("${spring.datasource.password}")
private String password;
配置来源选择:
- K8s 部署 → K8s Secrets
- 传统部署 → Nacos/Apollo 加密配置
- 临时过渡 → 环境变量
4.2 网络地址 → 配置文件 + 服务发现
// ❌ 错误
String url = "http://10.20.30.40:8080/api/user";
// ✅ 正确方式1:配置注入
@Value("${user-service.endpoint}")
private String userServiceEndpoint;
// ✅ 正确方式2:服务发现(微服务)
@FeignClient(name = "user-service")
public interface UserServiceClient { }
4.3 文件路径 → 相对路径 + 配置注入
// ❌ 错误
String path = "D:/data/export/";
// ✅ 正确
@Value("${app.data.dir:${user.home}/app-data}")
private String dataDir;
4.4 魔法数值 → 枚举或常量
// ❌ 错误:4 是什么意思?
if (order.getStatus() == 4) { }
// ✅ 正确:枚举自解释
public enum OrderStatus {
PENDING(1), PROCESSING(2), SHIPPED(3), COMPLETED(4);
}
if (order.getStatus() == OrderStatus.COMPLETED.getCode()) { }
4.5 魔法字符串 → 常量类统一管理
// ❌ 错误:字符串多处重复
if (user.getType().equals("PREMIUM")) { }
if (user.getType().equals("PREMIUM")) { } // 另一个地方又写一遍
// ✅ 正确:常量类统一定义
public class UserTypeConstants {
public static final String PREMIUM = "PREMIUM";
public static final String NORMAL = "NORMAL";
}
if (user.getType().equals(UserTypeConstants.PREMIUM)) { }
4.6 业务阈值 → 配置化
# application.yml
app:
http:
timeout-ms: ${HTTP_TIMEOUT:3000}
max-retry: ${MAX_RETRY:3}
@Value("${app.http.timeout-ms}")
private int timeoutMs;
4.7 提示文案 → 国际化资源文件
// ❌ 错误
throw new BizException("操作失败,请重试");
// ✅ 正确:使用 i18n
// messages.properties: error.operation.failed=操作失败,请重试
throw new BizException(messageSource.getMessage("error.operation.failed", null, locale));
4.8 业务白名单 → 配置或数据库
// ❌ 错误
List<String> whitelist = Arrays.asList("admin", "test");
// ✅ 正确:配置文件
@Value("${app.whitelist.users}")
private List<String> whitelist;
// ✅ 更好:数据库/配置中心(支持动态更新)
@Autowired
private WhitelistService whitelistService;
4.9 通用常量 → 工具类封装
// ❌ 错误:日期格式到处写
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(now);
// ✅ 正确:统一定义
public class DateConstants {
public static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final DateTimeFormatter DATETIME_FORMATTER =
DateTimeFormatter.ofPattern(DATETIME_FORMAT);
}
5. 执行计划
| 周次 | 任务 | 交付物 |
|---|---|---|
| W1 | 扫描存量问题,分配责任人 | 问题清单 |
| W2 | P0 问题清零 | 扫描结果 0 Blocker |
| W3 | P1 问题清零,CI 卡点上线 | 验收报告 |
| W4+ | P2 持续优化 | 技术债清理 |
附:自查清单
- 代码中无明文密码、密钥、Token
- 无硬编码的 IP 地址、端口号、域名
- 无写死的绝对路径
- 业务状态码使用枚举而非数字
- 重复使用的字符串提取为常量
- 超时、重试等参数可通过配置调整
- 用户提示语支持国际化(或至少集中管理)
- 白名单类数据可配置或存数据库
1525

被折叠的 条评论
为什么被折叠?



