意圖導(dǎo)向編程

1.1 基本思想

“意圖導(dǎo)向編程”,Programming by Intention,也稱目的導(dǎo)向編程/自頂向下編程.
其基本思想是:
每一個(gè)問題都可以分解成一系列的功能性步驟,在寫代碼的過程中,會(huì)按照順序有意識(shí)的去完成每一個(gè)步驟;而意圖導(dǎo)向編程則是先假設(shè)每一個(gè)步驟都有一個(gè)理想的方法來完成,而不關(guān)注每個(gè)步驟的具體實(shí)現(xiàn),在這種情況下,需要關(guān)心的是每個(gè)方法的輸入?yún)?shù),返回值以及什么樣的名字最符合它的含義

例如,創(chuàng)建一個(gè)服務(wù),它接受一個(gè)業(yè)務(wù)交易,然后提交,具體的需求如下:

  • 交易信息開始于一串標(biāo)準(zhǔn)的ASCII字符串。
  • 這個(gè)信息字符串必須轉(zhuǎn)換成一個(gè)字符串的數(shù)組,數(shù)組存放的值是此次交易用到的領(lǐng)域語言(domain language)中所包含的詞匯元素(token)。
  • 每一個(gè)詞匯元素必須標(biāo)準(zhǔn)化(第一個(gè)字母大寫,其余字母小寫,空格和非字符數(shù)字的符號(hào)都要?jiǎng)h掉)。
  • 超過150個(gè)詞匯元素的交易,應(yīng)該采用不同于小型交易的方式(不同的算法)來提交,以提高效率。
  • 如果提交成功,API方法返回true,否則返回false。

基于“意圖導(dǎo)向編程”的思想,我們假設(shè)有一個(gè)類,類中有一個(gè)API實(shí)現(xiàn)上面的服務(wù):

    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,用于提供服務(wù),而其他方法(tokenize()、normalizeTokens()、isALargeTransaction()、processLargeTransaction()、processSmallTransaction())都不屬于這個(gè)對(duì)象API,僅僅是實(shí)現(xiàn)過程中的功能性步驟,稱之為“輔助方法”(helper methods),暫時(shí)可以將他們視為私有方法。

通過這樣的編碼方式,可以將精力集中在如何分解最終目標(biāo),以及那些全局性的問題上。
并且這種實(shí)現(xiàn)方式,與直接把所有代碼寫到一個(gè)很長(zhǎng)的方法里相比并沒有增加工作量,不同點(diǎn)在于思考的方式和編碼的順序(先理清整體流程,再深入細(xì)節(jié))。

1.2 優(yōu)點(diǎn)

如果遵循意圖導(dǎo)向編程,那么代碼將會(huì):

  • 更加內(nèi)聚(職責(zé)單一)。
  • 更加可讀和清晰
  • 更易于調(diào)試
  • 更易于重構(gòu)和優(yōu)化
  • 更易于單元測(cè)試
  • 更易于維護(hù)
  • 創(chuàng)建的方法更容易從一個(gè)類移動(dòng)到另一個(gè)類
  • 更容易應(yīng)用設(shè)計(jì)模式

1.2.1 方法的內(nèi)聚性

代碼的質(zhì)量標(biāo)準(zhǔn)之一就是內(nèi)聚性。
以類為例,每個(gè)類都應(yīng)該根據(jù)職責(zé)來定義,并且應(yīng)該只有一個(gè)職責(zé)。類內(nèi)部包括方法、狀態(tài)以及與其他對(duì)象之間的關(guān)系,如果各個(gè)方面都緊密相關(guān),并且圍繞著這個(gè)類的唯一職責(zé),則說這個(gè)類的內(nèi)聚性很強(qiáng)。

如果一個(gè)方法只實(shí)現(xiàn)整體職責(zé)中一個(gè)單獨(dú)的功能點(diǎn),則說這個(gè)方法的內(nèi)聚性很強(qiáng)。

