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


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


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

相关文章
|
12天前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
38 0
|
5天前
|
存储 Java 开发者
【Java新纪元启航】JDK 22:解锁未命名变量与模式,让代码更简洁,思维更自由!
【9月更文挑战第7天】JDK 22带来的未命名变量与模式匹配的结合,是Java编程语言发展历程中的一个重要里程碑。它不仅简化了代码,提高了开发效率,更重要的是,它激发了我们对Java编程的新思考,让我们有机会以更加自由、更加创造性的方式解决问题。随着Java生态系统的不断演进,我们有理由相信,未来的Java将更加灵活、更加强大,为开发者们提供更加广阔的舞台。让我们携手并进,共同迎接Java新纪元的到来!
29 11
|
2天前
|
并行计算 Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
Lambda表达式在Java 8中引入,旨在简化集合操作和并行计算。本文将通过浅显易懂的语言,带你了解Lambda表达式的基本概念、语法结构,并通过实例展示如何在Java项目中应用Lambda表达式来优化代码,提高开发效率。我们将一起探讨这一现代编程工具如何改变我们的Java编码方式,并思考它对程序设计哲学的影响。
|
2天前
|
安全 Java 测试技术
掌握Java的并发编程:解锁高效代码的秘密
在Java的世界里,并发编程就像是一场精妙的舞蹈,需要精准的步伐和和谐的节奏。本文将带你走进Java并发的世界,从基础概念到高级技巧,一步步揭示如何编写高效、稳定的并发代码。让我们一起探索线程池的奥秘、同步机制的智慧,以及避免常见陷阱的策略。
|
12天前
|
Java Devops 持续交付
探索Java中的Lambda表达式:简化代码,提升效率DevOps实践:持续集成与部署的自动化之路
【8月更文挑战第30天】本文深入探讨了Java 8中引入的Lambda表达式如何改变了我们编写和管理代码的方式。通过简化代码结构,提高开发效率,Lambda表达式已成为现代Java开发不可或缺的一部分。文章将通过实际例子展示Lambda表达式的强大功能和优雅用法。
|
12天前
|
Java
用JAVA架建List集合为树形结构的代码方法
这段代码定义了一个表示树形结构的 `Node` 类和一个用于构建树形结构的 `TreeController`。`Node` 类包含基本属性如 `id`、`pid`、`name` 和 `type`,以及子节点列表 `children`。`TreeController` 包含初始化节点列表并将其转换为树形结构的方法。通过过滤和分组操作实现树形结构的构建。详情可见:[代码示例链接1](http://www.zidongmutanji.com/zsjx/43551.html),[代码效果参考链接2](https://www.257342.com/sitemap/post.html)。
25 5
|
10天前
|
Java API 开发者
代码小妙招:用Java轻松获取List交集数据
在Java中获取两个 `List`的交集可以通过 `retainAll`方法和Java 8引入的流操作来实现。使用 `retainAll`方法更为直接,但会修改原始 `List`的内容。而使用流则提供了不修改原始 `List`、更为灵活的处理方式。开发者可以根据具体的需求和场景,选择最适合的方法来实现。了解和掌握这些方法,能够帮助开发者在实际开发中更高效地处理集合相关的问题。
10 1
|
12天前
|
Java
java代码和详细的代码应用
代码块分为局部、构造、静态和同步代码块。局部代码块控制变量生命周期,例如 `int a` 只在特定代码块内有效。构造代码块用于创建对象时执行附加功能,避免构造方法中代码重复。静态代码块随类加载执行一次,常用于初始化操作。同步代码块确保多线程环境下方法执行的原子性,通过 `synchronized` 关键字实现。
22 3
|
13天前
|
设计模式 算法 Java
【揭秘】如何巧妙运用Java模板方法模式,让你的代码优雅升级?
【8月更文挑战第30天】模板方法模式是一种行为型设计模式,它定义了算法的骨架并将某些步骤延迟到子类中,使子类能在不改变算法结构的情况下重定义特定步骤。此模式适用于具有共同结构但细节不同的场景,如角色升级系统。通过定义一个抽象类 `Character` 包含模板方法 `levelUp` 和抽象步骤方法,子类如 `Warrior` 和 `Mage` 可以实现具体逻辑。这种方式提供了良好的扩展性,确保算法结构不变,同时保持系统的稳定性和一致性,在数据处理和业务流程管理中广泛应用。
35 2
|
12天前
|
Java
Java中的Lambda表达式:简化代码,提升效率
【8月更文挑战第31天】Lambda表达式在Java 8中引入,旨在使代码更加简洁和易读。本文将探讨Lambda表达式的基本概念、使用场景及如何通过Lambda表达式优化Java代码。我们将通过实际示例来展示Lambda表达式的用法和优势,帮助读者更好地理解和应用这一特性。