如何识别和解决 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;
    // ...
}


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


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

相关文章
|
3天前
|
Java 索引
Java实现扑克牌游戏 | 随机发牌 ( 过程拆分详解+完整代码 )
Java实现扑克牌游戏 | 随机发牌 ( 过程拆分详解+完整代码 )
|
5天前
|
Java Spring
JAVA注解:传统与现代的完美结合,你的代码值得拥有!
【6月更文挑战第29天】Java注解,作为连接传统与现代的编程工具,简化企业级应用开发,提升代码可读性和维护性。通过自定义注解如`@Loggable`,可以将行为(如日志记录)与方法实现分离,减少模板代码。使用AOP(如Spring)处理注解,实现行为拦截,增强代码灵活性和可扩展性。拥抱Java注解,让代码更现代、更高效!
28 16
|
5天前
|
IDE Java 编译器
JAVA注解,你的代码需要的一次“心灵按摩”!
【6月更文挑战第29天】Java注解是提升代码可维护性的关键,它们是编译器和IDE理解代码意图的特殊标记,不同于仅作解释的注释。注解可用于编译时检查(如@Override、@NotNull)、自动生成代码(Lombok的@Getter、@Setter)、框架集成(Spring的@Autowired、MyBatis的@Mapper)。通过注解,代码变得更简洁、功能更强大,为项目带来效率提升。尝试使用注解,赋予代码新生命!
21 12
|
5天前
|
存储 缓存 监控
如何优化你的Java代码性能
如何优化你的Java代码性能
|
5天前
|
Java 编译器 开发者
JAVA注解,让代码“开口说话”的魔法术!
【6月更文挑战第29天】Java注解,一种元数据机制,让代码“开口”传达意图。它们增强可读性,提供编译器与框架处理代码的额外信息。例如,@Description注解描述方法功能,@Autowired在Spring中自动装配Bean,自定义注解如@MyCustomAnnotation允许定义独特行为。注解提升开发效率,是理解与使用Java的关键。
|
5天前
|
Java 编译器 数据库连接
JAVA注解:代码界的“隐形翅膀”?!
【6月更文挑战第29天】Java注解,编程的“隐形翅膀”,提供编译检查、框架集成及自定义元数据功能。如@Override确保方法重写正确,@Autowired在Spring中自动装配Bean。通过自定义注解,开发者能创造独特代码逻辑。例如,定义@SpecialProcessing注解标记需特殊处理的方法,增强代码可读性和可扩展性。利用注解,让代码飞翔在更广阔的世界。
12 1
|
1天前
|
设计模式 IDE Java
提高Java代码的可读性的技巧
提高Java代码的可读性的技巧
|
1天前
|
Java 测试技术 开发者
Java中设计可测试的代码的最佳实践
Java中设计可测试的代码的最佳实践
|
2天前
|
人工智能 搜索推荐 Java
Java中的智能语音识别与处理技术
Java中的智能语音识别与处理技术
|
2天前
|
人工智能 Java API
Java中的智能语音识别与处理技术实践
Java中的智能语音识别与处理技术实践