新聞中心
譯者 | 劉汪洋

審校 | 重樓
什么是 Java 代碼重構(gòu)?
Java 代碼重構(gòu)是一種在不影響代碼外部行為的前提下進行的代碼優(yōu)化,它通過漸進和小規(guī)模的優(yōu)化來改善現(xiàn)有代碼的結(jié)構(gòu)和質(zhì)量。重構(gòu)的目標是提高代碼的可讀性、性能、可維護性和效率等。
Martin Fowler 是這個領域的權(quán)威的大牛和非常高產(chǎn)的作家,他在多篇文章和書籍中探討了代碼設計和重構(gòu)的主題。在他的作品《重構(gòu):改善既有代碼的設計》中,他精辟地解釋了重構(gòu)的本質(zhì):
_“重構(gòu)是在不改變代碼外在行為的前提下,對代碼做出修改,以改進程序內(nèi)在結(jié)構(gòu)的過程。重構(gòu)是一種經(jīng)過千錘百煉形成的有條不紊的程序整理方法,可以最大限度地減少整理過程中引入的錯誤幾率。其核心是不斷進行一些小的優(yōu)化,每個優(yōu)化看似不起眼,但積少成多,效果顯著。”
——Martin Fowler
在進行 Java 代碼重構(gòu)時,可以考慮以下常見的優(yōu)化措施:
- 為變量、類、函數(shù)和其他元素重命名,使其具有更好的可讀性和自描述性。
- 通過內(nèi)聯(lián)減少方法或函數(shù)調(diào)用,使用更簡潔的內(nèi)容。
- 抽取函數(shù)中的代碼塊,并將它們移到新的獨立函數(shù)中,以增強模塊性和可讀性。
- 消除冗余,通過刪除相同功能的多個代碼片段,并將它們合并為一個。
- 拆分處理過多職責的類或模塊,將其分解為更小、更有內(nèi)聚的組件。
- 合并具有相似功能的類或模塊,簡化結(jié)構(gòu)。
- 進行代碼性能方面的優(yōu)化。
Java 代碼重構(gòu)技巧
變量和方法的重新命名
為變量和方法選擇具有代表性的名稱是增強代碼可讀性的重要方法。
代碼的可讀性是構(gòu)建高質(zhì)量代碼庫的關(guān)鍵要素之一。易讀的代碼能夠清晰地表達其目的,而難以理解的代碼則會增加重構(gòu)過程中出現(xiàn)錯誤的風險。采用有意義的變量和方法名稱可以減少注釋的需求,并降低溝通成本。
// 重構(gòu)前
int d = 30; // 天數(shù)
int h = 24; // 一天的小時數(shù)
// 重構(gòu)后
int daysInMonth = 30;
int hoursInDay = 24;方法的提取
在 Java 代碼重構(gòu)技術(shù)中,方法的提取是一種常見而實用的策略。當一個方法變得過長和過于復雜時,通過提取部分功能到一個新的方法中能夠使原方法更簡潔和易讀。這不僅使代碼更具可維護性,還提高了其可重用性。
假設你有一個簡單的類,用于處理訂單并計算小計、稅費和總費用。
public class OrderProcessor {
private List- items;
private double taxRate;
public double processOrder() {
double subtotal = 0;
for (Item item : items) {
subtotal += item.getPrice();
}
double totalTax = subtotal * taxRate;
double totalCost = subtotal + totalTax;
return totalCost;
}
}
你可以將該代碼重構(gòu),把計算小計、稅費和總費用的代碼分別提取到 calculateSubtotal、calculateTax 和 calculateTotalCost 三個獨立的方法中,從而使類更加易讀、模塊化和可重用。
public class OrderProcessor {
private List- items;
private double taxRate;
public double processOrder() {
double subtotal = calculateSubtotal();
double totalTax = calculateTax(subtotal);
return calculateTotalCost(subtotal, totalTax);
}
private double calculateSubtotal() {
double subtotal = 0;
for (Item item : items) {
subtotal += item.getPrice();
}
return subtotal;
}
private double calculateTax(double subtotal) {
return subtotal * taxRate;
}
private double calculateTotalCost(double subtotal, double totalTax) {
return subtotal + totalTax;
}
}
消除“魔法”數(shù)字和字符串
“魔法”數(shù)字和字符串是指直接硬編碼在代碼中的值。這種做法不僅會降低代碼的可維護性,還可能因輸入錯誤而導致結(jié)果不一致和錯誤的增多。為了避免這樣的問題,你應當避免使用硬編碼的值,而是通過使用具有清晰描述性的常量來重構(gòu)你的代碼。
// 重構(gòu)前
if (status == 1) {
// ... 活躍狀態(tài)的代碼 ...
}
// 重構(gòu)后
public static final int ACTIVE_STATUS = 1;
if (status == ACTIVE_STATUS) {
// ... 活躍狀態(tài)的代碼 ...
}代碼復用
代碼復用是指刪除代碼庫中多處出現(xiàn)的重復或相似的代碼段。這樣的代碼不僅降低了代碼質(zhì)量和效率,還可能導致 bug 更加頻繁的出現(xiàn)和代碼庫變得更加復雜。因此,開發(fā)人員通常會對這類代碼感到反感。為了優(yōu)化代碼,我們可以考慮提取重復部分來創(chuàng)建可復用的方法或函數(shù),同時確保重構(gòu)過程中保持原有代碼的功能和邏輯。
重構(gòu)前
public class NumberProcessor {
// 計算總和
public int calculateTotal(int[] numbers) {
int total = 0;
for (int i = 0; i < numbers.length; i++) {
total += numbers[i];
}
return total;
}
// 計算平均值
public double calculateAverage(int[] numbers) {
int total = 0;
for (int i = 0; i < numbers.length; i++) {
total += numbers[i];
}
double average = (double) total / numbers.length;
return average;
}
}重構(gòu)后
public class NumberProcessor {
// 計算總和
public int calculateSum(int[] numbers) {
int total = 0;
for (int i = 0; i < numbers.length; i++) {
total += numbers[i];
}
return total;
}
// 計算總和
public int calculateTotal(int[] numbers) {
return calculateSum(numbers);
}
// 計算平均值
public double calculateAverage(int[] numbers) {
int total = calculateSum(numbers);
double average = (double) total / numbers.length;
return average;
}
}在優(yōu)化后的代碼中,我們將用于計算數(shù)組總和的邏輯提取到了一個名為 calculateSum 的獨立方法中?,F(xiàn)在,calculateTotal 和 calculateAverage 方法可以直接調(diào)用 calculateSum 來獲取數(shù)組的總和,從而避免了代碼重復。
簡化方法
隨著時間的推移,隨著維護代碼的人越來越多,代碼庫容易變得陳舊和混亂。為保證代碼的清晰度和易維護性,就非常有必要對代碼進行重構(gòu),使其更易理解、維護和擴展。
在簡化方法的過程中,首先要識別出那些包含復雜嵌套邏輯和承擔過多職責的方法。接著,可以通過以下幾步來簡化它們:
- 遵循單一責任原則(SRP)來調(diào)整方法的功能劃分。
- 將部分功能提取出來,創(chuàng)建新的子方法。
- 刪除無用和冗余的代碼。
- 減少方法內(nèi)部的嵌套層次,使其結(jié)構(gòu)更清晰。
接下來,我們將通過一個 Java 代碼重構(gòu)示例來具體展示如何簡化方法。
簡化前
public class ShoppingCart {
private List- items;
// 計算總價
public double calculateTotalCost() {
double total = 0;
for (Item item : items) {
if (item.isDiscounted()) {
total += item.getPrice() * 0.8;
} else {
total += item.getPrice();
}
}
if (total > 100) {
total -= 10;
}
return total;
}
}
我們可以通過提取 calculateItemPrice 邏輯到 calculateItemPrice 和 applyDiscount 方法中,并使用三元運算符來簡化條件判斷,使上述示例更為簡潔。
簡化后
public class ShoppingCart {
private List- items;
// 計算總價
public double calculateTotalCost() {
double total = 0;
for (Item item : items) {
total += calculateItemPrice(item);
}
total -= applyDiscount(total);
return total;
}
// 計算價格
private double calculateItemPrice(Item item) {
return item.isDiscounted() ? item.getPrice() * 0.8 : item.getPrice();
}
// 獲取滿減
private double applyDiscount(double total) {
return total > 100 ? 10 : 0;
}
}
紅綠重構(gòu)流程
圖片來源: Codecademy
紅綠重構(gòu),又稱測試驅(qū)動開發(fā)(TDD),是一種強調(diào)先編寫測試再編寫能通過這些測試的代碼的代碼重構(gòu)技術(shù)。該技術(shù)是一個循環(huán)迭代的過程,每一輪迭代都包括編寫新的測試和足夠的代碼來通過這些測試,最后對代碼進行重構(gòu)。
這一技術(shù)包括以下三個階段:
- 紅色階段: 在這一階段,你還未編寫實際的代碼。首先需要編寫一組預期會失敗的測試(標記為紅色),因為還沒有相應的實現(xiàn)來滿足這些測試條件。
- 綠色階段: 此階段的目的是編寫足夠的代碼來通過之前編寫的未通過的測試,即讓測試變綠。注意,此時的目標不是編寫完美或高度優(yōu)化的代碼,而是簡單地確保測試的通過。
- 重構(gòu)階段: 在確認代碼已成功通過所有測試后,此時應著重于代碼重構(gòu),以提升其性能和結(jié)構(gòu),而不改變其基本功能,確保測試仍然能夠順利通過。
每完成一個測試用例后,你將進入下一個循環(huán),繼續(xù)編寫新的測試用例和對應的代碼,然后再進行代碼重構(gòu)以實現(xiàn)更好的優(yōu)化。
優(yōu)化違反單一責任原則的代碼
可能你已對面向?qū)ο缶幊讨械?SOLID 原則有所了解。SOLID 是五個設計原則的首字母縮寫。
- S:單一責任原則(Single Responsibility Principle),這個原則強調(diào)一個類應該僅有一個變化的原因,簡言之,一個類應只負責一個功能點。
- O:開放封閉原則(Open Closed Principle),軟件實體應該是可擴展的而不是可修改的。
- L:里氏替換原則(Liskov Substitution Principle),對象應該能夠被其子類型所替換,而不影響程序的正確性。
- I:接口隔離原則(Interface Segregation Principle),客戶端不應被強迫依賴于它們不用的接口。
- D:依賴倒置原則(Dependency Inversion Principle),高層模塊不應該依賴于低層模塊,二者都應該依賴于抽象。
單一責任原則是首要原則,該原則強調(diào)每個類應僅有一個變化的原因,即一個類只負責一個功能點遵守單一責任原則是確保代碼可維護、可讀、靈活和模塊化的基本方式之一。下面我們將展示一個 OrderProcessor 類的示例,這個類違反了單一責任原則,因為它同時承擔了訂單處理和記錄信息日志兩項職責。
重構(gòu)前
public class OrderProcessor {
// 處理訂單
public void processOrder(Order order) {
// 訂單驗證
// 處理邏輯
// 日志記錄
Logger logger = new Logger();
logger.log("Order processed: " + order.getId());
}
}為了遵循單一責任原則,我們可以將該類重構(gòu)為三個類:一個是 OrderProcessor 類,只負責訂單處理;OrderValidator負責訂單校驗,另外一個是 OrderLogger 類,專門負責日志記錄。
重構(gòu)后
public class OrderProcessor {
private final OrderValidator orderValidator;
public OrderProcessor(OrderValidator orderValidator) {
this.orderValidator = orderValidator;
}
// 處理訂單
public void processOrder(Order order) {
// 訂單驗證
if(!orderValidator.validate(order)) {
throw new IllegalArgumentException("Order is not valid");
}
// 處理邏輯
// ...
// 日志記錄
OrderLogger logger = new OrderLogger();
logger.logOrderProcessed(order);
}
}
public class OrderValidator {
// 訂單驗證
public boolean validate(Order order) {
// 驗證邏輯
// ...
return true;
}
}
public class OrderLogger {
public void logOrderProcessed(Order order) {
Logger logger = new Logger();
logger.log("Order processed: " + order.getId());
}
}Java 代碼重構(gòu)的 14 條最佳實踐
代碼重構(gòu)是一個能夠顯著提升代碼質(zhì)量的重要步驟,它帶來了我們之前強調(diào)的諸多好處。但在進行重構(gòu)時也需謹慎,特別是在處理龐大的代碼庫或不熟悉的代碼庫時,以避免無意中改變軟件的功能或產(chǎn)生未預見的問題。
為了避免任何潛在問題,您可以參考以下重構(gòu) Java 代碼的技巧和最佳實踐:
- 保持功能不變: Java 代碼重構(gòu)的首要目標是提高代碼質(zhì)量。然而,程序的外部行為,如它如何響應輸入和輸出以及其他用戶交互應該保持不變。
- 充分理解代碼: 在開始重構(gòu)某個代碼片段之前,請確保你充分理解你即將重構(gòu)的代碼。這包括其功能、交互和依賴關(guān)系。這種理解將指導你的決策,并幫助你避免做出可能影響代碼功能的更改。
- 將 Java 重構(gòu)過程分解為小步驟: 重構(gòu)大型軟件,尤其是當你對它還不夠了解時,可能會令人感到不知所措。然而,通過將重構(gòu)過程分解為更小、易于管理的步驟,你可以使工作負擔更輕,減少錯誤的風險,并容易持續(xù)驗證你的更改。
- 使用版本控制創(chuàng)建備份: 由于重構(gòu)涉及對代碼庫進行更改,有可能事情不會按計劃進行。在一個單獨的分支上備份你的工作軟件是一個好主意,以避免在找不出是哪些更改破壞了你的軟件時浪費大量時間和資源。像 Git 這樣的版本控制系統(tǒng)允許你同時管理不同的軟件版本。
- 頻繁測試你的更改: 在重構(gòu)代碼時你最不想做的就是不小心破壞程序的功能或引入影響軟件的錯誤。在進行任何重大的代碼更改之前,尤其是重構(gòu),建立一套測試集是提供安全網(wǎng)的好方法。這些測試驗證你的代碼行為是否符合預期,以及現(xiàn)有的功能是否保持完整。
- 利用重構(gòu)工具: 有了像 Eclipse 和 IntelliJ 這樣的現(xiàn)代 IDE,Java 代碼重構(gòu)不必是一個緊張的過程。例如,IntelliJ IDEA 包括一套強大的重構(gòu)功能。一些功能包括安全刪除,提取方法/參數(shù)/字段/變量,內(nèi)聯(lián)重構(gòu),復制/移動等。這些功能使你的工作更輕松,減少了你在重構(gòu)過程中引入錯誤的機會。
- 深入理解代碼變更:在重構(gòu)過程中,深入了解你所做的代碼變更是非常重要的,它可以幫助你快速識別和解決可能出現(xiàn)的問題。
- 充分利用單元測試:作為開發(fā)者,我們需要確保在重構(gòu)代碼時不會破壞現(xiàn)有的應用程序或引入新的 bug。一個完善的單元測試套件可以幫助你檢測回歸問題,確保功能的完整性,同時也有利于團隊合作和長期的代碼維護。
- 持續(xù)跟蹤性能變化并反饋:Java 代碼重構(gòu)不僅僅是為了改善代碼結(jié)構(gòu),它還涉及到性能優(yōu)化。通過持續(xù)監(jiān)控性能指標,你可以確保你的重構(gòu)工作正在取得實質(zhì)性的進展。
- 進行同行代碼審查:重構(gòu)完成后,建議邀請另一名開發(fā)者來審查你的更改,他們可以從一個新的角度來發(fā)現(xiàn)可能被你忽視的問題,并提供有價值的反饋。
- 記錄重構(gòu)變更:當你與其他開發(fā)者一起工作時,記錄你的重構(gòu)變更非常重要,因為這樣可以提高透明度和協(xié)作效率,同時也有助于新員工更快地了解項目。
- 進行回歸測試:完成重構(gòu)后,需要通過回歸測試來確保所有現(xiàn)有的功能都得到了保留,并且新引入的邏輯沒有與現(xiàn)有代碼產(chǎn)生沖突。
- 保持團隊同步:在多人開發(fā)團隊中工作時,及時通報你的重構(gòu)變更非常必要,這可以避免沖突并幫助團隊更好地適應新的變更。
- 在必要時執(zhí)行回滾:如果重構(gòu)過程中遇到無法解決的問題,不要猶豫,立即回滾到一個穩(wěn)定的狀態(tài),以避免浪費更多的時間和資源。
結(jié)論
重構(gòu)是一項至關(guān)重要的技術(shù)實踐,它是確保軟件項目長期成功的關(guān)鍵。 通過融入我們之前討論的技術(shù)到你的開發(fā)周期并嚴格遵循最佳實踐,你可以把任何復雜且混亂的代碼庫改造為一個可讀、可維護和可擴展的軟件解決方案。但請注意,Java 代碼重構(gòu)不是一次性的任務,而是一個可以持續(xù)整合到你的開發(fā)周期中的過程。
常見問題解答
應該何時重構(gòu)我的 Java 代碼?
在軟件開發(fā)的任何階段都可以進行代碼重構(gòu)。無論是在添加新功能、修復 bug 還是優(yōu)化難以理解的代碼片段時,都是進行重構(gòu)的好時機。定期預留時間來進行重構(gòu)可以避免技術(shù)債務的累積,從而維持代碼庫的高質(zhì)量。
如何決定哪些代碼需要重構(gòu)?
你可以從識別代碼庫中難以理解、存在重復邏輯或容易產(chǎn)生 bug 的區(qū)域開始。尋找具有冗長的方法、復雜條件語句的代碼,并嘗試遵循“單一職責原則”來提高代碼的組織性。
如何確保重構(gòu)不會引入 bug?
擁有一套完善的自動化測試套件是減少重構(gòu)過程中引入 bug 的風險的關(guān)鍵。在開始重構(gòu)之前,確保你的代碼具有良好的測試覆蓋率,這將幫助你捕捉任何可能的回歸問題,確保代碼功能的穩(wěn)定性。
如何在 Java 中重構(gòu)類?
首先,你需要確定目標類并深入理解其行為和依賴關(guān)系。然后可以考慮拆分龐大的方法和將方法移動到更適合的類中,同時利用繼承和接口來實現(xiàn)更清晰的結(jié)構(gòu)。此外,重命名變量和方法、重新組織代碼結(jié)構(gòu)和簡化條件語句都可以提高代碼的可讀性。最后,確保對所有更改進行徹底測試,以保證功能的完整性。
譯者介紹
劉汪洋,社區(qū)編輯,昵稱:明明如月,一個擁有 5 年開發(fā)經(jīng)驗的某大廠高級 Java 工程師,擁有多個主流技術(shù)博客平臺博客專家稱號。
標題:CODE REFACTORING IN JAVA: TIPS, BEST PRACTICES, TECHNIQUES,作者:Digma
網(wǎng)站欄目:Java中的代碼重構(gòu):技巧、優(yōu)秀實踐與方法
標題來源:http://fisionsoft.com.cn/article/coohoeg.html


咨詢
建站咨詢
