代码坏味道有24种?我看未必!

微信公众号:牛奶 Yoka 的小屋
有任何问题。欢迎来撩~
最近更新:2024/08/03

[大家好,我是牛奶。]

我在上一篇文章打开IDEA,程序员思考的永远只有两件事!中,通过代码命名、重复代码、合格方法三个章节,着重讲解了24种代码坏味道最常见的五种:注释、命名、重复代码、过长函数、过长的参数列表。在这篇文章中,我将通过对象数据、对象关系、代码表达力及重复的坏味道四个章节,再详细讲一讲剩下的19种坏味道。

《重构》的大佬Martin Fowler给出24种代码坏味道,但我看未必。我认为有些坏味道犯了“重复坏味道”的问题,所以我把它们汇总到最后一章重复的坏味道中,大家看完其他坏味道再看这些,便可很快理解;有些坏味道只是代码啰嗦了点,并不会对业务逻辑产生什么实质影响,我将他们放在了代码表达力的章节;还有些坏味道根因类似,比如基本类型偏执和重复的switch,所以我把它们放到一起,方便理解。本篇文章的阅读导图如下:

561edeaf131729c1cf93cf18bd9c7966.png

代码坏味道

总之,当你看完所有的坏味道,会发现大佬翻来覆去,讲的都是那几类问题。愿此篇文章,能使诸君对坏味道代码的敏感度有所提升!

对象数据定义时有哪些坑?

普通数据可变问题

可变数据是Martin Fowler大佬在《重构》第二版新增的坏味道。这个坏味道很是反直觉,我琢磨了半天,才咂巴出点味道。突然想起句名人名言:

我横竖睡不着,仔细看了半夜,才从字缝里看出字来,整段都写着几个字是“别让数据可变”!——坡迅

什么是可变数据?

任何在赋值之后仍可以修改的变量称为可变数据。例如拥有Getter方法和Setter方法的类变量(Getter方法的返参可变导致变量可变);再或者拥有类似Setter设置变更值方法的变量。(咱就说这两类变量常不常用!)

为啥可变数据很危险?

一句话概括,你不知道数据会在哪里被何人以什么方式修改。下面给一个银行存钱的简单案例,上代码:

import java.math.BigDecimal;

public class BankAccount {
private BigDecimal balance;

public BankAccount(BigDecimal initialBalance) {
this.balance = initialBalance;
  }

// 存款方法
public void deposit(BigDecimal amount) {
balance =  balance.add(amount);
  }

// 取款方法
public void withdraw(BigDecimal amount) {
if (amount.compareTo(balance) < 1) {
balance = balance.subtract(amount);
      } else {
          System.out.println("Insufficient funds");
      }
  }

// 获取余额方法
public BigDecimal getBalance() {
return balance;
  }
}

BankService类:

public class BankService {
 private BankAccount account;

 public BankService(BankAccount account) {
 this.account = account;
    }

 // 服务方法,意外地修改了账户余额
 public void performService() {
 // 假设这里执行了一些操作,需要修改账户余额
 account.deposit(BigDecimal.valueOf(100)); // 意外地给账户增加了100元
 }
}

主函数:

public class Main {
 public static void main(String[] args) {
        BankAccount account = new BankAccount(BigDecimal.valueOf(1000)); // 初始余额1000元
 BankService service = new BankService(account);

 // 显示初始余额
 System.out.println("Initial balance: " + account.getBalance());

 // 执行服务,意外修改了账户余额
 service.performService();

 // 显示修改后的余额,意外地增加了100元
 System.out.println("Balance after service: " + account.getBalance());
    }
}

代码中的银行余额balance为可变变量,该变量会被传递到银行服务类BankService中时,该类可能是另一位同事负责开发,他在执行服务的方法中意外修改了银行余额,最终导致BankAccount类中的可变变量发生了非预期的变化。

数据可变带来的不可控和难定位等风险远比我们想象严重,甚至出现了完全建立在“数据永不改变”概念基础上的软件开发流派——函数式编程。在该编程范式中,如果要更新一个数据结构,就返回一份新的数据副本,旧的数据仍保持不变。

我仔细想了想,发现写过的大部分类中的可变变量,基本都是一次赋值,需要再次赋值都会new出一个新类。所以,针对可变数据,Martin Fowler给出的优化建议是:

  • 将可变数据设置为私有且不可变状态(private final)

  • 将参数赋值优化为使用有参构造函数

  • 必须二次赋值时使用新的数据副本

作者倡导类的创建和类变量的赋值要同时进行,创建类的时候就要初始化变量,自此不可更改,非要更改就重新创建一个新类。

上述银行余额问题,满足优化建议第二点,但不满足第一三点。而银行余额是必须要存取,因此,BankAccount类代码优化后为:

public class BankAccount {
  private final BigDecimal balance;

  public BankAccount(BigDecimal initialBalance) {
      this.balance = initialBalance;
  }

  public BankAccount deposit(BigDecimal amount) {
      if (amount.compareTo(BigDecimal.ZERO) <= 0) {
          throw new IllegalArgumentException("Deposit amount must be positive.");
      }
      return new BankAccount(this.balance.add(amount));
  }

  public BankAccount withdraw(BigDecimal amount) {
      if (amount.compareTo(BigDecimal.ZERO) <= 0 || amount.compareTo(this.balance) > 0) {
          throw new IllegalArgumentException("Invalid withdrawal amount.");
      }
      return new BankAccount(this.balance.subtract(amount));
  }

  public BigDecimal getBalance() {
      return balance;
  }
}

优化后,余额balance成为不可变变量,依然作为构造函数参数不变。同时存取方法返回的是新的实例对象,这样即使在BankService类的performService方法中误操作,最后在主函数中打印account.getBalance()银行余额,依然是1000元,不会平白无故新增100元。大家可以在编辑器中进行简单测试。

新的问题随之而来,如果有个类中有1000个变量,难道往构造函数中传1000个参数么?答案是,不用,用构建器替代构造函数(构建器可直接使用Build注解)。

上代码:

@Data
public class BankAccount {
    public BigDecimal balance;
    public String amount;
}

应优化为

@Builder
public class BankAccount {
  private final BigDecimal balance;
  private final String amount;
}

构建器使用方式:

 BankAccount bankAccount = BankAccount.builder().balance(0.00).amount("1000").build();

使用构建器后,就可以选择性的赋值参数,添加参数也不影响旧代码。提高了代码的扩展性和可维护性。

如果可变数据赋值固定,赋值总是那几个值,比如订单状态等。开源项目Moco作者郑晔大佬还提出一个优化建议:将可变数据的赋值操作封装为一个不带参的类方法,取代Setter方法。比如这种:

原代码:

public void approve(final long bookId) {
...
book.setReviewStatus(ReviewStatus.APPROVED);
...
}

优化后可为:

public void approve(final long bookId) {
...
book.approve();
...
}
class Book {
public void approve() {
  this.reviewStatus
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值