意圖導向編程

1.1 基本思想

「意圖導向編程」,Programming by Intention,也稱目的導向編程/自頂向下編程.
其基本思想是:
每個問題均可以分解成一系列的功能性步驟,在寫代碼的過程當中,會按照順序有意識的去完成每個步驟;而意圖導向編程則是先假設每個步驟都有一個理想的方法來完成,而不關注每一個步驟的具體實現,在這種狀況下,須要關心的是每一個方法的輸入參數,返回值以及什麼樣的名字最符合它的含義算法

例如,建立一個服務,它接受一個業務交易,而後提交,具體的需求以下:編程

  • 交易信息開始於一串標準的ASCII字符串。
  • 這個信息字符串必須轉換成一個字符串的數組,數組存放的值是這次交易用到的領域語言(domain language)中所包含的詞彙元素(token)。
  • 每個詞彙元素必須標準化(第一個字母大寫,其他字母小寫,空格和非字符數字的符號都要刪掉)。
  • 超過150個詞彙元素的交易,應該採用不一樣於小型交易的方式(不一樣的算法)來提交,以提升效率。
  • 若是提交成功,API方法返回true,不然返回false。

基於「意圖導向編程」的思想,咱們假設有一個類,類中有一個API實現上面的服務:設計模式

public class Transaction{
    public Boolean commit(String command){
        Boolean result = true;
        String[] tokens = tokenize(command);
        normalizeTokens(tokens);
        if(isALargeTransaction(tokens)){
            result = processLargeTransaction(tokens);
        }else{
            result = processSmallTransaction(tokens);
        }

        return result;
    }
}

commit()方法是程序API,用於提供服務,而其餘方法(tokenize()、normalizeTokens()、isALargeTransaction()、processLargeTransaction()、processSmallTransaction())都不屬於這個對象API,僅僅是實現過程當中的功能性步驟,稱之爲「輔助方法」(helper methods),暫時能夠將他們視爲私有方法。數組

經過這樣的編碼方式,能夠將精力集中在如何分解最終目標,以及那些全局性的問題上。
而且這種實現方式,與直接把全部代碼寫到一個很長的方法裏相比並無增長工做量,不一樣點在於思考的方式和編碼的順序(先理清總體流程,再深刻細節)。dom

1.2 優勢

若是遵循意圖導向編程,那麼代碼將會:函數

  • 更加內聚(職責單一)。
  • 更加可讀和清晰
  • 更易於調試
  • 更易於重構和優化
  • 更易於單元測試
  • 更易於維護
  • 建立的方法更容易從一個類移動到另外一個類
  • 更容易應用設計模式

1.2.1 方法的內聚性

代碼的質量標準之一就是內聚性。
以類爲例,每一個類都應該根據職責來定義,而且應該只有一個職責。類內部包括方法、狀態以及與其餘對象之間的關係,若是各個方面都緊密相關,而且圍繞着這個類的惟一職責,則說這個類的內聚性很強。單元測試

若是一個方法只實現總體職責中一個單獨的功能點,則說這個方法的內聚性很強。測試

人的思惟方式是單線程的,當進行「多任務」的時候,其實是在多個任務之間快速切換而已,人們仍舊習慣於一次只思考一件事情。意圖導向編程正是利用這一事實,用思惟鏈條單一性的特定去建立一樣具有單一性的內聚方法。優化

1.2.2 可讀性和表達性

經過閱讀最初的實例代碼:編碼

public class Transaction{
    public Boolean commit(String command){
        Boolean result = true;
        String[] tokens = tokenize(command);
        normalizeTokens(tokens);
        if(isALargeTransaction(tokens)){
            result = processLargeTransaction(tokens);
        }else{
            result = processSmallTransaction(tokens);
        }

        return result;
    }
}

能夠發現該服務的實現流程是:
獲取到一個指令,而後對指令進行分詞,再把分詞後獲得的指令標準化,判斷須要進行交易處理的類型,根據判斷結果來決定進行大型事務仍是小型事務的處理,最後返回結果。

由於上面的代碼只涉及到「作什麼」,而不是具體的「如何作」,這種狀況下,不須要註釋也能讀懂代碼的基本邏輯,這得益於規範的命名和步驟的清晰界定。

考慮下面的實現方式:

public class Transaction{
    public Boolean commit(String command){
        Boolean result = true;

        // tokenize the string
        some code here
        some more code here
        even some more code here that sets tokens

        // normalize the tokens
        some code here that normalize tokens
        some more code here that normalize tokens
        even some more code here that normalize tokens

        // see if you have a large transaction
        code that determines if you have a large transaction
        set lt = true if you do
            if(lt){
                // process large transaction
                some code here to process large transaction
                some more code here to process large transaction
            }else{
                // process small transaction
                some code here to process small transaction
                some more code here to process small transaction
            }

        return result;
    }
}

