代码坏味道之非必要的
:notebook: 本文已歸檔到:「blog」
翻譯自:https://sourcemaking.com/refactoring/smells/dispensables
非必要的(Dispensables)這組壞味道意味著:這樣的代碼可有可無,它的存在反而影響整體代碼的整潔和可讀性。
- 冗余類
- 夸夸其談未來性
- 純稚的數(shù)據(jù)類
- 過多的注釋
- 重復(fù)代碼
- 擴展閱讀
- 參考資料
冗余類
冗余類(Lazy Class)
理解和維護總是費時費力的。如果一個類不值得你花費精力,它就應(yīng)該被刪除。
問題原因
也許一個類的初始設(shè)計是一個功能完全的類,然而隨著代碼的變遷,變得沒什么用了。 又或者類起初的設(shè)計是為了支持未來的功能擴展,然而卻一直未派上用場。
解決方法
- 沒什么用的類可以運用 將類內(nèi)聯(lián)化(Inline Class) 來干掉。
- 如果子類用處不大,試試 折疊繼承體系(Collapse Hierarchy) 。
收益
- 減少代碼量
- 易于維護
何時忽略
- 有時,創(chuàng)建冗余類是為了描述未來開發(fā)的意圖。在這種情況下,嘗試在代碼中保持清晰和簡單之間的平衡。
重構(gòu)方法說明
將類內(nèi)聯(lián)化(Inline Class)
問題
某個類沒有做太多事情。
解決
將這個類的所有特性搬移到另一個類中,然后移除原類。
折疊繼承體系(Collapse Hierarchy)
問題
超類和子類之間無太大區(qū)別。
解決
將它們合為一體。
夸夸其談未來性
夸夸其談未來性(Speculative Generality)
存在未被使用的類、函數(shù)、字段或參數(shù)。
問題原因
有時,代碼僅僅為了支持未來的特性而產(chǎn)生,然而卻一直未實現(xiàn)。結(jié)果,代碼變得難以理解和維護。
解決方法
- 如果你的某個抽象類其實沒有太大作用,請運用 折疊繼承體系(Collapse Hierarch) 。
- 不必要的委托可運用 將類內(nèi)聯(lián)化(Inline Class) 消除。
- 無用的函數(shù)可運用 內(nèi)聯(lián)函數(shù)(Inline Method) 消除。
- 函數(shù)中有無用的參數(shù)應(yīng)該運用 移除參數(shù)(Remove Parameter) 消除。
- 無用字段可以直接刪除。
收益
- 減少代碼量。
- 更易維護。
何時忽略
- 如果你在一個框架上工作,創(chuàng)建框架本身沒有使用的功能是非常合理的,只要框架的用戶需要這個功能。
- 刪除元素之前,請確保它們不在單元測試中使用。如果測試需要從類中獲取某些內(nèi)部信息或執(zhí)行特殊的測試相關(guān)操作,就會發(fā)生這種情況。
重構(gòu)方法說明
折疊繼承體系(Collapse Hierarchy)
問題
超類和子類之間無太大區(qū)別。
解決
將它們合為一體。
將類內(nèi)聯(lián)化(Inline Class)
問題
某個類沒有做太多事情。
解決
將這個類的所有特性搬移到另一個類中,然后移除原類。
內(nèi)聯(lián)函數(shù)(Inline Method)
問題
一個函數(shù)的本體比函數(shù)名更清楚易懂。
class PizzaDelivery {//...int getRating() {return moreThanFiveLateDeliveries() ? 2 : 1;}boolean moreThanFiveLateDeliveries() {return numberOfLateDeliveries > 5;} } 復(fù)制代碼解決
在函數(shù)調(diào)用點插入函數(shù)本體,然后移除該函數(shù)。
class PizzaDelivery {//...int getRating() {return numberOfLateDeliveries > 5 ? 2 : 1;} } 復(fù)制代碼移除參數(shù)(Remove Parameter)
問題
函數(shù)本體不再需要某個參數(shù)。
解決
將該參數(shù)去除。
純稚的數(shù)據(jù)類
純稚的數(shù)據(jù)類(Data Class) 指的是只包含字段和訪問它們的 getter 和 setter 函數(shù)的類。這些僅僅是供其他類使用的數(shù)據(jù)容器。這些類不包含任何附加功能,并且不能對自己擁有的數(shù)據(jù)進行獨立操作。
問題原因
當一個新創(chuàng)建的類只包含幾個公共字段(甚至可能幾個 getters / setters)是很正常的。但是對象的真正力量在于它們可以包含作用于數(shù)據(jù)的行為類型或操作。
解決方法
- 如果一個類有公共字段,你應(yīng)該運用 封裝字段(Encapsulated Field) 來隱藏字段的直接訪問方式。
- 如果這些類含容器類的字段,你應(yīng)該檢查它們是不是得到了恰當?shù)姆庋b;如果沒有,就運用 封裝集合(Encapsulated Collection) 把它們封裝起來。
- 找出這些 getter/setter 函數(shù)被其他類運用的地點。嘗試以 搬移函數(shù)(Move Method) 把那些調(diào)用行為搬移到 純稚的數(shù)據(jù)類(Data Class) 來。如果無法搬移這個函數(shù),就運用 提煉函數(shù)(Extract Method) 產(chǎn)生一個可搬移的函數(shù)。
- 在類已經(jīng)充滿了深思熟慮的函數(shù)之后,你可能想要擺脫舊的數(shù)據(jù)訪問方法,以提供適應(yīng)面較廣的類數(shù)據(jù)訪問接口。為此,可以運用 移除設(shè)置函數(shù)(Remove Setting Method) 和 隱藏函數(shù)(Hide Method) 。
收益
- 提高代碼的可讀性和組織性。特定數(shù)據(jù)的操作現(xiàn)在被集中在一個地方,而不是在分散在代碼各處。
- 幫助你發(fā)現(xiàn)客戶端代碼的重復(fù)處。
重構(gòu)方法說明
封裝字段(Encapsulated Field)
問題
你的類中存在 public 字段。
class Person {public String name; } 復(fù)制代碼解決
將它聲明為 private,并提供相應(yīng)的訪問函數(shù)。
class Person {private String name;public String getName() {return name;}public void setName(String arg) {name = arg;} } 復(fù)制代碼封裝集合(Encapsulated Collection)
問題
有個函數(shù)返回一個集合。
解決
讓該函數(shù)返回該集合的一個只讀副本,并在這個類中提供添加、移除集合元素的函數(shù)。
搬移函數(shù)(Move Method)
問題
你的程序中,有個函數(shù)與其所駐類之外的另一個類進行更多交流:調(diào)用后者,或被后者調(diào)用。
解決
在該函數(shù)最常引用的類中建立一個有著類似行為的新函數(shù)。將舊函數(shù)變成一個單純的委托函數(shù),或是舊函數(shù)完全移除。
提煉函數(shù)(Extract Method)
問題
你有一段代碼可以組織在一起。
void printOwing() {printBanner();//print detailsSystem.out.println("name: " + name);System.out.println("amount: " + getOutstanding()); } 復(fù)制代碼解決
移動這段代碼到一個新的函數(shù)中,使用函數(shù)的調(diào)用來替代老代碼。
void printOwing() {printBanner();printDetails(getOutstanding()); }void printDetails(double outstanding) {System.out.println("name: " + name);System.out.println("amount: " + outstanding); } 復(fù)制代碼移除設(shè)置函數(shù)(Remove Setting Method)
問題
類中的某個字段應(yīng)該在對象創(chuàng)建時被設(shè)值,然后就不再改變。
解決
去掉該字段的所有設(shè)值函數(shù)。
隱藏函數(shù)(Hide Method)
問題
有一個函數(shù),從來沒有被其他任何類用到。
解決
將這個函數(shù)修改為 private。
過多的注釋
過多的注釋(Comments)
注釋本身并不是壞事。但是常常有這樣的情況:一段代碼中出現(xiàn)長長的注釋,而它之所以存在,是因為代碼很糟糕。
問題原因
注釋的作者意識到自己的代碼不直觀或不明顯,所以想使用注釋來說明自己的意圖。這種情況下,注釋就像是爛代碼的除臭劑。
最好的注釋是為函數(shù)或類起一個恰當?shù)拿帧?/p>
如果你覺得一個代碼片段沒有注釋就無法理解,請先嘗試重構(gòu),試著讓所有注釋都變得多余。
解決方法
- 如果一個注釋是為了解釋一個復(fù)雜的表達式,可以運用 提煉變量(Extract Variable) 將表達式切分為易理解的子表達式。
- 如果你需要通過注釋來解釋一段代碼做了什么,請試試 提煉函數(shù)(Extract Method) 。
- 如果函數(shù)已經(jīng)被提煉,但仍需要注釋函數(shù)做了什么,試試運用 函數(shù)改名(Rename Method) 來為函數(shù)起一個可以自解釋的名字。
- 如果需要對系統(tǒng)某狀態(tài)進行斷言,請運用 引入斷言(Introduce Assertion) 。
收益
- 代碼變得更直觀和明顯。
何時忽略
注釋有時候很有用:
- 當解釋為什么某事物要以特殊方式實現(xiàn)時。
- 當解釋某種復(fù)雜算法時。
- 當你實在不知可以做些什么時。
重構(gòu)方法說明
提煉變量(Extract Variable)
問題
你有個難以理解的表達式。
void renderBanner() {if ((platform.toUpperCase().indexOf("MAC") > -1) &&(browser.toUpperCase().indexOf("IE") > -1) &&wasInitialized() && resize > 0 ){// do something} } 復(fù)制代碼解決
將表達式的結(jié)果或它的子表達式的結(jié)果用不言自明的變量來替代。
void renderBanner() {final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;final boolean wasResized = resize > 0;if (isMacOs && isIE && wasInitialized() && wasResized) {// do something} } 復(fù)制代碼提煉函數(shù)(Extract Method)
問題
你有一段代碼可以組織在一起。
void printOwing() {printBanner();//print detailsSystem.out.println("name: " + name);System.out.println("amount: " + getOutstanding()); } 復(fù)制代碼解決
移動這段代碼到一個新的函數(shù)中,使用函數(shù)的調(diào)用來替代老代碼。
void printOwing() {printBanner();printDetails(getOutstanding()); }void printDetails(double outstanding) {System.out.println("name: " + name);System.out.println("amount: " + outstanding); } 復(fù)制代碼函數(shù)改名(Rename Method)
問題
函數(shù)的名稱未能恰當?shù)慕沂竞瘮?shù)的用途。
class Person {public String getsnm(); } 復(fù)制代碼解決
修改函數(shù)名。
class Person {public String getSecondName(); } 復(fù)制代碼引入斷言(Introduce Assertion)
問題
某一段代碼需要對程序狀態(tài)做出某種假設(shè)。
double getExpenseLimit() {// should have either expense limit or a primary projectreturn (expenseLimit != NULL_EXPENSE) ?expenseLimit:primaryProject.getMemberExpenseLimit(); } 復(fù)制代碼解決
以斷言明確表現(xiàn)這種假設(shè)。
double getExpenseLimit() {Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);return (expenseLimit != NULL_EXPENSE) ?expenseLimit:primaryProject.getMemberExpenseLimit(); } 復(fù)制代碼注:請不要濫用斷言。不要使用它來檢查”應(yīng)該為真“的條件,只能使用它來檢查“一定必須為真”的條件。實際上,斷言更多是用于自我檢測代碼的一種手段。在產(chǎn)品真正交付時,往往都會消除所有斷言。
重復(fù)代碼
重復(fù)代碼(Duplicate Code)
重復(fù)代碼堪稱為代碼壞味道之首。消除重復(fù)代碼總是有利無害的。
問題原因
重復(fù)代碼通常發(fā)生在多個程序員同時在同一程序的不同部分上工作時。由于他們正在處理不同的任務(wù),他們可能不知道他們的同事已經(jīng)寫了類似的代碼。
還有一種更隱晦的重復(fù),特定部分的代碼看上去不同但實際在做同一件事。這種重復(fù)代碼往往難以找到和消除。
有時重復(fù)是有目的性的。當急于滿足 deadline,并且現(xiàn)有代碼對于要交付的任務(wù)是“幾乎正確的”時,新手程序員可能無法抵抗復(fù)制和粘貼相關(guān)代碼的誘惑。在某些情況下,程序員只是太懶惰。
解決方法
- 同一個類的兩個函數(shù)含有相同的表達式,這時可以采用 提煉函數(shù)(Extract Method) 提煉出重復(fù)的代碼,然后讓這兩個地點都調(diào)用被提煉出來的那段代碼。
- 如果兩個互為兄弟的子類含有重復(fù)代碼:
- 首先對兩個類都運用 提煉函數(shù)(Extract Method) ,然后對被提煉出來的函數(shù)運用 函數(shù)上移(Pull Up Method) ,將它推入超類。
- 如果重復(fù)代碼在構(gòu)造函數(shù)中,運用 構(gòu)造函數(shù)本體上移(Pull Up Constructor Body) 。
- 如果重復(fù)代碼只是相似但不是完全相同,運用 塑造模板函數(shù)(Form Template Method) 獲得一個 模板方法模式(Template Method) 。
- 如果有些函數(shù)以不同的算法做相同的事,你可以選擇其中較清晰地一個,并運用 替換算法(Substitute Algorithm) 將其他函數(shù)的算法替換掉。
- 如果兩個毫不相關(guān)的類中有重復(fù)代碼:
- 請嘗試運用 提煉超類(Extract Superclass) ,以便為維護所有先前功能的這些類創(chuàng)建一個超類。
- 如果創(chuàng)建超類十分困難,可以在一個類中運用 提煉類(Extract Class) ,并在另一個類中使用這個新的組件。
- 如果存在大量的條件表達式,并且它們執(zhí)行完全相同的代碼(僅僅是它們的條件不同),可以運用 合并條件表達式(Consolidate Conditional Expression) 將這些操作合并為單個條件,并運用 提煉函數(shù)(Extract Method) 將該條件放入一個名字容易理解的獨立函數(shù)中。
- 如果條件表達式的所有分支都有部分相同的代碼片段:可以運用 合并重復(fù)的條件片段(Consolidate Duplicate Conditional Fragments) 將它們都存在的代碼片段置于條件表達式外部。
收益
- 合并重復(fù)代碼會簡化代碼的結(jié)構(gòu),并減少代碼量。
- 代碼更簡化、更易維護。
重構(gòu)方法說明
提煉函數(shù)(Extract Method)
問題
你有一段代碼可以組織在一起。
void printOwing() {printBanner();//print detailsSystem.out.println("name: " + name);System.out.println("amount: " + getOutstanding()); } 復(fù)制代碼解決
移動這段代碼到一個新的函數(shù)中,使用函數(shù)的調(diào)用來替代老代碼。
void printOwing() {printBanner();printDetails(getOutstanding()); }void printDetails(double outstanding) {System.out.println("name: " + name);System.out.println("amount: " + outstanding); } 復(fù)制代碼函數(shù)上移(Pull Up Method)
問題
有些函數(shù),在各個子類中產(chǎn)生完全相同的結(jié)果。
解決
將該函數(shù)移至超類。
構(gòu)造函數(shù)本體上移(Pull Up Constructor Body)
問題
你在各個子類中擁有一些構(gòu)造函數(shù),它們的本體幾乎完全一致。
class Manager extends Employee {public Manager(String name, String id, int grade) {this.name = name;this.id = id;this.grade = grade;}//... } 復(fù)制代碼解決
在超類中新建一個構(gòu)造函數(shù),并在子類構(gòu)造函數(shù)中調(diào)用它。
class Manager extends Employee {public Manager(String name, String id, int grade) {super(name, id);this.grade = grade;}//... } 復(fù)制代碼塑造模板函數(shù)(Form Template Method)
問題
你有一些子類,其中相應(yīng)的某些函數(shù)以相同的順序執(zhí)行類似的操作,但各個操作的細節(jié)上有所不同。
解決
將這些操作分別放進獨立函數(shù)中,并保持它們都有相同的簽名,于是原函數(shù)也就變得相同了。然后將原函數(shù)上移至超類。
注:這里只提到具體做法,建議了解一下模板方法設(shè)計模式。
替換算法(Substitute Algorithm)
問題
你想要把某個算法替換為另一個更清晰的算法。
String foundPerson(String[] people){for (int i = 0; i < people.length; i++) {if (people[i].equals("Don")){return "Don";}if (people[i].equals("John")){return "John";}if (people[i].equals("Kent")){return "Kent";}}return ""; } 復(fù)制代碼解決
將函數(shù)本體替換為另一個算法。
String foundPerson(String[] people){List candidates =Arrays.asList(new String[] {"Don", "John", "Kent"});for (int i=0; i < people.length; i++) {if (candidates.contains(people[i])) {return people[i];}}return ""; } 復(fù)制代碼提煉超類(Extract Superclass)
問題
兩個類有相似特性。
解決
為這兩個類建立一個超類,將相同特性移至超類。
提煉類(Extract Class)
問題
某個類做了不止一件事。
解決
建立一個新類,將相關(guān)的字段和函數(shù)從舊類搬移到新類。
合并條件表達式(Consolidate Conditional Expression)
問題
你有一系列條件分支,都得到相同結(jié)果。
double disabilityAmount() {if (seniority < 2) {return 0;}if (monthsDisabled > 12) {return 0;}if (isPartTime) {return 0;}// compute the disability amount//... } 復(fù)制代碼解決
將這些條件分支合并為一個條件,并將這個條件提煉為一個獨立函數(shù)。
double disabilityAmount() {if (isNotEligableForDisability()) {return 0;}// compute the disability amount//... } 復(fù)制代碼合并重復(fù)的條件片段(Consolidate Duplicate Conditional Fragments)
問題
在條件表達式的每個分支上有著相同的一段代碼。
if (isSpecialDeal()) {total = price * 0.95;send(); } else {total = price * 0.98;send(); } 復(fù)制代碼解決
將這段重復(fù)代碼搬移到條件表達式之外。
if (isSpecialDeal()) {total = price * 0.95; } else {total = price * 0.98; } send(); 復(fù)制代碼擴展閱讀
- 代碼的壞味道和重構(gòu)
- 代碼壞味道之代碼臃腫
- 代碼壞味道之濫用面向?qū)ο?/li>
- 代碼壞味道之變革的障礙
- 代碼壞味道之非必要的
- 代碼壞味道之耦合
參考資料
- 重構(gòu)——改善既有代碼的設(shè)計 - by Martin Fowler
- https://sourcemaking.com/refactoring
轉(zhuǎn)載于:https://juejin.im/post/5c812403f265da2d971120a6
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的代码坏味道之非必要的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一文了解Kubernetes的前世今生
- 下一篇: S/4HANA生产订单增强WORKORD