譯者 | 劉汪洋
審校 | 重樓
對于軟件開發者而言,編寫可重用的代碼是一項基本而重要的技能。每位工程師都應掌握如何盡可能地提高代碼的復用性。當前,一些開發人員可能會認為微服務的本質是小而高效,因此他們無需編寫高質量代碼。然而,即便是微服務,在變得龐大時,閱讀和理解代碼的時間成本也會迅速增加至編寫時的十倍。
代碼一開始編寫得不佳,將會大幅增加修復 bug 或添加新功能的工作量。在一些極端情況下,我見證過團隊因代碼質量問題而放棄原有代碼,重新編寫。這不僅浪費了寶貴時間,還可能導致開發人員承擔責任并失去工作。
本文將介紹經過實踐驗證的提高JAVA 中代碼可重用性的八條指導原則。
Java 編程中編寫可重用代碼的八大指導原則
- 制定編碼規范
- 記錄 API 文檔
- 遵守代碼命名規范
- 提高代碼內聚性
- 代碼解耦
- 遵循 SOLID 原則
- 合理運用設計模式
- 避免重復造輪子
制定編碼規范
編寫可重用代碼的首要步驟是與團隊一同確立代碼規范。如果對編碼規范不能達成共識,代碼很快就會變得混亂不堪。如果團隊成員之間意見不統一,關于代碼實現的無效討論也會頻繁發生。同時,你需要確立一個基礎的代碼設計框架,以解決軟件需要解決的問題。
在制定了標準和代碼設計框架之后,接下來應當明確代碼指導原則。
常見的指導原則包括:
- 代碼命名規范
- 類和方法的行數限制
- 異常處理方式
- 包結構設計
- 編程語言及其版本
- 所使用的框架、工具和庫
- 代碼測試標準
- 代碼結構層次(如控制器、服務、存儲庫、領域等)
一旦團隊就這些規范達成共識,每個成員都應對代碼審查負責,以確保編寫出高質量、可重用的代碼。如果沒有共識,寫出高質量且可重用的代碼幾乎不可能。
記錄 API 文檔
當創建服務并以 API 形式公開時,應該詳細記錄 API 信息,以便新加入的開發人員能夠輕松理解和使用。
API 在微服務架構中扮演著重要角色。因此,對你的項目不太熟悉的其他團隊成員必須能夠通過閱讀 API 文檔來理解其功能。如果 API 文檔記錄不當,代碼的重復編寫風險會增加。新開發人員可能會無意中創建一個和現有功能重復的方法。
因此,精確記錄 API 至關重要。但在代碼中過度使用文檔可能并無益處。應該只記錄 API 中的關鍵信息,如業務操作的解釋、參數、返回值對象等。
遵守代碼命名規范
簡潔且具有描述性的代碼命名總是優于晦澀難懂的縮寫。瀏覽不熟悉的代碼庫時,我發現縮寫往往難以讓人立即理解其含義。
因此,相較于使用像Ctr這樣的縮寫,直接命名為Customer更為明晰和有意義。Ctr可能代表了合同(contract)、控制(control)、客戶(customer)等多種含義,使人難以確定其準確意圖。
此外,要遵循你所使用編程語言的命名規范。以 Java 為例,它有 JavaBeans 命名規范,這對每個 Java 開發者來說都是基本常識。以下是 Java 中類、方法、變量和包的命名方式:
- 類名采用 PascalCase(帕斯卡命名法):如CustomerContract
- 方法和變量采用 camelCase(駝峰命名法):如customerContract
- 包名全部小寫:如service
提高代碼內聚性
內聚的代碼應該專注于_做好一件事_。雖然這是一個簡單的概念,但即便是經驗豐富的開發人員也常常忽視它。這樣,他們就會創建出所謂的_超級復雜類_,即一個承擔了過多職責的類,有時也被稱為_全能類_。
要實現高內聚的代碼,關鍵是學會拆分代碼,確保每個類和方法專注于單一職責。比如,如果你創建了一個名為saveCustomer的方法,它應當只負責一個動作:保存客戶信息。它不應該同時負責更新和刪除客戶信息。
同理,如果有一個名為CustomerService的類,它應該僅包含與客戶相關的功能。如果CustomerService類中有執行產品相關操作的方法,應移至ProductService類中。
與其在CustomerService類中添加執行產品操作的方法,不如在該類中引用ProductService,并調用我們所需的任何方法。
為了更清晰地介紹這個概念,我們來分析一個低內聚的類示例:
public class CustomerPurchaseService {
public void saveCustomerPurchase(CustomerPurchase customerPurchase) {
// 執行與客戶相關的操作
registerProduct(customerPurchase.getProduct());
// 更新客戶信息
// 刪除客戶信息
}
private void registerProduct(Product product) {
// 在客戶領域中執行對產品的邏輯操作…
}
}
這個類存在以下問題:
- saveCustomerPurchase方法不止注冊產品,還涉及到更新和刪除客戶的操作。這個方法承擔了過多的職責。
- registerProduct方法在當前位置難以被其他開發者發現。因此,如果其他開發者需要類似的功能,可能會不必要地重寫這個方法。
- registerProduct方法處于不恰當的領域。CustomerPurchaseService不應負責注冊產品。
- saveCustomerPurchase方法調用了一個私有方法,而不是委托給專門處理產品操作的外部類。
識別出這些問題后,我們可以重新編寫這段代碼,使其變得更加內聚。我們將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 原則
SOLID 代表面向對象編程(OOP)中五個關鍵的設計原則,它們的目標是使軟件系統更加可維護、靈活,并易于理解。
下面是這些原則的簡要說明:
- 單一職責原則(SRP):一個類應該僅承擔一個職責或目標,并且完全封裝這個職責。這個原則促進_高內聚_,幫助保持類的專注和易于管理。
- 開放封閉原則(OCP):軟件實體(如類、模塊、函數等)應該對擴展開放,對修改封閉。應設計代碼以便在不更改現有代碼的情況下增加新功能,從而減少更改的影響并促進代碼重用。
- 里氏替換原則(LSP):超類的任何實例都應該能被其子類的實例替換,而不影響程序的正確性。換言之,基類的任何實例都應該可以用其派生類的實例替換,保證程序行為的一致性。
- 接口隔離原則(ISP):客戶端不應被迫依賴它們不需要的接口。建議將大型接口拆分為更小且更具體的接口,使得客戶端僅需依賴它們真正需要的接口。這有助于松耦合和避免不必要的依賴。
- 依賴反轉原則(DIP):高層模塊不應依賴低層模塊,兩者都應依賴抽象。鼓勵使用抽象(接口或抽象類)來解耦高層模塊和低層實現細節,促進基于抽象而非具體實現的依賴。
遵循這些 SOLID 原則有助于開發者編寫更模塊化、可維護且易于擴展的代碼。這些原則有助于實現更易于理解、測試和修改的代碼,從而形成更健壯、適應性更強的軟件系統。
合理運用設計模式
設計模式是經驗豐富的開發者在處理多種編碼場景后總結出的最佳實踐。恰當地使用設計模式可以顯著提升代碼的復用性。
掌握設計模式還能增強你閱讀和理解代碼的能力——這包括 JDK 中的代碼。當你能識別出其背后的設計模式時,代碼會變得更加清晰。
盡管設計模式有其用處,但并非每種模式適用于所有情況,因此使用時需謹慎。僅僅因為我們了解某個模式,并不意味著就應該隨意應用。在不恰當的場景中使用設計模式可能會使代碼變得更復雜、更難以維護。然而,在合適的場合應用設計模式,可以使代碼更加靈活和易于擴展。
以下是面向對象編程中常見的設計模式簡要概述:
創建型模式
- 單例(Singleton):確保一個類僅有一個實例,并提供一個全局訪問點。
- 工廠方法(Factory Method):定義一個用于創建對象的接口,但由子類決定實例化哪個類。
- 抽象工廠(Abstract Factory):提供一個接口,用于創建相關或依賴對象的族群。
- 建造者(Builder):分離復雜對象的構建和表示。
- 原型(Prototype):通過復制現有的實例來創建新實例。
結構型模式
- 適配器(Adapter):將一個類的接口轉換成客戶端所期望的另一種接口。
- 裝飾器(Decorator):動態地為對象添加新的功能。
- 代理(Proxy):為另一個對象提供一個代理或占位符,以控制對這個對象的訪問。
- 組合(Composite):將對象組合成樹形結構,以表示部分整體的層次結構。
- 橋接(Bridge):將抽象部分與其實現部分分離,使它們可以獨立變化。
行為型模式
- 觀察者:在對象間建立一種一對多的依賴關系,使得當一個對象改變狀態時,所有依賴它的對象都會自動收到通知并更新。
- 策略:封裝了一系列算法,并在運行時允許選擇其中的一種。
- 模板方法:在基類中定義一個算法的框架,并允許子類提供具體的實現。
- 命令:將請求或簡單操作封裝成對象,這使得你可以使用不同的請求、隊列或日志請求,并支持可撤銷的操作。
- 狀態:當對象的內部狀態改變時,允許對象改變其行為。
- 迭代器:提供一種方法來順序訪問聚合對象的元素,而無需暴露其底層表示。
- 責任鏈:允許請求沿著處理者鏈傳遞,直到一個處理者處理它。
- 中介者:定義一個對象,該對象封裝了一組對象如何交互的方式,從而促進它們之間的松散耦合。
- 訪問者:將算法從操作的對象中分離出來,將算法封裝到一個稱為訪問者的對象中。
并不需要記住每一種設計模式,重要的是意識到這些模式的存在,并理解它們各自的使用場景。這樣,你就能夠根據具體的編程情境選擇最合適的設計模式。
避免重復造輪子
許多公司在沒有充分理由的情況下仍選擇使用內部框架,但這對非大型科技公司來說通常是不現實的。對于中小型企業來說,與開源社區或大型科技公司競爭,開發出更優解決方案的可能性較低。
相比于重復發明輪子和制造不必要的工作,更理智的選擇是直接利用已有的工具和技術。這不僅節省時間,還有助于開發人員的職業發展,因為他們無需學習只在公司內部使用的框架。
例如,Hibernate 是一個經過嚴格測試并被廣泛使用的持久性框架。我遇到過的一家公司選擇使用自己的內部框架進行持久化處理,盡管它并不具備 Hibernate 的全部功能和穩定性。維護和擴展這種內部框架給公司帶來了額外負擔,而沒有帶來相應的好處。
因此,建議盡可能使用市場上廣泛可用且流行的技術和工具。開發一個能與成熟開源軟件匹敵的框架幾乎不可能,因為后者是眾多才華橫溢的開發者多年合作的成果。此外,許多大型公司也支持開源項目,確保它們能按預期運行。
結論
理解和應用代碼復用性的關鍵原則對于構建高效且可維護的軟件系統至關重要。通過掌握抽象、封裝、關注點分離、標準化和文檔化等關鍵概念,開發人員能創建可節省時間和減少重復工作的組件,同時提升代碼質量。
設計模式對代碼復用至關重要,提供了針對常見設計問題的經過驗證過的解決方案。內聚性和低耦合確保組件獨立且依賴性最小,提高了它們的復用性。遵循 SOLID 原則有助于創建模塊化、可擴展的代碼,易于集成到不同項目中。
編寫可復用代碼能夠為開發人員帶來諸多好處,包括提高生產力、加強協作和加快開發周期。可復用代碼使項目迭代更快、維護更容易,能夠有效利用現有解決方案。總之,掌握代碼復用性的核心原則能夠幫助開發人員構建可擴展、適應性強且面向未來的軟件系統。
譯者介紹
劉汪洋,51CTO社區編輯,昵稱:明明如月,一個擁有 5 年開發經驗的某大廠高級 Java 工程師,擁有多個主流技術博客平臺博客專家稱號。
原文標題:How to write reusable Java code,作者:Rafael del Nero