如何识别和解决 Java 代码中的坏味道

简介: 编程中,代码质量随着时间推移逐渐退化是一个普遍问题,这种现象被称为代码坏味道(Code Smell)。代码坏味道并不意味着代码有错误,而是指出可能存在更深层问题的迹象,影响代码的可读性、可维护性和扩展性。识别和解决代码坏味道是提升代码质量的关键步骤。

作为程序员,大家都知道在软件研发的过程中,代码质量的退化是一个常见的问题,也是一个必然的现象,这种现象称之为代码坏味道,它指的是一些可能指示着更深层次问题的迹象。


坏味道本身并不代表存在错误,但是通常是代码维护困难和扩展性差的征兆。识别和解决这些坏味道是我们提升代码质量的重要步骤。


今天灸哥和大家一起聊聊我们常见的代码坏味道以及解决之道。


识别坏味道


代码坏味道的识别一般是要求开发者具备一定的代码审查能力和对设计原则相关的理解,同时也需要一定的经验和技巧,在日常编码过程中,以下三个手段是可以有助于你识别和解决坏味道:


  1. 代码审查:定期组织团队成员对代码进行审查,可以借助集体智慧来发现潜在的坏味道并及时修复
  2. 代码分析:使用静态的代码分析工具可以帮助程序员自动识别一些常见的代码坏味道
  3. 重构实践:通过不断地重构代码,可以逐渐消除坏味道,提高代码质量


常见坏味道


过长方法


过长方法的坏味道一般表现为方法过于冗长,包含多个逻辑分支和多个职责,难以理解和维护。


具体的表现为:

  • 方法行数超过 50 或者 100 行
  • 方法中有多个嵌套的 if/else 语句
  • 方法的命名难以表达其所有逻辑


一般针对过长方法的解决路径如下:

  • 将方法分解为更小的、职责单一的函数
  • 移除重复代码,使用辅助方法来提高可读性
  • 简化条件表达式,使用早返回减少嵌套


我们来看看具体的代码示例

// 坏味道代码
public void processOrder(Order order) {
    if (order.isValid()) {
        if (order.isExpress()) {
            calculateExpressShipping(order);
        } else {
            calculateStandardShipping(order);
        }
        if (order.isDiscountEligible()) {
            applyDiscount(order);
        }
        persistOrder(order);
    } else {
        throw new IllegalArgumentException("Invalid order");
    }
}
// 重构后的代码
private void validateOrder(Order order) {
    if (!order.isValid()) {
        throw new IllegalArgumentException("Invalid order");
    }
}
private void calculateShipping(Order order) {
    if (order.isExpress()) {
        calculateExpressShipping(order);
    } else {
        calculateStandardShipping(order);
    }
}
private void applyDiscountIfEligible(Order order) {
    if (order.isDiscountEligible()) {
        applyDiscount(order);
    }
}
private void persistOrder(Order order) {
    // Persist order logic...
}


重复代码


重复代码的坏味道一般表现为相同或者非常相似的代码片段在不同的地方重复出现。


具体的表现为:

  • 多个方法或类中有几乎相同的代码块
  • 复制粘贴修改的研发模式


一般针对过长方法的解决路径如下:

  • 将重复的代码提取到一个公共的方法或者类中
  • 使用继承或者组合来共享代码


我们来看看具体的代码示例

// 坏味道代码
public int calculateTotalForOrderA(Order order) {
    int total = 0;
    for (Item item : order.getItems()) {
        total += item.getPrice();
    }
    return total * 0.9; // 10% discount
}
public int calculateTotalForOrderB(Order order) {
    int total = 0;
    for (Item item : order.getItems()) {
        total += item.getPrice();
    }
    return total * 0.9; // 10% discount
}
// 重构后的代码
public int calculateTotalWithDiscount(Order order) {
    int total = 0;
    for (Item item : order.getItems()) {
        total += item.getPrice();
    }
    return total * 0.9; // 10% discount
}


过大的类


过大的类的坏味道一般表现为类承担了过多的职责,包含多个不相关的功能。


具体的表现为:

  • 类中有多个互不相关的功能方法
  • 类的职责难以通过类名表达


一般针对过长方法的解决路径如下:

  • 将类分解为多个更小的类,每个类负责单一职责
  • 使用继承或者接口来组织相关功能


我们来看看具体的代码示例

// 坏味道代码
class User {
    // 用户认证相关方法...
    // 用户信息管理相关方法...
    // 用户订单处理相关方法...
}
// 重构后的代码
class Authenticator {
    // 用户认证相关方法...
}
class UserProfile {
    // 用户信息管理相关方法...
}
class OrderManager {
    // 用户订单处理相关方法...
}