人的思維方式是單線程的,當(dāng)進(jìn)行“多任務(wù)”的時(shí)候,實(shí)際上是在多個(gè)任務(wù)之間快速切換而已,人們?nèi)耘f習(xí)慣于一次只思考一件事情。意圖導(dǎo)向編程正是利用這一事實(shí),用思維鏈條單一性的特定去創(chuàng)建同樣具備單一性的內(nèi)聚方法。

1.2.2 可讀性和表達(dá)性

通過閱讀最初的實(shí)例代碼:

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;
    }
}

可以發(fā)現(xiàn)該服務(wù)的實(shí)現(xiàn)流程是:
獲取到一個(gè)指令,然后對(duì)指令進(jìn)行分詞,再把分詞后得到的指令標(biāo)準(zhǔn)化,判斷需要進(jìn)行交易處理的類型,根據(jù)判斷結(jié)果來決定進(jìn)行大型事務(wù)還是小型事務(wù)的處理,最后返回結(jié)果。

因?yàn)樯厦娴拇a只涉及到“做什么”,而不是具體的“如何做”,這種情況下,不需要注釋也能讀懂代碼的基本邏輯,這得益于規(guī)范的命名和步驟的清晰界定。

考慮下面的實(shí)現(xiàn)方式:

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;
    }
}

上面的實(shí)現(xiàn)方式是將所有邏輯寫在一個(gè)大方法中,并且加了詳盡的注釋,但與意圖導(dǎo)向編程的實(shí)現(xiàn)方式相比,注釋顯得很沒有必要,并且代碼太多,給人的心理無形中造成一種壓力。

1.2.3 調(diào)試

在程序的代碼錯(cuò)誤修復(fù)過程中,尋找錯(cuò)誤所在才是最花時(shí)間的。在遵循意圖導(dǎo)向編程時(shí),由于一個(gè)方法只做一件事,這個(gè)時(shí)候,如果出現(xiàn)錯(cuò)誤,則可試試下面的辦法:

  • 通讀一遍整個(gè)方法,看看所有事情是怎么運(yùn)作的
  • 對(duì)無法正常工作的部分,檢查輔助代碼的細(xì)節(jié)有什么問題

相比于費(fèi)力的查閱一大段復(fù)雜的代碼,這種調(diào)試方法發(fā)現(xiàn)代碼錯(cuò)誤的速度要快很多。

1.2.4 重構(gòu)和增強(qiáng)

重構(gòu)系統(tǒng):保持系統(tǒng)行為不變的情況下,更改它的結(jié)構(gòu)。
增強(qiáng)系統(tǒng):增加或修改系統(tǒng)的行為以符合新的需求。

重構(gòu)通常認(rèn)為是“清理”系統(tǒng)中寫得糟糕的代碼,重構(gòu)的一個(gè)基本實(shí)現(xiàn)方式是:把一部分代碼從一個(gè)巨大的方法中抽取出來,放到一個(gè)屬于它自己的新方法中,而在原來代碼中的那個(gè)位置直接調(diào)用這個(gè)新方法。

由于原來方法的一部分臨時(shí)變量也需要遷移到新方法中,所以需要多個(gè)步驟才能完成一個(gè)函數(shù)的提煉。

如果采用意圖導(dǎo)向編程,一開始就是輔助方法了,只需要把公用的輔助方法遷移到其他類即可。這樣的重構(gòu)是很快的(復(fù)制-粘貼)。

當(dāng)系統(tǒng)實(shí)現(xiàn)后,有新需求加進(jìn)來了,如:與第三方程序交互時(shí),由于第三方程序的原因,不再支持某些舊版詞匯,這個(gè)時(shí)候需要更新一個(gè)詞匯元素,如:

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;
    }
}

有新需求加進(jìn)來的時(shí)候,只需要在API方法的實(shí)現(xiàn)流程中增加updateTokens()方法,其他都不需要修改到,把影響降到了最低。

如果修改了標(biāo)準(zhǔn)化的算法,則更改normalizeTokens()方法即可,其他都無需改動(dòng)。在修改的過程中,代碼能很快定位。

1.2.5 單元測(cè)試

