CodeGym /Java Blog /Toto sisi /什麼是反模式?讓我們看一些例子(第 2 部分)
John Squirrels
等級 41
San Francisco

什麼是反模式?讓我們看一些例子(第 2 部分)

在 Toto sisi 群組發布
什麼是反模式?讓我們看一些例子(第 1 部分) 今天我們繼續回顧最流行的反模式。如果你錯過了第一部分,就在這裡什麼是反模式? 讓我們看一些例子(第 2 部分)- 1因此,設計模式是最佳實踐。換句話說,它們是解決特定問題的經過時間考驗的好方法的例子。反過來,反模式是它們的對立面,從某種意義上說,它們是解決各種問題時的陷阱或錯誤的模式(邪惡模式)。讓我們繼續下一個軟件開發反模式。

8.金鎚

鎚子是一種反模式,其定義是相信特定的解決方案是普遍適用的。例子:
  1. 在遇到問題並找到完美解決方案的模式後,程序員會嘗試將這種模式無處不在,將其應用到當前和所有未來的項目中,而不是為特定情況尋找合適的解決方案。

  2. 一些開發人員曾經為特定情況創建了自己的緩存變體(因為沒有其他合適的)。後來,在下一個不涉及特殊緩存邏輯的項目中,他們再次使用他們的變體,而不是使用現成的庫(例如,Ehcache)。結果是一堆錯誤和不兼容,以及大量的時間浪費和焦躁不安。

    任何人都會愛上這種反模式。如果您是初學者,您可能不了解設計模式。這可能會導致您嘗試以您掌握的一種方式解決所有問題。如果我們談論的是專業人士,那麼我們稱之為專業變形或書呆子觀點。您有自己喜歡的設計模式,您可以使用自己喜歡的而不是正確的設計模式,假設過去的良好契合可以保證將來產生相同的結果。

    這個陷阱會產生非常可悲的結果——從糟糕的、不穩定的、難以維護的實施到項目的徹底失敗。正如沒有包治百病的藥丸一樣,也沒有一種適用於所有場合的設計模式。

9.過早的優化

過早的優化是一種反模式,其名稱不言自明。
“程序員花費大量時間思考和擔心代碼中的非關鍵位置並嘗試對其進行優化,這只會對後續調試和支持產生負面影響。我們通常應該在 97% 的情況下忘記優化。此外,過早的優化是萬惡之源。也就是說,我們必須全力關注剩下的 3%。” — 唐納德·克努特
例如,過早地向數據庫添加索引。為什麼那麼糟糕?好吧,糟糕的是,索引存儲為二叉樹。因此,每次添加和刪除新值時,都會重新計算樹,這會消耗資源和時間。所以,只有在急需的時候(數據量大,查詢時間過長)才加索引,而且只對最重要的字段(查詢次數最多的字段)加索引。

10.意大利麵條代碼

意大利麵條代碼是一種反模式,由結構不良、混亂且難以理解的代碼定義,包含各種分支,例如包裝異常、條件和循環。以前,goto 運算符是這種反模式的主要盟友。Goto 語句實際上不再被使用,它愉快地消除了一些相關的困難和問題。