全局状态


全局状态的坏味道一般表现为系统的多个部分依赖于全局变量或者单例状态,导致难以追踪和维护。


具体的表现为:

  • 多个类依赖于同一个全局变量或者单例对象
  • 状态的变化影响整个系统的行为


一般针对过长方法的解决路径如下:

  • 将全局状态封装到类中,提供方法来访问和修改状态
  • 使用依赖注入来管理依赖关系


我们来看看具体的代码示例

// 坏味道代码
class Config {
    private static int maxUsers = 100;
    public static int getMaxUsers() {
        return maxUsers;
    }
    public static void setMaxUsers(int maxUsers) {
        Config.maxUsers = maxUsers;
    }
}
// 重构后的代码
class AppConfig {
    private int maxUsers = 100;
    public int getMaxUsers() {
        return maxUsers;
    }
    public void setMaxUsers(int maxUsers) {
        this.maxUsers = maxUsers;
    }
}


魔法数字


魔法数字的坏味道一般表现为代码中直接使用了未定义的数字常量,缺乏可读性。


具体的表现为:

  • 数字值在代码中多次出现,但没有明确的含义
  • 数字与代码逻辑紧密相关,但未通过命名常量表示


一般针对过长方法的解决路径如下:

  • 将魔法数字替换为命名常量或者配置项
  • 使用美剧或者类常量来提供更好的可读性


我们来看看具体的代码示例

// 坏味道代码
if (list.size() > 10) {
    // ...
}
// 重构后的代码
public static final int MAX_SIZE = 10;
if (list.size() > MAX_SIZE) {
    // ...
}


神秘代码


神秘代码的坏味道一般表现为代码中存在难以理解的复杂表达式或者算法,缺乏注释或者文档说明。


具体的表现为:

  • 代码逻辑复杂,难以一眼看出其意图
  • 缺少文档或者注释,其他开发者难以快速理解代码


一般针对过长方法的解决路径如下:

  • 简化复杂表达式,使用辅助方法或者函数
  • 补充必要的文档或者注释,清晰说明代码的目的或者逻辑


我们来看看具体的代码示例

// 坏味道代码
int result = (a * 3) + (b / 2) - 5;
// 重构后的代码
private static final int MULTIPLIER_A = 3;
private static final int DIVIDER_B = 2;
private static final int SUBTRACTION_CONSTANT = 5;
int result = (a * MULTIPLIER_A) + (b / DIVIDER_B) - SUBTRACTION_CONSTANT;


数据泥团


数据泥团的坏味道一般表现为多个数据项经常一起使用,但是没有封装在一起,导致数据管理混乱。


具体的表现为:

  • 多个变量经常一起出现,但是未作为一个整体处理
  • 数据项之间的关联关系未在代码中体现


一般针对过长方法的解决路径如下:

  • 创建一个新的类或者数据结构来封装这些数据项
  • 使用对象或者集合来管理这些数据项的关系


我们来看看具体的代码示例

// 坏味道代码
int customerId;
String customerName;
Date customerSince;
// 重构后的代码
class Customer {
    private int id;
    private String name;
    private Date since;
    // 构造函数、getter和setter...
}


过度耦合


过度耦合的坏味道一般表现为类之间或者模块之间的依赖关系过于紧密,一个变更可能会影响多个部分。


具体的表现为:

  • 一个类的改变需要修改多个其他类
  • 类或者模块之间的接口过于复杂


一般针对过长方法的解决路径如下:

  • 减少类之间的直接依赖,使用接口或者抽象类来解耦
  • 采用设计模式,比如观察者模式、策略模式等,来降低耦合度


我们来看看具体的代码示例

// 坏味道代码
class ReportGenerator {
    private Printer printer;
    private Database database;
    public void generateReport() {
        // ...
        printer.print(report);
        // ...
        List<Data> data = database.getData();
        // ...
    }
}
// 重构后的代码
class ReportGenerator {
    private ReportPrinter reportPrinter;
    private ReportDataAccessor dataAccessor;
    public void generateReport() {
        List<Data> data = dataAccessor.getData();
        Report report = createReport(data);
        reportPrinter.printReport(report);
    }
}
interface ReportPrinter {
    void printReport(Report report);
}
interface ReportDataAccessor {
    List<Data> getData();
}


过复杂条件


过复杂条件的坏味道一般表现为条件语句过于复杂,难以理解和维护。


具体的表现为:

  • 多层嵌套的 if/else 语句
  • 复杂的逻辑表达式,难以一眼看出其逻辑