設(shè)計(jì)的基本建議:使用服務(wù)的客戶端,在設(shè)計(jì)時(shí)應(yīng)該遵照的是服務(wù)的接口定義,而不是服務(wù)的具體實(shí)現(xiàn)。
在上面的實(shí)現(xiàn)中,輔助方法被定義成了私有方法,是為了不想與外部對(duì)象發(fā)生關(guān)聯(lián),但這種情況下就不利于方法的單元測(cè)試。
我們只能對(duì)commit()方法進(jìn)行單元測(cè)試,即測(cè)試服務(wù)的整體行為。此時(shí)測(cè)試情況比較復(fù)雜,會(huì)有很多種因素導(dǎo)致測(cè)試失敗。
可以有如下解決辦法:

  • 如果輔助方法只是實(shí)現(xiàn)單個(gè)服務(wù)的一部分,則沒必要單獨(dú)測(cè)試輔助方法,測(cè)試這個(gè)服務(wù)流程即可。
  • 如果某些輔助方法是能被其他服務(wù)使用到的,則需要將該輔助方法單獨(dú)到其他的類中,并且定義成公有的方法,則對(duì)原來輔助方法的調(diào)用就變成了對(duì)新類方法的調(diào)用,并且新類的公有方法是能進(jìn)行單元測(cè)試的。

1.2.6 可遷移的方法

為了提高類的內(nèi)聚性,需要把這個(gè)類不應(yīng)該有的方法遷移到其他類中,這樣可以讓這個(gè)類所關(guān)注的東西減少。

意圖導(dǎo)向編程創(chuàng)建的方法只完成一個(gè)功能,這樣避免了遷移方法是經(jīng)常遇到的問題:包含不能遷走的部分。
當(dāng)一個(gè)方法只做一件事時(shí),要么全部遷移,要么不遷移。

方法遷移難,還可能由于它直接關(guān)聯(lián)到了類中的狀態(tài)變量,在使用意圖導(dǎo)向編程時(shí),習(xí)慣于將參數(shù)傳遞到輔助方法,然后獲取一個(gè)返回結(jié)果,而不是讓方法直接使用對(duì)象的狀態(tài)。

1.2.7 易于修改和擴(kuò)展

從之前的重構(gòu)和增強(qiáng)可看成,當(dāng)增加需求時(shí),只需要在流程中增加對(duì)應(yīng)的輔助方法;
當(dāng)需要修改需求時(shí),只需要修改對(duì)應(yīng)的輔助方法。這種修改和擴(kuò)展容易定位并且不影響其他代碼。

1.2.8 在代碼中發(fā)現(xiàn)模式

上面的例子中,如果有兩個(gè)不同的交易類型,流程步驟一樣(分詞、標(biāo)準(zhǔn)化、更新、處理),但每一步的實(shí)現(xiàn)方式不一樣。
使用意圖導(dǎo)向編程時(shí),處理每個(gè)輔助方法具體實(shí)現(xiàn)不一樣,commit()方法是一樣的,這個(gè)時(shí)候,可以很容易的應(yīng)用模板方法模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,048評(píng)論 25 709
  • * 部門 * 倉儲(chǔ)部 質(zhì)檢部 采購部 供應(yīng)商 A采購部發(fā)到貨入庫通知給倉儲(chǔ)部 B倉儲(chǔ)部接收到貨入庫通知 C倉儲(chǔ)部管...
    無謂石閱讀 1,412評(píng)論 0 1
  • 1 夜來了 我急忙找出紙和筆 要趕在 天完全黑下來之前 寫下這句:我愛你 2 愛你時(shí)一切美好 怨你時(shí)... 還是愛...
    王泊一閱讀 247評(píng)論 1 2
  • 不知道為啥顏色總覺得太淺,線條的把握也不行,總體感覺就一個(gè)字---難!
    小小小N閱讀 312評(píng)論 0 0
  • 騎著摩拜川流在西安的大街小巷,別有一番滋味 大雁塔
    pan123456閱讀 350評(píng)論 0 0

友情鏈接更多精彩內(nèi)容