代码工艺:实践《修改代码的艺术》中如何安全地在现有代码库中修改代码的方法

《修改代码的艺术》一书中,对如何安全地在现有代码库中修改代码提出了以下步骤:1.定义变更点;2.寻找测试点;3.打破依赖关系;4.编写测试;5.进行修改和重构。

场景描述

已有一段代码逻辑更新用户信息,但它的代码存在以下问题:

  1. 缺乏单元测试,无法验证修改是否正确。
  2. 存在硬编码和强耦合,导致难以扩展和测试。
  3. 方法过于复杂,多个逻辑混在一起,影响可读性。

原始代码(待修改)

以下是现有的代码逻辑:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public void updateUser(User user) {
        // 更新用户信息的复杂逻辑
        if (user.getId() == null) {
            throw new IllegalArgumentException("User ID cannot be null");
        }
        user.setUpdatedAt(new Date());
        userMapper.updateUser(user);
        // 发送更新通知
        sendUpdateNotification(user);
    }

    private void sendUpdateNotification(User user) {
        System.out.println("Sending update notification for user: " + user.getId());
    }
}

第一步:定义变更点

目标是将用户信息更新逻辑优化为更易测试和扩展的实现,同时保留现有行为。

变更点:

  1. updateUser 方法逻辑过于复杂,需要拆分。
  2. sendUpdateNotification 方法是不可控的逻辑,需要重构以支持测试。

第二步:寻找测试点

测试点updateUser 方法。它的入口是服务层,我们需要编写测试以验证方法的行为。

第三步:打破依赖关系

  1. 拆分复杂方法:将 updateUser 拆分为多个小方法。
  2. 引入接口:将通知逻辑抽象为接口,方便测试。
  3. 使用依赖注入:通过 Spring 的 DI 注入依赖对象。

第四步:编写测试

为确保安全修改,先编写测试以捕获现有行为。

测试前的重构代码:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private NotificationService notificationService;

    public void updateUser(User user) {
        validateUser(user);
        updateUserDetails(user);
        notificationService.sendUpdateNotification(user);
    }

    private void validateUser(User user) {
        if (user.getId() == null) {
            throw new IllegalArgumentException("User ID cannot be null");
        }
    }

    private void updateUserDetails(User user) {
        user.setUpdatedAt(new Date());
        userMapper.updateUser(user);
    }
}

public interface NotificationService {
    void sendUpdateNotification(User user);
}

@Service
public class NotificationServiceImpl implements NotificationService {
    @Override
    public void sendUpdateNotification(User user) {
        System.out.println("Sending update notification for user: " + user.getId());
    }
}

编写单元测试:

public class UserServiceTest {
    @InjectMocks
    private UserService userService; // 被测试的类

    @Mock
    private UserMapper userMapper; // Mock 的依赖

    @Mock
    private NotificationService notificationService; // Mock 的依赖

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this); // 初始化 Mock 对象
    }

    @Test
    public void testUpdateUser() {
        // 准备测试数据
        User user = new User();
        user.setId(1L);
        user.setName("Test User");

        // 执行测试
        userService.updateUser(user);

        // 验证调用行为
        verify(userMapper, times(1)).updateUser(any(User.class));
        verify(notificationService, times(1)).sendUpdateNotification(any(User.class));
    }
}

第五步:修改和重构

  1. 添加新的通知功能,例如发送邮件。
  2. 优化数据库访问逻辑。

重构后的代码:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private NotificationService notificationService;

    public void updateUser(User user) {
        validateUser(user);
        updateUserDetails(user);
        notificationService.sendUpdateNotification(user);
    }

    private void validateUser(User user) {
        if (user.getId() == null) {
            throw new IllegalArgumentException("User ID cannot be null");
        }
    }

    private void updateUserDetails(User user) {
        user.setUpdatedAt(new Date());
        userMapper.updateUser(user);
    }
}

@Service
public class EmailNotificationService implements NotificationService {
    @Override
    public void sendUpdateNotification(User user) {
        System.out.println("Sending email notification for user: " + user.getId());
    }
}

总结

通过以上实践,我们完成了:

  1. 定义变更点:明确需要优化的 updateUser 方法。
  2. 寻找测试点:定位服务层逻辑并引入测试。
  3. 打破依赖关系:拆分方法,抽象接口,并引入依赖注入。
  4. 编写测试:为原有功能的行为添加单元测试。
  5. 修改和重构:重构代码以提升可读性和扩展性。

这种方法确保了代码的安全修改,同时保留了现有行为。

你可能感兴趣的:(代码工艺,代码规范)