一般针对过长方法的解决路径如下:

  • 使用多态、策略模式或者状态模式来简化条件判断
  • 将复杂条件分解为多个简单的条件,使用卫语句


我们来看看具体的代码示例

// 坏味道代码
if (user.isAdmin() && date.isWeekend() && item.isInStock()) {
    // ...
}
// 重构后的代码
if (!user.canMakePurchase()) {
    return;
}
if (!item.isAvailable()) {
    return;
}
if (!isPurchaseTimeValid(date)) {
    return;
}
// ...


发散式变化


发散式变化的坏味道一般表现为修改一处代码需要在多个地方进行更新,导致维护困难。


具体的表现为:

  • 应用一处变更时,需要修改多个文件或者类
  • 类或者模块的变更频繁,且互相影响


一般针对过长方法的解决路径如下:

  • 重构代码,减少类或者模块之间的耦合
  • 引入新的抽象层或者使用组合代替继承


我们来看看具体的代码示例

// 坏味道代码
class Product {
    private String name;
    private double price;
    public void updatePrice(double newPrice) {
        this.price = newPrice;
        notifyPriceChange();
    }
    private void notifyPriceChange() {
        // Notify multiple systems...
    }
}
// 重构后的代码
class Product {
    private String name;
    private double price;
    private List<PriceChangeListener> listeners;
    public void updatePrice(double newPrice) {
        this.price = newPrice;
        notifyListeners();
    }
    private void notifyListeners() {
        for (PriceChangeListener listener : listeners) {
            listener.priceChanged(this);
        }
    }
    public void addPriceChangeListener(PriceChangeListener listener) {
        listeners.add(listener);
    }
}
interface PriceChangeListener {
    void priceChanged(Product product);
}


特征羡慕


特征羡慕的坏味道一般表现为一个类频繁使用另一个类的方法或者属性,显示出对这个类的过度依赖。


具体的表现为:

  • 一个类的方法主要操作另一个类的属性
  • 一个类包含多个与另一个类紧密相关的功能


一般针对过长方法的解决路径如下:

  • 重新组织类的结构,将羡慕的类或者属性移动到依赖它的类中
  • 建立新的类来封装羡慕的特征


我们来看看具体的代码示例

// 坏味道代码
class OrderService {
    private Order order;
    private Customer customer;
    public void processOrder() {
        // ...
        order.setShippingAddress(customer.getShippingAddress());
        // ...
    }
}
// 重构后的代码
class CustomerService {
    private Customer customer;
    public void updateShippingAddress(String address) {
        customer.setShippingAddress(address);
    }
}
class Order {
    private Customer customer;
    // ...
}


我本次列举出比较常见的代码坏味道,除了这些还有其他的代码坏味道,欢迎留言交流,也欢迎大家继续总结关于代码坏味道的内容。


通用识别和解决这些常见的代码坏味道,是可以显著提高代码的质量和可维护性的。重构不仅仅是代码改进的过程,也是开发者技能提升的过程。通过持续的实践和学习,我们可以更好地写出清晰、健壮和易于维护的代码。

相关文章
|
2月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
390 5
|
3月前
|
IDE Java 关系型数据库
Java 初学者学习路线(含代码示例)
本教程为Java初学者设计,涵盖基础语法、面向对象、集合、异常处理、文件操作、多线程、JDBC、Servlet及MyBatis等内容,每阶段配核心代码示例,强调动手实践,助你循序渐进掌握Java编程。
500 3
|
3月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
401 3
|
3月前
|
Java API 开发工具
【Azure Developer】Java代码实现获取Azure 资源的指标数据却报错 "invalid time interval input"
在使用 Java 调用虚拟机 API 获取指标数据时,因本地时区设置非 UTC,导致时间格式解析错误。解决方法是在代码中手动指定时区为 UTC,使用 `ZoneOffset.ofHours(0)` 并结合 `withOffsetSameInstant` 方法进行时区转换,从而避免因时区差异引发的时间格式问题。
237 3
|
2月前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
271 115
|
2月前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
192 98
|
4月前
|
人工智能 监控 安全
智慧工地解决方案,java智慧工地程序代码
智慧工地系统融合物联网、AI、大数据等技术,实现对施工现场“人、机、料、法、环”的全面智能监控与管理,提升安全、效率与决策水平。
152 2
|
3月前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
427 0
|
2月前
|
安全 Java 容器
告别繁琐判空:Optional让你的Java代码更优雅
告别繁琐判空:Optional让你的Java代码更优雅