译者 | 刘汪洋
审校 | 重楼
对于软件开发者而言,编写可重用的代码是一项基本而重要的技能。每位工程师都应掌握如何尽可能地提高代码的复用性。当前,一些开发人员可能会认为微服务的本质是小而高效,因此他们无需编写高质量代码。然而,即便是微服务,在变得庞大时,阅读和理解代码的时间成本也会迅速增加至编写时的十倍。
代码一开始编写得不佳,将会大幅增加修复 bug 或添加新功能的工作量。在一些极端情况下,我见证过团队因代码质量问题而放弃原有代码,重新编写。这不仅浪费了宝贵时间,还可能导致开发人员承担责任并失去工作。
本文将介绍经过实践验证的提高JAVA 中代码可重用性的八条指导原则。
编写可重用代码的首要步骤是与团队一同确立代码规范。如果对编码规范不能达成共识,代码很快就会变得混乱不堪。如果团队成员之间意见不统一,关于代码实现的无效讨论也会频繁发生。同时,你需要确立一个基础的代码设计框架,以解决软件需要解决的问题。
在制定了标准和代码设计框架之后,接下来应当明确代码指导原则。
常见的指导原则包括:
一旦团队就这些规范达成共识,每个成员都应对代码审查负责,以确保编写出高质量、可重用的代码。如果没有共识,写出高质量且可重用的代码几乎不可能。
当创建服务并以 API 形式公开时,应该详细记录 API 信息,以便新加入的开发人员能够轻松理解和使用。
API 在微服务架构中扮演着重要角色。因此,对你的项目不太熟悉的其他团队成员必须能够通过阅读 API 文档来理解其功能。如果 API 文档记录不当,代码的重复编写风险会增加。新开发人员可能会无意中创建一个和现有功能重复的方法。
因此,精确记录 API 至关重要。但在代码中过度使用文档可能并无益处。应该只记录 API 中的关键信息,如业务操作的解释、参数、返回值对象等。
简洁且具有描述性的代码命名总是优于晦涩难懂的缩写。浏览不熟悉的代码库时,我发现缩写往往难以让人立即理解其含义。
因此,相较于使用像Ctr这样的缩写,直接命名为Customer更为明晰和有意义。Ctr可能代表了合同(contract)、控制(control)、客户(customer)等多种含义,使人难以确定其准确意图。
此外,要遵循你所使用编程语言的命名规范。以 Java 为例,它有 JavaBeans 命名规范,这对每个 Java 开发者来说都是基本常识。以下是 Java 中类、方法、变量和包的命名方式:
内聚的代码应该专注于_做好一件事_。虽然这是一个简单的概念,但即便是经验丰富的开发人员也常常忽视它。这样,他们就会创建出所谓的_超级复杂类_,即一个承担了过多职责的类,有时也被称为_全能类_。
要实现高内聚的代码,关键是学会拆分代码,确保每个类和方法专注于单一职责。比如,如果你创建了一个名为saveCustomer的方法,它应当只负责一个动作:保存客户信息。它不应该同时负责更新和删除客户信息。
同理,如果有一个名为CustomerService的类,它应该仅包含与客户相关的功能。如果CustomerService类中有执行产品相关操作的方法,应移至ProductService类中。
与其在CustomerService类中添加执行产品操作的方法,不如在该类中引用ProductService,并调用我们所需的任何方法。
为了更清晰地介绍这个概念,我们来分析一个低内聚的类示例:
public class CustomerPurchaseService {
public void saveCustomerPurchase(CustomerPurchase customerPurchase) {
// 执行与客户相关的操作
registerProduct(customerPurchase.getProduct());
// 更新客户信息
// 删除客户信息
}
private void registerProduct(Product product) {
// 在客户领域中执行对产品的逻辑操作…
}
}
这个类存在以下问题:
识别出这些问题后,我们可以重新编写这段代码,使其变得更加内聚。我们将registerProduct方法移动到更合适的位置,即ProductService类中。这样做使代码更易于搜索和重用,同时避免将此方法局限在CustomerPurchaseService中:
public class CustomerPurchaseService {
private ProductService productService;
public CustomerPurchaseService(ProductService productService) {
this.productService = productService;
}
public void saveCustomerPurchase(CustomerPurchase customerPurchase) {
// 仅执行与客户购买相关的操作
productService.registerProduct(customerPurchase.getProduct());
}
}
public class ProductService {
public void registerProduct(Product product) {
// 在产品领域中执行相关逻辑…
}
}
在这个改进后的版本中,saveCustomerPurchase仅执行其主要职责:保存客户购买信息。同时,registerProduct方法的职责被正确地委托给了ProductService类,从而使两个类都更加专注和内聚。现在,这些类及其方法都专注于执行预期的特定任务。
_高度耦合的代码_指的是那些具有过多依赖关系的代码,这种情况会导致代码难以维护。类中定义的依赖(其他类)越多,其耦合程度就越高。
微服务架构的目标之一就是将服务解耦,如果一个微服务与其他服务都有连接,那么它就会高度耦合。
想要更好地实现代码复用,就需要尽可能使系统和代码各自独立。虽然服务和代码之间的通信不可避免会产生一定程度的耦合,但关键在于让这些服务尽可能保持独立性。
下面是一个高度耦合类的例子:
public class CustomerOrderService {
private ProductService productService;
private OrderService orderService;
private CustomerPaymentRepository customerPaymentRepository;
private CustomerDiscountRepository customerDiscountRepository;
private CustomerContractRepository customerContractRepository;
private CustomerOrderRepository customerOrderRepository;
private CustomerGiftCardRepository customerGiftCardRepository;
// 其他方法…
}
注意CustomerService类与许多其他服务类的耦合程度很高。这么多的依赖意味着该类将包含大量代码,这不利于代码的测试和维护。
更有效的方法是拆分这个类,创造多个依赖更少的服务。我们可以通过将CustomerService类分解为独立的服务来降低其耦合度:
public class CustomerOrderService {
private OrderService orderService;
private CustomerPaymentService customerPaymentService;
private CustomerDiscountService customerDiscountService;
// 省略其他方法…
}
public class CustomerPaymentService {
private ProductService productService;
private CustomerPaymentRepository customerPaymentRepository;
private CustomerContractRepository customerContractRepository;
// 省略其他方法…
}
public class CustomerDiscountService {
private CustomerDiscountRepository customerDiscountRepository;
private CustomerGiftCardRepository customerGiftCardRepository;
// 省略其他方法…
}
经过这样的重构,CustomerService及其他类变得更易于进行单元测试,同时也更便于维护。类的职责越单一且清晰,就越容易实现新功能。如果出现 bug,也更容易进行修复。
SOLID 代表面向对象编程(OOP)中五个关键的设计原则,它们的目标是使软件系统更加可维护、灵活,并易于理解。
下面是这些原则的简要说明:
遵循这些 SOLID 原则有助于开发者编写更模块化、可维护且易于扩展的代码。这些原则有助于实现更易于理解、测试和修改的代码,从而形成更健壮、适应性更强的软件系统。
设计模式是经验丰富的开发者在处理多种编码场景后总结出的最佳实践。恰当地使用设计模式可以显著提升代码的复用性。
掌握设计模式还能增强你阅读和理解代码的能力——这包括 JDK 中的代码。当你能识别出其背后的设计模式时,代码会变得更加清晰。
尽管设计模式有其用处,但并非每种模式适用于所有情况,因此使用时需谨慎。仅仅因为我们了解某个模式,并不意味着就应该随意应用。在不恰当的场景中使用设计模式可能会使代码变得更复杂、更难以维护。然而,在合适的场合应用设计模式,可以使代码更加灵活和易于扩展。
以下是面向对象编程中常见的设计模式简要概述:
并不需要记住每一种设计模式,重要的是意识到这些模式的存在,并理解它们各自的使用场景。这样,你就能够根据具体的编程情境选择最合适的设计模式。
许多公司在没有充分理由的情况下仍选择使用内部框架,但这对非大型科技公司来说通常是不现实的。对于中小型企业来说,与开源社区或大型科技公司竞争,开发出更优解决方案的可能性较低。
相比于重复发明轮子和制造不必要的工作,更理智的选择是直接利用已有的工具和技术。这不仅节省时间,还有助于开发人员的职业发展,因为他们无需学习只在公司内部使用的框架。
例如,Hibernate 是一个经过严格测试并被广泛使用的持久性框架。我遇到过的一家公司选择使用自己的内部框架进行持久化处理,尽管它并不具备 Hibernate 的全部功能和稳定性。维护和扩展这种内部框架给公司带来了额外负担,而没有带来相应的好处。
因此,建议尽可能使用市场上广泛可用且流行的技术和工具。开发一个能与成熟开源软件匹敌的框架几乎不可能,因为后者是众多才华横溢的开发者多年合作的成果。此外,许多大型公司也支持开源项目,确保它们能按预期运行。
理解和应用代码复用性的关键原则对于构建高效且可维护的软件系统至关重要。通过掌握抽象、封装、关注点分离、标准化和文档化等关键概念,开发人员能创建可节省时间和减少重复工作的组件,同时提升代码质量。
设计模式对代码复用至关重要,提供了针对常见设计问题的经过验证过的解决方案。内聚性和低耦合确保组件独立且依赖性最小,提高了它们的复用性。遵循 SOLID 原则有助于创建模块化、可扩展的代码,易于集成到不同项目中。
编写可复用代码能够为开发人员带来诸多好处,包括提高生产力、加强协作和加快开发周期。可复用代码使项目迭代更快、维护更容易,能够有效利用现有解决方案。总之,掌握代码复用性的核心原则能够帮助开发人员构建可扩展、适应性强且面向未来的软件系统。
刘汪洋,51CTO社区编辑,昵称:明明如月,一个拥有 5 年开发经验的某大厂高级 Java 工程师,拥有多个主流技术博客平台博客专家称号。
原文标题:How to write reusable Java code,作者:Rafael del Nero