public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
看起來很糟糕,不是嗎?不幸的是,這是最常見的反模式 :( 即使是編寫此類代碼的人將來也無法理解它。其他看到代碼的開發人員會想,“好吧,如果它有效,那麼好吧 -最好不要去碰它”。往往,一個方法一開始很簡單,很透明,但是隨著新的需求的加入,這個方法逐漸背負著越來越多的條件語句,變成這樣一個龐然大物。如果這樣的方法出現,你需要重構它要么完全重構它,要么至少重構它最混亂的部分。通常,在安排項目時,會分配時間用於重構,例如,30% 的 sprint 時間用於重構和測試。當然,這是假設沒有任何匆忙(但什麼時候會發生)。在這裡

11. 魔法數字

幻數是一種反模式,在這種模式中,程序中使用了各種常量,但沒有對它們的目的或含義進行任何解釋。也就是說,它們通常命名不當,或者在極端情況下,沒有註釋解釋註釋是什麼或為什麼。就像意大利麵條代碼一樣,這是最常見的反模式之一。沒有編寫代碼的人可能知道也可能不知道幻數或它們的工作原理(及時,作者本人將無法解釋它們)。結果,更改或刪除數字會導致代碼神奇地停止一起工作。例如,3673. 為了對抗這種反模式,我建議進行代碼審查。您的代碼需要由未參與代碼相關部分的開發人員查看。他們的眼睛會很新鮮,他們會有疑問:這是什麼,你為什麼要那樣做?當然,您需要使用解釋性名稱或發表評論。

12.複製粘貼編程

複製粘貼編程是一種反模式,在這種模式下,不加思索地複制和粘貼其他人的代碼,可能會導致意想不到的副作用。例如,複製和粘貼我們不完全理解的數學計算或複雜算法的方法。它可能適用於我們的特定情況,但在某些其他情況下可能會導致麻煩。假設我需要一種方法來確定數組中的最大數。在網上翻來覆去,找到了這個解決方案:

public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
我們得到一個包含數字 3、6、1、4 和 2 的數組,該方法返回 6。太好了,讓我們保留它!但是後來我們得到一個由2.5、-7、2、3組成的數組,那麼我們的結果就是-7。而這個結果是不行的。這裡的問題是 Math.abs() 返回絕對值。忽視這一點會導致災難,但僅限於某些情況。如果不深入了解解決方案,很多情況下是無法驗證的。複製的代碼也可能超出應用程序的內部結構,無論是在風格上還是在更基本的架構層面上。這樣的代碼將更難閱讀和維護。當然,我們不能忘記直接複製別人的代碼是一種特殊的剽竊行為。

13. 重新發明輪子

重新發明輪子是一種反模式,有時也稱為重新發明方輪. 本質上,這個模板與上面考慮的複制粘貼反模式相反。在這種反模式中,開發人員針對已經存在解決方案的問題實施他或她自己的解決方案。有時這些現有的解決方案比程序員發明的要好。大多數情況下,這只會導致時間的浪費和生產力的降低:程序員可能根本找不到解決方案,或者可能找到的解決方案遠非最佳。也就是說,我們不能排除創建獨立解決方案的可能性,因為這樣做是複制粘貼編程的直接途徑。程序員應該以出現的特定編程任務為指導,以便勝任地解決它們,無論是使用現成的解決方案還是創建自定義解決方案。常常,使用這種反模式的原因很簡單。結果是對(搜索)現成解決方案的淺層分析。 重新發明方輪是所考慮的反模式產生負面結果的情況。也就是說,該項目需要一個自定義解決方案,開發人員創建了它,但很糟糕。同時,一個好的選擇已經存在並且其他人正在成功地使用它。底線:大量的時間被浪費了。首先,我們創造了一些不起作用的東西。然後我們嘗試重構它,最後我們用已經存在的東西替換它。一個例子是在已經存在大量實現的情況下實現您自己的自定義緩存。無論你作為一名程序員多麼有才華,你都應該記住,重新發明一個方輪至少是在浪費時間。而且,如您所知,時間是最寶貴的資源。

14.溜溜球問題

溜溜球問題是一種反模式,其中應用程序的結構由於過度碎片化而過於復雜(例如,過度細分的繼承鏈)。當您需要了解繼承層次結構又長又復雜的程序時,就會出現“溜溜球問題”,從而創建深度嵌套的方法調用。因此,程序員需要在許多不同的類和方法之間導航,以便檢查程序的行為。這個反模式的名字來源於玩具的名字。作為例子,讓我們看看下面的繼承鏈:我們有一個 Technology 接口:

public interface Technology {
   void turnOn();
}
Transport接口繼承了它:

public interface Transport extends Technology {
   boolean fillUp();
}
然後我們有另一個接口,GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
從那裡,我們派生了一個抽象的 Car 類:

public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
接下來是抽象的 Volkswagen 類:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
最後,一個特定的模型:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
這個鏈條迫使我們尋找以下問題的答案:
  1. 有幾種方法VolkswagenAmarok

  2. 為了實現最大抽象,應該插入什麼類型而不是問號:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
這類問題很難快速回答——需要我們去觀察和調查,而且很容易混淆。如果層次結構更大、更長、更複雜,並且有各種重載和覆蓋怎麼辦?由於過度碎片化,我們本來的結構會變得模糊。最好的解決辦法是減少不必要的分裂。在我們的例子中,我們將離開技術 → 汽車 → VolkswagenAmarok。

15.偶然的複雜性

不必要的複雜性是一種反模式,其中將不必要的複雜性引入到解決方案中。
“任何傻瓜都可以編寫計算機可以理解的代碼。優秀的程序員可以編寫人類可以理解的代碼。” — 馬丁·福勒
那麼什麼是複雜性?它可以定義為程序中執行每個操作的難易程度。通常,複雜性可以分為兩種類型。第一種複雜性是系統具有的功能數量。它只能通過一種方式減少——通過刪除一些功能。需要監測現有方法。如果一個方法不再使用或仍在使用但沒有帶來任何價值,則應將其刪除。更重要的是,您需要評估應用程序中所有方法的使用方式,以便了解哪些投資是值得的(大量代碼重用)以及您可以拒絕的地方。第二種複雜性是不必要的複雜性。它只能通過專業方法治愈。而不是做一些“酷”的事情 (年輕的開發人員並不是唯一易患這種疾病的人),你需要考慮如何盡可能簡單地做到這一點,因為最好的解決方案總是簡單的。例如,假設我們有一些小的相關表,其中包含一些實體的描述,例如用戶: 什麼是反模式? 讓我們看一些例子(第 2 部分)- 3因此,我們有用戶的 ID、進行描述的語言的 ID 以及描述本身。同樣,我們有汽車、文件、計劃和客戶表的輔助描述符。那麼將新值插入此類表會是什麼樣子呢?

public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
因此,我們有這個枚舉:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
一切看起來都很簡單很好……但是其他的方法呢?事實上,它們也會有一堆switch語句和一堆幾乎相同的數據庫查詢,這反過來又會使我們的類變得非常複雜和膨脹。如何讓這一切變得更容易?讓我們稍微升級一下枚舉:

@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
現在每種類型都有其表的原始字段的名稱。因此,創建描述的方法變為:

private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
方便、簡單、緊湊,您不覺得嗎?一個好的開發人員的標誌甚至不是他或她使用模式的頻率,而是他或她避免反模式的頻率。無知是最大的敵人,因為你需要通過視覺了解你的敵人。好吧,這就是我今天的全部內容。謝謝大家!:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION