救命啊!我的 Java 代码又臭又长,这 7 个“雷区”全踩了!大佬求带,这篇看完感觉能少走十年弯路!

🧾 引言

即使是经验丰富的 Java 开发者,有时也会在代码中不经意地留下一些“代码坏味道 (code smells)”——这些模式虽然本身可能不是 Bug,但它们会让代码更难理解、维护或扩展

✅ 代码坏味道不是 Bug
✅ 但它们是设计中存在问题的警告信号

在这篇指南中,你将了解到每个开发者都必须认识的 7 个常见的 Java 代码坏味道 —— 以及如何通过可运行的示例来修复它们


✅ 1. 过长方法 (Long Methods)

当一个方法变得过于冗长,并且试图承担多种职责时,它就会:

  • • 难以阅读

  • • 难以维护

  • • 难以测试

  • • 当需求变更时更容易出错

这种坏味道违反了单一职责原则 (Single Responsibility Principle, SRP),这是整洁编码最重要的基石之一。

理想目标: ✅ 每个方法都应该只做一件事,并把它做好

❌ 真实世界的坏例子:乱糟糟的 processOrder()
假设你的 processOrder 方法长这样:

public voidprocessOrder(Order order) {
    // 步骤 1: 校验订单
    if (order == null) {
        thrownewIllegalArgumentException("订单不能为空");
    }
    if (order.getItems() == null || order.getItems().isEmpty()) {
        thrownewIllegalArgumentException("订单至少需要包含一个商品");
    }
    for (OrderItem item : order.getItems()) {
        if (item.getQuantity() <= 0) {
            thrownewIllegalArgumentException("商品数量必须为正数");
        }
    }

    // 步骤 2: 计算总金额
    doubletotalAmount=0;
    for (OrderItem item : order.getItems()) {
        totalAmount += item.getPrice() * item.getQuantity();
    }
    if (order.isDiscountApplicable()) {
        totalAmount = totalAmount * 0.9; // 应用 10% 折扣
    }

    // 步骤 3: 保存到数据库
    DatabaseConnectiondb=newDatabaseConnection(); // 假设这是个数据库连接类
    db.connect();
    db.saveOrder(order, totalAmount);
    db.close();

    // 步骤 4: 发送确认邮件
    EmailServiceemailService=newEmailService(); // 假设这是邮件服务类
    Stringmessage="感谢您的订单。总金额为: ¥" + totalAmount;
    emailService.sendEmail(order.getUserEmail(), message);

    // 步骤 5: 更新库存
    for (OrderItem item : order.getItems()) {
        InventoryService.updateStock(item.getProductId(), -item.getQuantity()); // 假设这是库存服务
    }
}

❌ 这里有什么问题?

  • • 这个方法自己干了 5 件不同的事情:

    1. 1. 订单校验逻辑

    2. 2. 总金额计算逻辑

    3. 3. 数据库连接和持久化

    4. 4. 邮件内容组织和发送

    5. 5. 库存更新

  • • 难以阅读。

  • • 难以独立测试每个部分。

  • • 想安全地修改任何一部分都很困难。

✅ 真正的修复:重构成更小、更专注的方法
这是一个真正整洁的重构版本:

public voidprocessOrder(Order order) {
    validateOrder(order); // 提取订单校验逻辑
    doubletotalAmount= calculateTotal(order); // 提取总金额计算逻辑
    saveOrder(order, totalAmount); // 提取保存订单逻辑
    sendConfirmationEmail(order, totalAmount); // 提取发送确认邮件逻辑
    updateInventory(order); // 提取更新库存逻辑
}

// 单独的校验方法
privatevoidvalidateOrder(Order order) {
    if (order == null) {
        thrownewIllegalArgumentException("订单不能为空");
    }
    if (order.getItems() == null || order.getItems().isEmpty()) {
        thrownewIllegalArgumentException("订单至少需要包含一个商品");
    }
    for (OrderItem item : order.getItems()) {
        if (item.getQuantity() <= 0) {
            thrownewIllegalArgumentException("商品数量必须为正数");
        }
    }
}

// 单独的总金额计算方法
privatedoublecalculateTotal(Order order) {
    doubletotalAmount=0;
    for (OrderItem item : order.getItems()) {
        totalAmount += item.getPrice() * item.getQuantity();
    }
    if (order.isDiscountApplicable()) {
        totalAmount *= 0.9; // 应用 10% 折扣
    }
    return totalAmount;
}

