如何重构和优化代码
1. 理解现有代码
在开始重构前,你需要完全理解现有代码的功能。阅读代码,运行并测试现有应用以确保你了解它的行为。
2. 小步修改
重构应该一小步一小步来进行。每做一个小的修改,就运行测试以确保这个改动没有破坏任何东西。这样可以避免引入新的bug
3. 消除重复代码
查找并消除重复的代码。这可以通过提取方法(Extract Method)、提取类(Extract Class)、或使用设计模式如模板方法或策略模式来实现
4. 提高模块化和解耦
评估代码的模块化程度和组件间的耦合性。使用适当的设计模式来改进设计,比如工厂模式、观察者模式或依赖注入等,以减少组件之间的直接依赖。
5. 优化数据结构和算法
在不影响代码可读性的前提下,优化使用的数据结构和算法。有时候,选择更合适的数据结构可以显著提升性能。
6. 提升代码的可读性
通过改善变量和方法的命名、简化复杂的条件语句、减少方法的长度等方式来提升代码的可读性。良好的代码格式和一致的风格也很重要。
7. 定期进行代码审查
通过代码审查可以发现并修复问题,同时也是交流最佳实践和提高团队技能的好机会。
重构是一个持续的过程,应该成为开发周期的一部分。定期审视并改进代码库是保持软件健康的关键。
8. 合理适用AI工具对代码进行优化
9. 即时清理不用的代码
10. 业务层ServiceImpl职责尽可能单一
每个Service应该只关注一组相关的业务功能。不要去实现获取处理其他Service的逻辑
11. 平时写代码,关注自己的代码风格
平时写代码,应该多关注自己的代码风格,可读性,易维护,模块化,精简,解耦,可借助AI工具进行优化
实战(示例)
1. ServiceImpl 代码应该尽可能简洁,实体类可以承担更多的职责
@Override
public FinSettlementDto selectSettlementMonth(FinSettlementMonthQueryVO queryVO) {
·····
List<FinOrderItemEntity> itemList = orderEntity.getItemList();
HashSet<Integer> settlementRuleIdSet = new HashSet<>();
if (CollectionUtils.isNotEmpty(itemList)) {
settlementRuleIdSet.addAll(itemList.stream().map(FinOrderItemEntity::getSettlementRuleId)
.collect(Collectors.toList()));
}
······
}
将提取 settlementRuleIdSet
的逻辑封装到 orderEntity
对象的一个方法中是一个很好的做法。这样的封装不仅使得 ServiceImpl
代码更加简洁,还遵循了面向对象编程的封装原则,将数据和操作数据的行为组合在一起。这样的方法能增强代码的可读性和可维护性,并可以在其他需要此逻辑的地方重用这个方法,避免代码重复
优化后
FinOrderVO 对象
public HashSet<Integer> fetchSettlementRuleIdSet() {
List<FinOrderItemEntity> itemList = this.getItemList();
HashSet<Integer> settlementRuleIdSet = new HashSet<>();
if (CollectionUtils.isNotEmpty(itemList)) {
settlementRuleIdSet.addAll(itemList.stream().map(FinOrderItemEntity::getSettlementRuleId)
.collect(Collectors.toList()));
}
return settlementRuleIdSet;
}
原来的就变成一行了
@Override
public FinSettlementDto selectSettlementMonth(FinSettlementMonthQueryVO queryVO) {
·····
Set<Integer> settlementRuleIdSet = orderEntity.fetchSettlementRuleIdSet();
······
}
方法命名 如果用get,请注意使用@JsonIgnore,避免对象在序列化时方法被调用,特别是使用了像 Jackson 或 Gson 这样的库进行 JSON 序列化,那么以 get
前缀命名的方法可能会被自动调用作为序列化过程的一部分
2. 当ServiceImpl如果有空的方法实现时,Servcie应该需要有默认实现,需要时再进行重写
public interface InvoiceOrderService {
FinInvoiceOrder checkCanInvoiceAndSetRedundancy(SupportUserInfo userInfo, FinInvoiceEntity invoice);
void saveInvoice(FinInvoiceOrder order, AuditStatusEnum auditStatus);
void invoiceToPaid(Integer orderId);
void updateInvoiceAuditStatus(Map<Integer, FinInvoiceEntity> orderIdAndInvoiceMap, boolean pass);
void openInvoice(Integer orderId);
}
public class FinContractServiceImpl extends ServiceImpl<FinContractMapper, FinContractEntity>
implements FinContractService, FinRefundOriginTypeEnum.RefundService, FinInvoiceOrderTypeEnum.InvoiceOrderService {
@Override public FinInvoiceOrderTypeEnum.FinInvoiceOrder checkCanInvoiceAndSetRedundancy(SupportUserInfo userInfo, FinInvoiceEntity invoice) {
FinContractEntity finContractEntity = getById(invoice.getContractId());
ArgUtil.notNull(finContractEntity, "选择的账单不存在或已被删除");
// 设置发票冗余字段 invoice.setBusinessType(finContractEntity.getBusinessType());
invoice.setOrderHandleUser(finContractEntity.getCreateUser());
invoice.setOrderReceivable(finContractEntity.getReceivable());
invoice.setOrderPaid(finContractEntity.getPaid());
invoice.setChildId(finContractEntity.getChildId());
return finContractEntity;
}
@Override public void saveInvoice(FinInvoiceOrderTypeEnum.FinInvoiceOrder order, AuditStatusEnum auditStatus) {
}
@Override public void invoiceToPaid(Integer orderId) {
}
@Override public void updateInvoiceAuditStatus(Map<Integer, FinInvoiceEntity> orderIdAndInvoiceMap, boolean pass) {
}
@Override public void openInvoice(Integer orderId) {
}
}
可以看到FinContractServiceImpl中有大量的空方法;Service当不需要特定的实现,或者如果提供一个基本的默认行为能够帮助避免在每个实现类中重复编写相同的代码
优化后
FinContractServiceImpl的一大堆代码就可以去掉了
何时应该提供默认实现:
- 通用逻辑: 如果方法有通用的行为,那么在接口中提供一个默认实现可以减少重复代码,简化接口的实现类。
- 可选方法: 如果方法对于某些实现不是必需的,提供一个默认实现可以使这些方法变成可选实现,而不是强制实现。
- 向后兼容性: 在已经广泛使用的接口中添加新方法时,提供默认实现可以避免破坏现有的实现类。
何时不应提供默认实现:
- 核心业务逻辑: 如果方法涉及核心业务逻辑,那么强制实现类提供具体实现可能更安全,以确保每个实现都考虑到特定的业务规则。
- 依赖具体环境: 如果方法的实现依赖于特定的环境或配置,那么在接口中提供默认实现可能不合适。