上面的實現方式是將全部邏輯寫在一個大方法中,而且加了詳盡的註釋,但與意圖導向編程的實現方式相比,註釋顯得很沒有必要,而且代碼太多,給人的心理無形中形成一種壓力。

1.2.3 調試

在程序的代碼錯誤修復過程當中,尋找錯誤所在纔是最花時間的。在遵循意圖導向編程時,因爲一個方法只作一件事,這個時候,若是出現錯誤,則可試試下面的辦法:

  • 通讀一遍整個方法,看看全部事情是怎麼運做的
  • 對沒法正常工做的部分,檢查輔助代碼的細節有什麼問題

相比於費力的查閱一大段複雜的代碼,這種調試方法發現代碼錯誤的速度要快不少。

1.2.4 重構和加強

重構系統:保持系統行爲不變的狀況下,更改它的結構。
加強系統:增長或修改系統的行爲以符合新的需求。

重構一般認爲是「清理」系統中寫得糟糕的代碼,重構的一個基本實現方式是:把一部分代碼從一個巨大的方法中抽取出來,放到一個屬於它本身的新方法中,而在原來代碼中的那個位置直接調用這個新方法。

因爲原來方法的一部分臨時變量也須要遷移到新方法中,因此須要多個步驟才能完成一個函數的提煉。

若是採用意圖導向編程,一開始就是輔助方法了,只須要把公用的輔助方法遷移到其餘類便可。這樣的重構是很快的(複製-粘貼)。

當系統實現後,有新需求加進來了,如:與第三方程序交互時,因爲第三方程序的緣由,再也不支持某些舊版詞彙,這個時候須要更新一個詞彙元素,如:

public class Transaction{
    public Boolean commit(String command){
        Boolean result = true;
        String[] tokens = tokenize(command);
        normalizeTokens(tokens);
    updateTokens(tokens);
        if(isALargeTransaction(tokens)){
            result = processLargeTransaction(tokens);
        }else{
            result = processSmallTransaction(tokens);
        }

        return result;
    }
}

有新需求加進來的時候,只須要在API方法的實現流程中增長updateTokens()方法,其餘都不須要修改到,把影響降到了最低。

若是修改了標準化的算法,則更改normalizeTokens()方法便可,其餘都無需改動。在修改的過程當中,代碼能很快定位。

1.2.5 單元測試

設計的基本建議:使用服務的客戶端,在設計時應該遵守的是服務的接口定義,而不是服務的具體實現。
在上面的實現中,輔助方法被定義成了私有方法,是爲了避免想與外部對象發生關聯,但這種狀況下就不利於方法的單元測試。
咱們只能對commit()方法進行單元測試,即測試服務的總體行爲。此時測試狀況比較複雜,會有不少種因素致使測試失敗。
能夠有以下解決辦法:

  • 若是輔助方法只是實現單個服務的一部分,則不必單獨測試輔助方法,測試這個服務流程便可。
  • 若是某些輔助方法是能被其餘服務使用到的,則須要將該輔助方法單獨到其餘的類中,而且定義成公有的方法,則對原來輔助方法的調用就變成了對新類方法的調用,而且新類的公有方法是能進行單元測試的。

1.2.6 可遷移的方法

爲了提升類的內聚性,須要把這個類不該該有的方法遷移到其餘類中,這樣可讓這個類所關注的東西減小。

意圖導向編程建立的方法只完成一個功能,這樣避免了遷移方法是常常遇到的問題:包含不能遷走的部分。
當一個方法只作一件事時,要麼所有遷移,要麼不遷移。

方法遷移難,還可能因爲它直接關聯到了類中的狀態變量,在使用意圖導向編程時,習慣於將參數傳遞到輔助方法,而後獲取一個返回結果,而不是讓方法直接使用對象的狀態。

1.2.7 易於修改和擴展

從以前的重構和加強可當作,當增長需求時,只須要在流程中增長對應的輔助方法;
當須要修改需求時,只須要修改對應的輔助方法。這種修改和擴展容易定位而且不影響其餘代碼。

1.2.8 在代碼中發現模式

上面的例子中,若是有兩個不一樣的交易類型,流程步驟同樣(分詞、標準化、更新、處理),但每一步的實現方式不同。
使用意圖導向編程時,處理每一個輔助方法具體實現不同,commit()方法是同樣的,這個時候,能夠很容易的應用模板方法模式

相關文章
相關標籤/搜索