設計模式(07)——設計原則(2)

KISS原則

Keep It Simple and Stupid 這個原則聽起來比較簡單,重點是理解什麼樣的程式碼是簡單的,程式碼行數少就是簡單的程式碼嗎???還是說當程式的邏輯十分複雜不容易理解時就是一個複雜的程式碼呢???

下面就讓我們來看看怎麼樣判斷簡單。

程式碼行數

程式碼就不展示了,其實懂的都懂,程式碼行數是一定不能作為判斷依據的,因為很多程式碼行數少的程式碼,其實是採用了一些高級語法特性的。如正則表達式、位運算符等。

如果盲目的採用這些特性,會影響程式碼的可讀性,因為這種運演算法為了減少程式碼行數,會採用很多方式來規定一些特性,這些特性有時會不好理解。例如用位運算符來代替算數運算,這樣雖然會提高程式碼性能和減少行數,但也不是 KISS 原則下的指導產物,其違反了程式碼的可讀性。

邏輯複雜

那程式碼的邏輯複雜可以作為判斷依據嗎?其實也是懂的都懂,程式碼也不展示了(因為太複雜了,所以不展示了-v-),為什麼邏輯複雜不能作為判斷依據呢???

因為有些程式碼他就是邏輯複雜的,如果不信的話,直接打開演算法相關實現,找到一個看不懂的,那麼其就是我們的目標了。

因此如果沒辦法,在特定場景下,只能採用該演算法的實現,那麼使用這種複雜邏輯就不是違反 KISS 原則了,因為沒辦法。

場景

綜上,我們該如何寫出符合 KISS 原則的程式碼呢,具體來說,還是需要 結合業務發展和技術團隊能力。

在實際場景下,我們盡量做到以下幾點就可以了。

  • 不使用同事可能不懂的語法和技術,例如 lambda、正則等(不過這些還好,例如 lambda 雖然有些時候部分地方還不讓使用,但其實其早已不是一個新技術了,還不懂的話,這種情況可以考慮換個地方或者進行下部門培訓了)
  • 不要重複造輪子,因為自己造的輪子在一定程度上肯定沒有網上大家都用的好,比較容易出現問題,而這些問題可能在對應的輪子上已經被解決來(但自己學習的時候,該造還是要造)
  • 不要過度優化,炫技,例如為了提高性能和減少程式碼行數,使用大量的位運算符、複雜的條件表達式等,這些都是給機器看的,一般我們都不需要在這個性能上過分的優化,反而犧牲來程式碼的可讀性

YAGNI原則

定義

不要去設計當前用不到的功能,不要去寫當前用不到的程式碼。

該原則是作為一個指導思想來做的,其作用就是防止過度設計,但需要注意的是其是讓你不做,但需要有可能要做的意識,提前留好拓展點,這樣如果要做的時候,也可以快速跟上。
**

後續會有一篇文章,來專門講解如何在軟體設計中,防止過度設計,但對對應該優化,該留好優化點如何實現。

DRY原則

定義

英語解釋為:Dont repeat yourself,可以理解為不要寫重複的程式碼,要做好程式碼的可復用性。但該規則跟 KISS 原則一樣,聽起來可能比較簡單,但是在實際使用中,卻要注重的一個原則。

因為在該原則中,有一個很關鍵的點,什麼樣的程式碼是重複的程式碼,只是簡單的程式碼一樣就是違反該原則了嗎?下面就讓我們來根據以下幾種場景來判斷什麼是重複的程式碼。

實現邏輯重複

 public boolean validUsername(String username) {
        if (StrUtil.isEmpty(username)) {
            return false;
        }
        if (username.contains("77777")) {
            return false;
        }
        return true;
    }

    public boolean validNickname(String nickname) {
        if (StrUtil.isEmpty(nickname)) {
            return false;
        }
        if (nickname.contains("77777")) {
            return false;
        }
        return true;
    }

我們來看一下,這一段程式碼是重複的嗎?

明顯能看出,兩個方法的執行邏輯是一模一樣的,那麼我們可以說其是重複的,而將其合為一個方法嗎?

答案是不行的,首先如果合成一個就違反了單一職責原則,一個方法做了多件事,此外,現在兩個參數的校驗邏輯是一樣的,因此看著重複,後面如果 nickname 的校驗邏輯改了呢?那就需要侵入式的修改程式碼,需要在所有使用該方法的程式碼中進行修改,依據 nickname 來特定的進行判斷,從而違反了開閉原則。

因此,實現邏輯重複,不能構成程式碼重複的條件。

功能語義重複

    public boolean checkPasswordByRegex(String password) {
        String regex = "123456";
        boolean matches = Pattern.matches(regex, password);
        return matches;
    }

    public boolean checkPasswordByStr(String password) {
        boolean matches = StrUtil.equals(password, "123456");
        return matches;
    }

我們來看一下,上述兩段程式碼是否構成重複?

答案是這兩段程式碼是重複的程式碼,雖然這兩段程式碼的實現邏輯不一致,程式碼編寫也不一致,但其仍然違反了 DRY 原則,因為這兩段程式碼所實現的點是一樣的,下面我們來看一下這樣重複的程式碼會帶來什麼隱患。

這樣的設計可能會導致以下問題,

  1. 當一個不知情的人看到這兩段程式碼完成的任務是一樣的,但採用的解決方式卻是不一樣的,則會想其的設計深意,以及不知道該用哪一個?
  2. 當函數的實現邏輯要改的時候,可能會導致修改了其中一個,而忘記修改另一個,這樣則會造成邏輯的不一致。

程式碼執行重複

    // 程式碼執行重複
    public boolean validContainsA(String temp) {
        if (StrUtil.isNotEmpty(temp) && temp.contains("A")) {
            return true;
        }
        return false;
    }

    public boolean validTemp(String temp) {
        if (StrUtil.isNotEmpty(temp) && temp.contains("A")) {
            return true;
        }
        return validContainsA(temp);
    }

上述程式碼因為舉的例子比較簡單,應該能很簡單的看出有一段程式碼被執行了兩次,那麼這樣的設計違反了 DRY 原則嗎?

我們認為其也是違反的,這裡放的只是一個簡單的邏輯判斷,所以看不出來什麼,如果這裡是放了一個耗時操作呢,例如讀取資料庫、文件等。那麼進行重複的執行,則會影響整個程式的對外性能。

總結

因此,判斷如何一個程式碼是否是重複的,需要結合具體場景來做判斷,在這裡,我們可以看到程式碼實現方式一樣不能作為判斷依據,因此其實現的語義是不同的,這就是功能語義的判斷範疇,而對於重複執行,我們在程式碼里需要做到盡量避免,因為如果重複執行費時操作,則會導致程式碼整體對外的性能下降。

迪米特原則

定義

迪米特原則是用來指導設計高內聚、低耦合程式碼的原則,因此我們先看一下什麼是高內聚、低耦合。

  • 高內聚:相近的功能放到一個類或模組中,不相近的功能不要放到一個類中,暗合了單一職責原則;
  • 低耦合:類和類之間的依賴關係清晰簡潔,即使有依賴,也要做到其中一個修改,依賴或被依賴的少修改。

看到想要完成的目標,現在再來看一下什麼是迪米特原則。

其概念是:每個類或模組只需要了解與他關係密切即緊密關聯的類或模組。再換句話說:

  • 不該有依賴關係的不要有依賴;
  • 有依賴關係,只依賴必要的介面。