曾经接手过一个“祖传”项目吗?打开代码库就像推开一扇吱呀作响的老木门,扑面而来的霉味——上千行的“父类”、随意散落的魔法数字、毫无章法的日志打印……这就是“屎山”的味道。别担心,今天分享的这10个接地气的实践,能帮你把Java后端项目从“危房”改造成“精装房”,让同事接手时直呼“这屎怎么变香了”!
1. 命名不是玄学,是精准定位
- 坏味道:
processData()、doSomething() - 清新剂:用业务名词+动词,明确表达意图
// 模糊的命名
public List<User> get(String type) { ... }
// 清晰的命名:一眼看懂查询条件
public List<User> findActiveUsersByDepartment(String departmentCode) { ... }
2. 短小精悍的函数看着多爽
- 黄金法则:一个函数只做一件事(SRP原则)
- 效果:调试时不再需要上下翻屏找逻辑
// 臃肿的聚合逻辑
public void placeOrder(Order order) {
// 验证库存... 30行
// 计算折扣... 40行
// 生成流水号... 20行
// 发送通知... 30行
}
// 拆解后的清晰结构
public void placeOrder(Order order) {
validateStock(order);
applyDiscounts(order);
generateTransactionId(order);
sendOrderNotification(order);
}
3. 告别魔法数字,常量是身份的象征
- 场景:状态码、配置阈值、枚举值
- 好处:修改时不用玩“大家来找茬”
// 魔法数字噩梦
if (user.getStatus() == 1) { ... }
// 常量赋予意义
public static final int USER_STATUS_ACTIVE = 1;
if (user.getStatus() == USER_STATUS_ACTIVE) { ... }
4. 注释不是遮羞布,代码应自解释
- 要注释的:复杂算法、特殊业务规则
- 不要注释的:getter/setter、显而易见的逻辑
// 反面教材:注释描述简单代码
// 设置用户姓名
user.setName(name);
// 正面教材:注释解释“为什么”
// 根据风控规则,VIP用户跳过地址验证(见需求文档RC-202)
if (user.isVip()) skipAddressVerification();
5. 日志:给系统装上“黑匣子”
- 关键点:区分INFO/DEBUG/ERROR级别
- 必备信息:用户ID、请求ID、关键参数
// 无效日志:只打印状态
log.info("订单状态更新");
// 有效日志:可追溯的上下文
log.info("订单状态更新 [订单号:{}] [原状态:{}] [新状态:{}] [操作人:{}]",
orderId, oldStatus, newStatus, operatorId);
6. 防御式编程:对异常温柔点
- 原则:早校验、早失败、明确异常类型
// 脆弱的代码:可能抛出隐式NPE
public void updateUser(User user) {
user.getAddress().setCity(newCity); // 万一address为null?
}
// 防御性校验:使用Objects工具
public void updateUser(User user) {
Objects.requireNonNull(user, "用户对象不能为空");
Address address = Optional.ofNullable(user.getAddress())
.orElseThrow(() -> new BizException("用户地址未设置"));
address.setCity(newCity);
}
7. DTO/Entity隔离:数据层的“门当户对”
- 问题:用Entity直接返回给前端暴露敏感字段
- 方案:专用DTO控制展示范围
// 实体类 (持久化层)
@Entity
public class User {
private Long id;
private String username;
private String passwordHash; // 敏感!
}
// DTO类 (展示层)
public class UserDTO {
private Long id;
private String username;
// 不包含密码!
}
8. 善用Stream API:告别for循环地狱
- 优势:链式调用提升可读性
- 注意:避免嵌套过深的Stream
// 传统循环:筛选+分组
Map<String, List<User>> departmentUserMap = new HashMap<>();
for (User user : users) {
if (user.isActive()) {
String dept = user.getDepartment();
departmentUserMap.computeIfAbsent(dept, k -> new ArrayList<>()).add(user);
}
}
// Stream优雅实现
Map<String, List<User>> departmentUserMap = users.stream()
.filter(User::isActive)
.collect(Collectors.groupingBy(User::getDepartment));
9. Optional:和NullPointerException说再见
- 正确用法:避免直接调用
get() - 替代方案:
orElse(),orElseThrow()
// 危险操作:可能触发NPE
String city = user.getAddress().getCity();
// Optional安全导航
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知城市"); // 提供默认值
10. 线程池:别让资源成为“野马”
- 坑点:
newFixedThreadPool可能堆积OOM - 推荐:使用
ThreadPoolExecutor定制参数
// 不安全的简易线程池
ExecutorService unsafePool = Executors.newFixedThreadPool(10);
// 推荐:自定义有界队列和拒绝策略
ThreadPoolExecutor safePool = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列防OOM
new ThreadPoolExecutor.CallerRunsPolicy() // 队列满后由调用者线程执行
);
最终决定项目寿命的,不是技术有多新潮,而是代码是否经得起时间的凝视。