// 单独的保存订单方法 (使用 try-with-resources 确保连接关闭)
privatevoidsaveOrder(Order order, double totalAmount) {
    try (DatabaseConnectiondb=newDatabaseConnection()) { // 假设 DatabaseConnection 实现了 AutoCloseable
        db.connect();
        db.saveOrder(order, totalAmount);
    } // db.close() 会被自动调用
}

// 单独的发送确认邮件方法
privatevoidsendConfirmationEmail(Order order, double totalAmount) {
    EmailServiceemailService=newEmailService();
    Stringmessage="感谢您的订单。总金额为: ¥" + totalAmount;
    emailService.sendEmail(order.getUserEmail(), message);
}

// 单独的更新库存方法
privatevoidupdateInventory(Order order) {
    for (OrderItem item : order.getItems()) {
        InventoryService.updateStock(item.getProductId(), -item.getQuantity());
    }
}

🎯 为什么这样重构是正确的

✅ 单一职责 (Single Responsibility): 每个私有方法现在都只精确地处理一项任务。
✅ 可测试性 (Testability): 你现在可以独立地测试 calculateTotal()validateOrder() 等方法了。
✅ 可维护性 (Maintainability): 任何变更——比如新的折扣规则或库存管理逻辑——都只局限在特定的方法内。
✅ 可读性 (Readability): processOrder() 方法现在读起来就像清晰的伪代码一样。


✅ 2. 重复代码 (Duplicated Code) - 常见的代码坏味道

重复代码意味着在多个地方复制粘贴了相同的逻辑。这会浪费精力、造成不一致性,并增加 Bug 出现的几率

✅ DRY 原则 (Don't Repeat Yourself - 不要重复你自己) 能帮助你避免代码重复。

❌ 坏例子:重复的税费计算
假设你正在构建一个购物应用,需要在多处计算税费。
你可能会看到这样的重复代码:

// 在 ProductService.java 中
double tax = product.getPrice() * 0.18; // 税率 18%

// 在 InvoiceService.java 中
double tax = item.getAmount() * 0.18; // 又是 18%

// 在 BillingService.java 中
double tax = bill.getTotal() * 0.18; // 还是 18%

❌ 为什么这样不好?

  • • 同样的税费计算逻辑(税率 18%)在三个不同的地方重复编写。

  • • 如果明天消费税(GST)从 18% 变成 20%,➔ 你将不得不手动找到所有出现该逻辑的地方并一一修改。

  • • 很有可能漏掉某个地方,导致计算错误

✅ 好例子:集中化逻辑
与其重复相同的代码,不如将它提取到一个可复用的方法中

第一步:创建一个简单的工具类

public classTaxCalculator {

    privatestaticfinaldoubleGST_RATE=0.18; // 将税率定义为常量, 18%

    publicstaticdoublecalculateTax(double amount) {
        return amount * GST_RATE;
    }
}

第二步:重构各个 Service
现在,任何需要计算税费的地方,都可以调用这个统一的方法:

// 在 ProductService.java 中
double tax = TaxCalculator.calculateTax(product.getPrice());

// 在 InvoiceService.java 中
double tax = TaxCalculator.calculateTax(item.getAmount());

// 在 BillingService.java 中
double tax = TaxCalculator.calculateTax(bill.getTotal());

✅ 一个通用的方法,到处都在用。

🛒 真实场景
假设 6 个月后,政府(比如印度政府)将消费税(GST)提高到 20%

  • • 使用旧的、重复的烂代码:🔴 你可能需要手动修改 10-15 个不同的文件

  • • 使用整洁的、重构后的好代码:✅ 你只需要在 TaxCalculator.java 文件中更新一行代码
    private static final double GST_RATE = 0.20; // 更新 GST 税率
    然后你的整个应用程序就会自动采用新的税率规则 —— 其他任何代码都不需要修改!

✅ 3. 过大类 (Large Classes) - 也叫“上帝类 (God Classes)”

一个上帝类是指那种试图包揽一切的类:

  • • 它可能同时管理业务逻辑、数据持久化、外部通信、日志记录等等——所有这些都塞在它自己内部。

  • • 它严重违反了单一职责原则 (SRP):一个类应该只有一个引起它变化的原因。

❌ 真实世界的坏例子:OrderService 演变成上帝类
假设你正在构建一个电子商务应用程序。起初,你创建了一个简单的 OrderService,但随着时间的推移,它不受控制地膨胀起来:

public classOrderService { // 这个类干了太多事!

    publicvoidprocessOrder(Order order) {
        // 校验订单
        if (order.getItems() == null || order.getItems().isEmpty()) {
            thrownewIllegalArgumentException("订单至少需要包含一个商品");
        }
        // 保存订单
        database.save(order); // 假设 database 是个依赖
    }

    publicvoidsendConfirmation(Order order) {
        // 发送确认邮件
        EmailSenderemailSender=newEmailSender(); // 不好的实践:直接 new
        emailSender.send(order.getUserEmail(), "您的订单已确认!");
    }

    publicvoidupdateStock(Order order) {
        // 更新库存
        for (Item item : order.getItems()) {
            InventoryManager.updateStock(item.getProductId(), -item.getQuantity()); // 静态方法调用?
        }
    }

    publicvoidcalculateDiscount(Order order) {
        // 应用折扣逻辑
        if (order.getTotalAmount() > 1000) {
            order.setDiscount(0.10);
        }
    }

    publicvoidlogAnalytics(Order order) {
        // 记录分析数据
        System.out.println("订单已记录用于分析: " + order.getId());
    }
}

❌ 为什么这样不好?

  • • 多种职责混杂在一起:

    • • 订单校验

    • • 订单持久化

    • • 邮件发送

    • • 库存管理

    • • 折扣计算

    • • 分析数据记录

  • • 这个类管得太宽了

  • • 难以测试: 你必须 Mock 所有相关的依赖(数据库、邮件服务、库存服务等)。

  • • 难以维护: ➔ 如果邮件逻辑变了,你得修改 OrderService。 ➔ 如果库存逻辑变了,你还得修改 OrderService

  • • 职责越多 = 引起变化的原因越多 = 代码越脆弱。

✅ 如何修复:拆分成更小、更专注的服务
应用单一职责原则 (SRP):✅ 每个类应该只专注于一项工作

重构后的版本:

// 只负责订单相关的核心处理逻辑
publicclassOrderProcessor {
    // private Database database; // 应该通过构造器注入
    publicvoidprocess(Order order) {
        if (order.getItems() == null || order.getItems().isEmpty()) {
            thrownewIllegalArgumentException("订单至少需要包含一个商品");
        }
        database.save(order);
    }
}

// 只负责发送邮件
publicclassEmailService {
    // private EmailSender emailSender; // 应该通过构造器注入
    publicvoidsendConfirmation(Order order) {
        // EmailSender emailSender = new EmailSender(); // 避免直接 new
        emailSender.send(order.getUserEmail(), "您的订单已确认!");
    }
}

// 只负责库存更新
publicclassInventoryService {
    publicvoidupdateStock(Order order) {
        for (Item item : order.getItems()) {
            // InventoryManager.updateStock(item.getProductId(), -item.getQuantity());
            // 最好也是通过注入的依赖来操作库存
        }
    }
}

// 只负责折扣计算
publicclassDiscountService {
    publicvoidapplyDiscount(Order order) {
        if (order.getTotalAmount() > 1000) {
            order.setDiscount(0.10);
        }
    }
}

// 只负责分析数据记录
publicclassAnalyticsService {
    publicvoidlogOrder(Order order) {
        System.out.println("订单已记录用于分析: " + order.getId());
    }
}

✅ 现在,在主工作流程中 (比如一个 CheckoutController):

public classCheckoutController {
    // 通过依赖注入获取这些 Service 实例 (这里用 new 仅为示例)
    privatefinalOrderProcessororderProcessor=newOrderProcessor();
    privatefinalEmailServiceemailService=newEmailService();
    privatefinalInventoryServiceinventoryService=newInventoryService();
    privatefinalDiscountServicediscountService=newDiscountService();
    privatefinalAnalyticsServiceanalyticsService=newAnalyticsService();

    publicvoidcheckout(Order order) {
        orderProcessor.process(order);
        discountService.applyDiscount(order);
        inventoryService.updateStock(order);
        emailService.sendConfirmation(order);
        analyticsService.logOrder(order);
    }
}

如果你正在使用 Spring Boot,你可以将这些拆分后的服务声明为 Spring Bean,并通过依赖注入来使用它们。

📚 真实场景
假设你的产品经理跑来跟你说:
“现在我们希望在用户下单时,除了发送邮件通知,还要发送短信通知。”

  • • 不拆分(烂代码):

    • • 你又得往那个已经臃肿不堪的 OrderService 里塞更多代码。

    • • 导致更多混乱,更难理解。

  • • 使用整洁的服务(好代码):

    • • 你只需要修改 EmailService(如果发送通知是它的职责范围)或者创建一个新的 SmsService

    • • OrderProcessor 和其他服务类完全不受影响

    • • ✅ 最小化、安全的变更。

✅ 关键规则
如果你的类有超过一个可能引起它变化的原因 —— 立刻拆分它
✅ 一个类 = 一项工作。
✅ 这能让系统保持整洁、可扩展,并且易于修改


✅ 4. 方法参数过多 (Too Many Parameters in Methods)

当一个方法需要太多参数(比如 4个、5个,甚至更多)时:

  • • 它会变得难以阅读

  • • 调用时很容易搞错参数的顺序

  • • 后续想要增加或修改参数会变得非常痛苦

  • • 这个方法给人感觉很笨重,而且很脆弱

✅ 最佳实践: 将相关的参数组织到一个单独的对象中(比如 DTO 或专门的请求类 Request Class)。

❌ 真实世界的坏例子:createUser() 方法
假设你正在开发一个用户注册模块
下面是一个参数列表乱糟糟的方法:

// 参数太多,看着就头大
public void createUser(String name, String email, int age, String country, String password) {
    // 注册逻辑...
    System.out.println("注册用户: " + name + ", 邮箱: " + email + "...");
}

当有人这样调用它时:

// 调用时很容易传错,比如把 country 和 email 的位置搞反
createUser("Ankit", "ankit@example.com", 28, "India", "secret123");

❌ 这里有什么问题?

  • • 在调用这个方法的地方,这些参数的具体含义都丢失了(只看到一串值)。

  • • 非常容易不小心把参数顺序搞错 —— 比如,把 country 的值传给了 email 参数。

  • • 以后想扩展这个方法(比如增加电话号码、地址等字段)将会破坏所有已有的调用点

✅ 如何修复:使用请求对象 (Request Object)
将相关的参数组织到一个简单的类中 —— 可以用普通的类,或者更好的方式是使用 record (Java 16+ 特性)。

第一步:创建一个 UserRegistrationRequest 类 (使用 record)

// 使用 record 定义一个不可变的数据类
public record UserRegistrationRequest(
    String name,
    String email,
    int age,
    String country,
    String password
    // record 会自动生成构造函数、getter、equals、hashCode、toString
) {}

✅ record 在这里简直是完美选择:

  • • 非常简洁

  • • 自动就是不可变的 (immutable)

  • • 非常适合用作简单的数据传输对象 (DTOs)

第二步:重构 createUser() 方法

// 方法参数现在只有一个请求对象,非常清晰
public void createUser(UserRegistrationRequest request) {
    // 通过 getter (record 会自动生成) 清爽地访问字段
    System.out.println("正在注册用户: " + request.name());
    System.out.println("邮箱: " + request.email());
    System.out.println("国家: " + request.country());
    // 实际的注册逻辑...
}

第三步:调用方法

// 先创建请求对象
UserRegistrationRequest request = new UserRegistrationRequest(
    "Ankit", "ankit@example.com", 28, "India", "secret123"
);
// 再调用方法
createUser(request);

✅ 现在这个方法调用不言自明。✅ 不会再有意外搞错参数顺序的风险了。

🛒 真实场景
明天,你的产品经理说:
“现在,用户注册时还必须提交他们的电话号码和性别。”

  • • 没有请求对象(烂代码):
    // 方法签名变得更长更丑
    public void createUser(String name, String email, int age, String country, String password, String phoneNumber, String gender) { /* ... */ }
    • • 你将需要在所有调用 createUser 的地方都加上 2 个新的参数

    • • 并且需要手动修复所有已有的方法调用 —— 风险非常高!

  • • 使用请求对象(好代码):
    public record UserRegistrationRequest(
        String name,
        String email,
        int age,
        String country,
        String password,
        String phoneNumber, // 新增电话号码
        String gender      // 新增性别
    ) {}
    • • createUser 方法的签名完全不需要改变

    • • 已有的方法调用点也不需要修改(除非你希望它们立即开始传递新字段的值,那也只需要修改请求对象的创建过程)。

    • • ✅ 最小化变更,最大化稳定性。

    • • 你只需要在 UserRegistrationRequest 这个 record(或类)中添加两个新的字段

“小方法 + 少参数 = 快乐的程序员和稳定的代码库。”

🎯 彩蛋:另一个进阶技巧
如果你的请求对象中有很多可选参数(例如,电话号码、地址、性别等字段都是可选的),✅ 可以考虑使用构建者模式 (Builder Pattern) 来更优雅、更灵活地创建这个请求对象:

// 假设 UserRegistrationRequest 有一个 builder() 方法
UserRegistrationRequest request = UserRegistrationRequest.builder()
    .name("Ankit")
    .email("ankit@example.com")
    .age(28)
    .country("India")
    .password("secret123")
    // .phoneNumber("1234567890") // 可选参数
    .build();

(当存在许多可选字段时,Builder 模式非常有用。)


✅ 5. 基本类型偏执 (Primitive Obsession)

基本类型偏执 指的是:

  • • 过度使用像 Stringintdouble 这样的基础数据类型。

  • • 而不是创建能够表达领域概念的、有意义的类

❌ 它会隐藏数据背后的真实含义
❌ 导致代码脆弱、易出错

✅ 最佳实践: 使用值对象 (Value Objects)(小而有意义的、通常不可变的类)来表示重要的领域概念。

❌ 真实世界的坏例子:银行转账方法
假设你正在开发一个银行应用程序
下面是一个在账户间转账的方法:

// 参数都是基本类型,缺乏业务含义和类型安全
public void transfer(String fromAccountId, String toAccountId, double amount) {
    // 转账逻辑...
    System.out.println("从账户 " + fromAccountId + " 向账户 " + toAccountId + " 转账 " + amount + " 元");
}

❌ 这里有什么问题?

  • • String 类型完全无法表明它代表的是一个“账户ID”。

  • • double 类型对于金额操作没有任何保护(可能会有舍入精度问题、币种不匹配等问题)。

  • • 任何人都有可能不小心传错数据 —— 比如,把转出账户ID和转入账户ID的位置搞反,或者传入一个随意的字符串作为账户ID。

  • • 没有封装与这些概念相关的领域行为(比如校验账户ID的格式、或格式化货币显示)。

✅ 如何修复:引入值对象
将重要的领域概念(比如 AccountId 账户ID、Money 金额)建模成强类型的类,而不是简单地使用基本数据类型。

第一步:创建值对象

// 一个类型安全的 AccountId 表示
publicrecordAccountId(String value) { // 使用 record 保证不可变性
    public AccountId { // record 的紧凑构造函数,用于校验
        if (value == null || value.isBlank()) {
            thrownewIllegalArgumentException("账户ID不能为空或空白");
        }
        // 可以在这里加入更复杂的格式校验逻辑
    }
}

// 一个类型安全的 Money 表示 (更完善的 Money 类会包含币种 Currency)
publicrecordMoney(double amount) { // 实际项目中,金额应该用 BigDecimal
    public Money {
        if (amount <= 0) {
            thrownewIllegalArgumentException("金额必须大于零");
        }
        // 可以加入精度、币种等校验
    }
}

✅ 校验规则现在被封装在值对象自身内部了,创建即校验。

第二步:更新转账方法

// 方法签名现在更具业务含义,且类型安全
public void transfer(AccountId from, AccountId to, Money amount) {
    // 转账逻辑...
    System.out.println("正在从账户 " + from.value() + " 向账户 " + to.value() + " 转账 ¥" + amount.amount());
}

第三步:调用方法

// 创建值对象实例
AccountIdfromAccount=newAccountId("ACC12345");
AccountIdtoAccount=newAccountId("ACC67890");
MoneyamountToTransfer=newMoney(5000.0); // 注意:真实项目中金额用 BigDecimal

// 调用转账方法
transfer(fromAccount, toAccount, amountToTransfer);

🏦 真实场景
假设明天业务需求变更:
“账户ID必须以 'ACC' 开头,后面跟 5 位数字。”

  • • 没有值对象(烂代码):

    • • 你将必须在所有接收账户ID作为 String 参数的地方手动添加这个校验逻辑(非常繁琐且容易遗漏)。

  • • 使用值对象(好代码):
    public record AccountId(String value) {
        public AccountId { // 紧凑构造函数
            if (value == null || !value.matches("^ACC\\d{5}$")) { // 使用正则表达式校验格式
                throw new IllegalArgumentException("无效的账户ID格式。正确格式: ACC + 5位数字");
            }
        }
    }

    ✅ 这个校验规则会立刻在所有使用 AccountId 的地方生效 —— 在 transferdepositwithdraw 等方法中都不再需要额外的检查了,因为创建 AccountId 对象时就已经保证了其有效性。

    • • 你只需要增强 AccountId 这个 record 的构造函数(或工厂方法)

“如果一个数据重要到需要在系统中各处传递,那它就重要到应该拥有自己的类型。”


✅ 6. 用注释代替清晰的代码(或者说,糟糕的代码才需要大量注释去解释)

给一段代码添加注释来解释它“做了什么”,通常表明这段代码本身写得不够清晰

❌ 注释变成了对糟糕命名或模糊逻辑的“创可贴”。
❌ 当代码发生改变,但注释没有同步更新时,注释就会过时并产生误导。

✅ 最佳实践: 编写富有表现力、能自我解释的代码 —— 让代码本身讲述故事,而不是依赖注释。

❌ 真实世界的坏例子:用注释解释代码

// 检查用户是否成年
if (age >= 18) { // "18" 是什么?成年标准?
    allowAccess();
}

❌ 为什么这样不好?

  • • 之所以需要这条注释,是因为 age >= 18 这个条件本身不够直观地表达“成年”这个业务概念

  • • 如果将来“成年”的逻辑发生变化(比如从 18 岁改成 21 岁),有人可能会更新条件 age >= 21,但忘记更新注释 —— 导致注释产生误导。

  • • 你在依赖人工的解释,而不是代码本身的清晰度

✅ 如何修复:使用富有表现力的代码(比如提取方法)
将这个“含义”提取到一个命名清晰的方法中:

// 直接调用一个能清晰表达意图的方法
if (isAdult(age)) {
    allowAccess();
}

// 这个方法的名字清楚地说明了它的作用
private boolean isAdult(int age) {
    final int ADULT_AGE_THRESHOLD = 18; // 将魔法数字定义为常量
    return age >= ADULT_AGE_THRESHOLD;
}

✅ 现在,即使没有任何注释,任何人都能立刻明白:
“我们在允许访问之前,正在检查用户是否已成年。”

🏛️ 真实场景
假设明天业务规则发生变化:
“现在成年标准是 21 岁,而不是 18 岁了。”

  • • 没有清晰代码(烂代码):

    • • 开发者可能会将条件更新为 age >= 21

    • • 但很容易忘记更新旧的注释 // 检查用户是否成年 (18+)

    • • ❌ 导致代码和文档(注释)不一致。

  • • 使用清晰代码(好代码):
    private boolean isAdult(int age) {
        final int ADULT_AGE_THRESHOLD = 21; // 只需修改这里
        return age >= ADULT_AGE_THRESHOLD;
    }

    ✅ 不用担心注释过时的问题。
    ✅ 逻辑保持清晰和正确。

    • • 只需要更新 isAdult() 方法内部的常量即可:

“最好的注释就是你根本不需要写的那种——因为你的代码已经把一切都解释清楚了。”
(当然,对于复杂的业务逻辑或算法,必要的解释性注释还是有价值的)


✅ 7. 忽略异常处理 (Ignoring Exception Handling)

忽略异常,具体表现为:

  • • 编写空的 catch 代码块 (swallowing exceptions)。

  • • 不加区分地捕获所有 Exception,却不理解或不恰当处理。

这将导致:

  • • 隐藏的 Bug,可能在很久之后才暴露出来。

  • • 静默的失败,用户或系统都不知道某些操作其实已经出错了。

  • • 生产环境中出现难以调试的问题

✅ 好的代码会捕获特定的异常,并进行有意义的处理

❌ 真实世界的坏例子:文件上传失败被忽略
假设你的应用程序允许用户上传头像。
下面是一个糟糕的实现:

public void uploadProfilePicture(File file) {
    try {
        fileStorageService.store(file); // 假设 store 方法可能抛出 FileStorageException
        System.out.println("文件似乎上传成功了..."); // 用户可能看到这个,但实际可能失败了
    } catch (Exception e) {
        // 默默地忽略了所有异常,什么也不做!
        // 或者只是 e.printStackTrace(); 这在生产环境等于没处理
    }
}

❌ 为什么这样不好?

  • • 如果发生磁盘空间不足文件权限错误网络故障等问题,➔ 用户(或者调用方)可能仍然以为上传成功了——但实际上它悄无声息地失败了

  • • 没有日志,没有告警 = 以后出现问题时的调试噩梦

  • • 不分青红皂白地捕获了所有 Exception,包括一些可能是程序逻辑错误导致的未预料异常。

✅ 如何修复:正确处理特定异常
✅ 明确你预期可能会发生的、并且能够处理的具体异常类型

例如,假设 fileStorageService.store(file) 方法可能抛出 FileStorageException(自定义异常或库异常):

// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
// 假设有 FileStorageException 和 UserActionException

publicvoiduploadProfilePicture(File file) {
    // private static final Logger logger = LoggerFactory.getLogger(ThisClass.class);
    try {
        fileStorageService.store(file);
        logger.info("头像文件 {} 上传成功。", file.getName());
    } catch (FileStorageException e) { // 只捕获能处理的特定异常
        // 记录详细的错误日志,供排查问题
        logger.error("上传头像文件 {} 失败: {}", file.getName(), e.getMessage(), e);
        // 向上抛出一个更贴近业务的、或者对调用方更友好的异常
        thrownewUserActionException("无法上传头像文件,请稍后再试。", e);
    } catch (IllegalArgumentException e) { // 比如处理文件为空或格式不对的情况
        logger.warn("上传头像文件校验失败: {}", e.getMessage());
        thrownewUserActionException("上传的文件无效,请检查后重试。", e);
    }
    // 不捕获 Exception e,让未预期的运行时异常自然抛出,由全局异常处理器处理
}

🛒 真实场景
想象明天:

  • • 服务器磁盘满了。

  • • 多个用户上传文件都失败了。

  • • 没有妥善处理(烂代码):

    • • 用户不停地重试,以为是网络问题。

    • • 客服工单堆积如山,用户抱怨。

    • • 运维和开发人员很难快速诊断出上传失败的真正原因(因为没有有意义的日志或错误反馈)。

  • • 妥善处理(好代码):

    • • 用户可能会看到清晰的错误提示(由上层或全局异常处理器转换而来):“无法上传头像文件,请稍后再试。”

    • • 日志系统捕获了确切的错误原因:➔ “上传头像文件 xxx.jpg 失败:磁盘空间不足。”

    • • ✅ 容易发现问题。 ✅ 容易修复问题。

✅ 开发者提示
✅ 只捕获你能处理的异常。 对于无法处理的,要么向上抛出,要么转换为更合适的异常类型再抛出。
✅ 记录有意义的日志信息,包括上下文和异常堆栈。
✅ 考虑将底层的技术性异常包装成业务层面或用户层面能理解的自定义异常(如 UserActionExceptionOrderProcessingException 等)。
✅ 永远不要“吞掉”异常(即空的 catch 块),除非你有非常非常充分的理由 —— 即便如此,也至少应该将它们记录下来

“异常就像火警警报 —— 忽略它们并不能阻止火灾的发生。”


✅ 结语
✅ 整洁的代码不是偶然写成的 —— 它是通过尽早发现并修复代码坏味道来实现的。
✅ 在方法、类以及异常处理等方面做一些小的改进,就能极大地提升代码质量

好的开发者让代码能跑起来。优秀的开发者编写出整洁、优雅、能够经受时间考验并持续演进的代码。

从今天开始,在你的代码中留意并修复这些坏味道吧 —— 你会立刻在专业的 Java 开发者中脱颖而出!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java干货

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值