亚洲免费av电影一区二区三区,日韩爱爱视频,51精品视频一区二区三区,91视频爱爱,日韩欧美在线播放视频,中文字幕少妇AV,亚洲电影中文字幕,久久久久亚洲av成人网址,久久综合视频网站,国产在线不卡免费播放

        ?

        通過(guò)代碼分離模型編寫(xiě)易維護(hù)易測(cè)試的代碼

        2021-10-15 12:48:28劉靜靜呂何新
        關(guān)鍵詞:單元測(cè)試調(diào)用開(kāi)發(fā)者

        劉靜靜 呂何新

        1(上海大學(xué)計(jì)算機(jī)工程與科學(xué)學(xué)院 上海 200444) 2(浙江樹(shù)人大學(xué)信息科技學(xué)院 浙江 杭州 310015)

        0 引 言

        隨著互聯(lián)網(wǎng)時(shí)代的發(fā)展,應(yīng)用軟件也百花齊放。消費(fèi)者對(duì)計(jì)算機(jī)應(yīng)用軟件的要求也越來(lái)越高。一款應(yīng)用從創(chuàng)新的想法到投入市場(chǎng)的時(shí)間及其后續(xù)對(duì)已有功能的改進(jìn)和新功能開(kāi)發(fā)的周期時(shí)間也越來(lái)越短,這樣才能讓科技公司抓住商機(jī),不斷滿(mǎn)足消費(fèi)者越來(lái)越高的需求。在這樣的挑戰(zhàn)下,計(jì)算機(jī)軟件的可維護(hù)性就凸顯出越來(lái)越重要的地位,因?yàn)橹挥锌删S護(hù)性好的應(yīng)用才能對(duì)各類(lèi)改變做出快速響應(yīng)并盡快投入市場(chǎng)。對(duì)于軟件的可維護(hù)性,有兩個(gè)非常重要的方面:是否容易修改和是否容易測(cè)試。測(cè)試尤其體現(xiàn)在是否容易做單元測(cè)試。為了寫(xiě)出易維護(hù)的代碼,計(jì)算機(jī)領(lǐng)域很多專(zhuān)家做了大量的研究和探索,并取得了非常多的成果。Erich等[1]提出了非常著名的設(shè)計(jì)模式的概念,該理念至今仍然沒(méi)有過(guò)時(shí),在現(xiàn)在的軟件開(kāi)發(fā)中仍然頻繁地被使用。為了讓現(xiàn)有的代碼變得更容易維護(hù)和測(cè)試,Martin[2]提出了重構(gòu)的概念,這是設(shè)計(jì)模式之后的又一里程碑。重構(gòu)現(xiàn)在仍然是開(kāi)發(fā)者們經(jīng)常提及和使用的技術(shù)和方法,國(guó)內(nèi)在重構(gòu)[3-5]和解依賴(lài)領(lǐng)域?qū)o態(tài)代碼依賴(lài)分析也做了大量研究[6]。Michael[7]對(duì)于修改現(xiàn)有的代碼給出了比重構(gòu)更加細(xì)致的方法,提出了多種有效修改已有代碼的技術(shù)和方法。Kent等[8]推出了成熟版的極限編程,在測(cè)試驅(qū)動(dòng)、持續(xù)集成、自動(dòng)化測(cè)試等多個(gè)方面給出了一整套用來(lái)開(kāi)發(fā)易維護(hù)應(yīng)用軟件的框架。對(duì)于每個(gè)類(lèi)如何編寫(xiě)、類(lèi)和類(lèi)之間如何協(xié)作,Kent[9]又給出了在實(shí)現(xiàn)細(xì)節(jié)層面的實(shí)現(xiàn)模式,為寫(xiě)出堅(jiān)實(shí)的代碼又貢獻(xiàn)了一份力量。在設(shè)計(jì)和實(shí)現(xiàn)大量理論之后,Martin[10]提出了整潔代碼的理念和技術(shù),并快速得到了大家的認(rèn)可,高原等[11]在此基礎(chǔ)上對(duì)代碼壞味道的處理順序給出了方案。Dustin等[12]在代碼的可讀性方面給出了各種實(shí)踐和建議,代碼的整潔和可讀性也成為開(kāi)發(fā)者密切關(guān)注的內(nèi)容。

        代碼的可測(cè)性方面,主要的成果來(lái)自于Kent。Kent設(shè)計(jì)并實(shí)現(xiàn)了JUnit,開(kāi)啟了單元測(cè)試的紀(jì)元,后續(xù)在此基礎(chǔ)上發(fā)展出了xUnit[13]系列。截至2019年9月8日,JUnit已迭代數(shù)十個(gè)版本,當(dāng)前最新的版本是5.5.2。單元測(cè)試的出現(xiàn)大幅提升了代碼的可維護(hù)性和質(zhì)量。Kent[14]提出了測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的理念,把單元測(cè)試的范疇從測(cè)試擴(kuò)展到了開(kāi)發(fā)和設(shè)計(jì)領(lǐng)域,為實(shí)現(xiàn)易維護(hù)易測(cè)試的代碼又向前邁進(jìn)了一步。

        1 問(wèn)題陳述

        現(xiàn)在已有大量的理論和方法來(lái)支持代碼的整潔和易維護(hù)性,但是目前開(kāi)發(fā)者仍然面臨代碼不易修改和不易測(cè)試的難題。仔細(xì)分析上面的理論和技術(shù)后發(fā)現(xiàn),已有理論對(duì)應(yīng)單個(gè)方法的修改和測(cè)試的支持有所不足,而開(kāi)發(fā)者編寫(xiě)和修改代碼的時(shí)候,大部分情況下都是直接面臨單個(gè)方法。對(duì)設(shè)計(jì)模式、重構(gòu)等技能的掌握要非常熟練的時(shí)候,開(kāi)發(fā)者才能輕松地將其應(yīng)用到單個(gè)方法之上。同時(shí),現(xiàn)有的理論和技術(shù)還存在第二個(gè)問(wèn)題,學(xué)習(xí)掌握的難度較大。這樣直接導(dǎo)致了一個(gè)現(xiàn)象,雖然這些技術(shù)非常知名和有用,但是現(xiàn)在對(duì)這些技能精通的開(kāi)發(fā)卻少之又少,進(jìn)而嚴(yán)重影響了其在提高代碼維護(hù)性方面的作用。

        鑒于上面存在的兩個(gè)問(wèn)題,本文在大量研究已有理論和技術(shù)的基礎(chǔ)上,提出了代碼分離模型,其包括隔離方法和隔離層的概念,并在研究和分析大量代碼實(shí)現(xiàn)場(chǎng)景之后,設(shè)計(jì)了代碼分離的一系列模式。

        隔離方法可以非常便捷地應(yīng)用于單個(gè)方法層面,同時(shí)理解和掌握的難度也大幅降低。代碼分離模式的提出可以幫助開(kāi)發(fā)者提前識(shí)別出常見(jiàn)的應(yīng)用場(chǎng)景,進(jìn)而提高該技術(shù)使用的可能性。

        2 代碼分離模型

        代碼易維護(hù)性之所以存在問(wèn)題,關(guān)鍵的一點(diǎn)是代碼不相關(guān)的部分混合在一起,也稱(chēng)為相互依賴(lài)。現(xiàn)有關(guān)于提高可維護(hù)性的核心理念也是解開(kāi)依賴(lài)。只要把不相關(guān)的部分分離開(kāi)來(lái),這樣每一部分就可以單獨(dú)進(jìn)行修改和測(cè)試,代碼的可維護(hù)性就會(huì)大幅提升。為了解決這一問(wèn)題,本文提出隔離方法如下。

        定義1數(shù)據(jù)隔離。把通過(guò)非基本類(lèi)型獲取數(shù)據(jù)的代碼部分提取并隔離起來(lái),將需要的數(shù)據(jù)從非基本類(lèi)型獲取后生成基本類(lèi)型的數(shù)據(jù),然后把原來(lái)方法對(duì)非基本類(lèi)型的依賴(lài)轉(zhuǎn)換為對(duì)基本類(lèi)型數(shù)據(jù)的依賴(lài),再使用該基本類(lèi)型數(shù)據(jù)來(lái)調(diào)用原來(lái)的方法。

        定義2行為隔離。把非業(yè)務(wù)行為的代碼提取并隔離起來(lái),將業(yè)務(wù)行為提取到一個(gè)單獨(dú)的方法中,在隔離起來(lái)的非業(yè)務(wù)行為方法里調(diào)用新提取的方法,并建立非業(yè)務(wù)行為和業(yè)務(wù)行為的單一連接點(diǎn)。

        在既沒(méi)有非基本類(lèi)型依賴(lài)也沒(méi)有非業(yè)務(wù)行為的場(chǎng)景下,業(yè)務(wù)邏輯還存在一種常見(jiàn)的耦合和依賴(lài),即流程與細(xì)節(jié)的緊密耦合。所以,第三種隔離方法為粗細(xì)隔離。粗的部分為流程和步驟,細(xì)的部分為詳細(xì)的邏輯內(nèi)容。

        定義3粗細(xì)隔離。把業(yè)務(wù)邏輯的流程和步驟隔離出來(lái),通過(guò)每一個(gè)步驟來(lái)調(diào)用其對(duì)應(yīng)的具體細(xì)節(jié)邏輯,進(jìn)而建立步驟和其對(duì)應(yīng)的具體業(yè)務(wù)邏輯的連接。引入面向接口編程[15],可以更方便地修改或者替換每一個(gè)步驟,此時(shí)為增強(qiáng)版粗細(xì)隔離模式。

        數(shù)據(jù)隔離和行為隔離方法的特點(diǎn)如下:

        1) 只包含隔離出來(lái)的非基本類(lèi)型或非業(yè)務(wù)行為。

        2) 包含對(duì)原有方法的調(diào)用。

        3) 沒(méi)有邏輯。

        4) 保留簽名。

        粗細(xì)隔離方法的特點(diǎn)如下:

        1) 只包含邏輯的步驟輪廓。

        2) 每一個(gè)步驟調(diào)用其對(duì)應(yīng)的業(yè)務(wù)邏輯。

        3) 沒(méi)有細(xì)節(jié)。

        4) 保留簽名。

        下文逐一講解三種隔離方法的詳細(xì)使用步驟。

        數(shù)據(jù)隔離方法的使用步驟如下:

        1) 找到需要隔離的非基本類(lèi)型。

        2) 將獲取數(shù)據(jù)的調(diào)用提取到一個(gè)新方法,即為隔離方法。

        3) 在隔離方法里從非基本類(lèi)型中獲取需要的基本類(lèi)型的數(shù)據(jù)。

        4) 根據(jù)需要為原來(lái)的方法添加參數(shù),將獲取數(shù)據(jù)的地方替換為參數(shù)引用。

        5) 在隔離方法里調(diào)用添加過(guò)參數(shù)的原來(lái)的方法,將獲取的基本數(shù)據(jù)類(lèi)型作為參數(shù)傳遞過(guò)去。

        行為隔離方法的使用步驟如下:

        1) 找出需要隔離的非業(yè)務(wù)邏輯行為。

        2) 將業(yè)務(wù)邏輯的行為放到一個(gè)新方法中。

        3) 將非業(yè)務(wù)邏輯的行為保留在原來(lái)的方法,形成隔離方法。

        4) 在隔離方法里調(diào)用新生成的只包含業(yè)務(wù)行為的方法。

        粗細(xì)隔離方法的使用步驟如下:

        1) 梳理業(yè)務(wù)邏輯的步驟。

        2) 將代碼整理并提取到每一個(gè)步驟對(duì)應(yīng)的方法中。

        3) 在隔離方法里調(diào)用每一個(gè)步驟對(duì)應(yīng)的方法。

        4) 如果為增強(qiáng)型粗細(xì)隔離方法,再為每個(gè)步驟創(chuàng)建一個(gè)接口及其現(xiàn)在對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)。

        假設(shè)方法1存在對(duì)方法2的調(diào)用,并且方法2中存在對(duì)非基本類(lèi)型的依賴(lài),對(duì)方法2使用隔離方法前后的對(duì)比如圖1所示。

        圖1 使用隔離方法前后對(duì)比

        如果代碼中存在對(duì)一類(lèi)非自己的API的大量依賴(lài),比如數(shù)據(jù)存取框架Hibernate[16]。這樣會(huì)有大量包含此類(lèi)API的隔離方法,后續(xù)維護(hù)也會(huì)成為難題。針對(duì)這種情況,本文提出隔離層的概念和方案來(lái)解決這一問(wèn)題。隔離層如圖2所示。

        圖2 隔離層

        定義4隔離層。把屬于一類(lèi)的隔離方法放到一起形成很薄的代碼層,便于以后對(duì)外部類(lèi)庫(kù)的修改和替換,并且代碼的改動(dòng)只發(fā)生在隔離層之中,業(yè)務(wù)邏輯對(duì)應(yīng)的代碼不受任何影響。

        當(dāng)需要替換成另一類(lèi)API時(shí),只需要按照接口再實(shí)現(xiàn)一遍方法1到方法N,然后在隔離層的隔離方法里面替換為新的方法1到方法N,原來(lái)對(duì)隔離層的調(diào)用不需要做任何修改。通過(guò)隔離層,把外部的類(lèi)庫(kù)從自己的代碼中分離出來(lái),這樣既實(shí)現(xiàn)了外部類(lèi)庫(kù)的方便替換,也實(shí)現(xiàn)了自己代碼的測(cè)試便捷性。

        在隔離方法和隔離層的基礎(chǔ)上,借鑒設(shè)計(jì)模式的理念,把常見(jiàn)的分離場(chǎng)景再歸為幾種代碼分離模式,就形成了本文的代碼分離模型,如圖3所示。

        圖3 代碼分離模型

        3 代碼分離模式

        有了隔離方法和隔離層之后,將其使用到軟件開(kāi)發(fā)的過(guò)程中,本文發(fā)現(xiàn)主要的應(yīng)用場(chǎng)景可以歸納為幾類(lèi),在此基礎(chǔ)上本文提出了代碼分離模式。通過(guò)代碼分離模式,開(kāi)發(fā)者可以在常用的場(chǎng)景下更加熟練和準(zhǔn)確地應(yīng)用代碼分離模型技術(shù),進(jìn)而提高編寫(xiě)出可維護(hù)易測(cè)試的代碼的可能性。下面將詳細(xì)描述每一個(gè)模式。在模式的描述中本文使用目前主流的開(kāi)發(fā)語(yǔ)言Java[17]。

        3.1 定義從細(xì)節(jié)中分離

        在編寫(xiě)代碼的過(guò)程中,我們需要?jiǎng)?chuàng)建大量對(duì)象,這些對(duì)象的直接創(chuàng)建造成了對(duì)象之間的強(qiáng)依賴(lài),而這樣的強(qiáng)依賴(lài)又帶來(lái)了維護(hù)和測(cè)試的難題??聪旅嬉欢未a。

        public String buildHtml() {

        StringBuilder html=new StringBuilder();

        html.append(" ");

        html.append(" ");

        html.append("

        隨機(jī)數(shù)為:"

        +new Random().nextInt()+"

        ");

        html.append(" ");

        html.append("");

        return html.toString();

        }

        這段代碼存在對(duì)隨機(jī)數(shù)Random的強(qiáng)依賴(lài),要替換時(shí)需要碰觸到業(yè)務(wù)邏輯對(duì)應(yīng)的代碼,要做單元測(cè)試時(shí)也發(fā)現(xiàn)因?yàn)殡S機(jī)數(shù)的原因不可測(cè)。這種場(chǎng)景下要應(yīng)用定義從細(xì)節(jié)中分離的模式。使用隔離方法把Random的定義從業(yè)務(wù)邏輯中分離出來(lái)并放到隔離方法中,然后在隔離方法中調(diào)用原來(lái)的buildHtml方法,并把原來(lái)Random的依賴(lài)改為對(duì)一個(gè)整數(shù)的依賴(lài)放到方法的參數(shù)中,把隔離方法命名為buildHtml()以保持方法簽名,進(jìn)而不影響對(duì)該方法調(diào)用的地方。修改后的代碼如下:

        //數(shù)據(jù)隔離方法

        public String buildHtml() {

        return buildHtml(new Random().nextInt());

        }

        public String buildHtml(Integer nextInt) {

        StringBuilder html=new StringBuilder();

        html.append(" ");

        html.append(" ");

        html.append("

        隨機(jī)數(shù)為:"+nextInt+"

        ");

        html.append(" ");

        html.append("");

        return html.toString();

        }

        現(xiàn)在,原來(lái)的buildHtml方法里面只對(duì)基本類(lèi)型數(shù)據(jù)存在依賴(lài),解除了對(duì)隨機(jī)數(shù)的依賴(lài),以后隨機(jī)數(shù)類(lèi)庫(kù)的改動(dòng)將不再影響到業(yè)務(wù)邏輯,同時(shí)現(xiàn)在的方法非常方便進(jìn)行單元測(cè)試,只需要傳入不同的整數(shù)即可完成單元測(cè)試。原來(lái)的依賴(lài)被隔離到隔離方法中,沒(méi)有任何邏輯,后續(xù)的改動(dòng)也方便在隔離方法中進(jìn)行。

        雖然Spring[18]的使用給對(duì)象管理帶來(lái)了很大的便捷,但是在單個(gè)方法中難免還是存在創(chuàng)建局部變量的情況而引入強(qiáng)依賴(lài)。隔離方法配合Spring使用可以全方位地解決對(duì)象定義的依賴(lài)問(wèn)題。另一方面,因?yàn)檫z留代碼等原因還存在大量的項(xiàng)目沒(méi)有使用Spring,這時(shí)候?yàn)榱税岩蕾?lài)隔離開(kāi)來(lái),隔離方法的使用就顯得尤為重要了。

        3.2 外部從內(nèi)部中分離

        我們先來(lái)區(qū)分一下內(nèi)部代碼和外部代碼。開(kāi)發(fā)者自己編寫(xiě)的代碼為內(nèi)部代碼,編碼過(guò)程中使用的JDK的類(lèi)也是內(nèi)部代碼,其他需要的類(lèi)庫(kù)為外部代碼。如果使用JDK的類(lèi)庫(kù)完成的代碼,可維護(hù)性和可測(cè)試性都會(huì)相對(duì)較高,一旦引入外部代碼,維護(hù)和測(cè)試的難度就會(huì)呈指數(shù)級(jí)上升,外部代碼越多越難維護(hù)和測(cè)試。如果我們能夠把外部代碼從內(nèi)部代碼中分離出來(lái),剩下的就是業(yè)務(wù)邏輯和內(nèi)部代碼,這樣就解決了上述存在的難題。例如下面這段代碼:

        public void buildHtml(HttpServletRequest request,

        HttpServletResponse response) {

        PrintWriter out=response.getWriter();

        try {

        String requestURI=request.getRequestURI();

        StringBuilder html=new StringBuilder();

        html.append(" ");

        html.append(" ");

        html.append("

        請(qǐng)求URI:"

        +requestURI+"

        ");

        html.append(" ");

        html.append("");

        out.println(html.toString());

        } finally {

        out.close();

        }

        }

        其中,外部依賴(lài)的類(lèi)庫(kù)為Web編程常見(jiàn)的兩個(gè)類(lèi)HttpServletRequest和HttpServletResponse。現(xiàn)在如果要進(jìn)行單元測(cè)試的話,因?yàn)閮?nèi)部和外部代碼混合在一起,需要模擬request和response的行為,這就大幅提高了實(shí)現(xiàn)測(cè)試的難度。這類(lèi)場(chǎng)景下要使用的模式為外部從內(nèi)部分離模式。將request和response及其對(duì)應(yīng)的方法調(diào)用提取到隔離方法中,在隔離方法里取得數(shù)據(jù)后再傳給原來(lái)的方法,讓原來(lái)的方法只包含業(yè)務(wù)邏輯和對(duì)基本類(lèi)型數(shù)據(jù)的依賴(lài)。修改后的代碼如下:

        //數(shù)據(jù)隔離方法

        public void buildHtml(HttpServletRequest request,

        HttpServletResponse response) {

        PrintWriter out=response.getWriter();

        try {

        String requestURI=request.getRequestURI();

        out.println(buildHtml(requestURI));

        } finally {

        out.close();

        }

        }

        protected String buildHtml(String requestURI) {

        StringBuilder html=new StringBuilder();

        html.append(" ");

        html.append(" ");

        html.append("

        請(qǐng)求URI:"

        +requestURI+"

        ");

        html.append(" ");

        html.append("")

        return html.toString();

        }

        修改之后,原來(lái)的方法只依賴(lài)字符串類(lèi)型變量,只包含業(yè)務(wù)邏輯和內(nèi)部代碼,所有外部代碼都被分離到隔離方法中,隔離方法中只包含獲取基本類(lèi)型數(shù)據(jù)和調(diào)用原來(lái)的方法這兩部分,沒(méi)有任何業(yè)務(wù)邏輯。現(xiàn)在對(duì)業(yè)務(wù)邏輯的測(cè)試就變得異常簡(jiǎn)單,外部代碼的改動(dòng)也不會(huì)影響到業(yè)務(wù)邏輯。

        3.3 步驟從細(xì)節(jié)中分離

        梳理業(yè)務(wù)邏輯時(shí),比較有效的方式是畫(huà)出這個(gè)邏輯的流程圖,圖上的每一個(gè)步驟都會(huì)對(duì)應(yīng)著一段業(yè)務(wù)邏輯。通過(guò)這個(gè)流程圖,我們可以更容易地理解這個(gè)邏輯,后續(xù)其他人也可以通過(guò)流程圖更快速地掌握該業(yè)務(wù)邏輯。所以我們會(huì)把步驟和每一步的細(xì)節(jié)分離開(kāi)來(lái)提高我們理解邏輯的效率。再回到代碼來(lái)看,開(kāi)發(fā)者寫(xiě)代碼時(shí),一般都是把步驟和細(xì)節(jié)放在了一起,或者步驟是通過(guò)代碼細(xì)節(jié)體現(xiàn)出來(lái)的,只有閱讀完代碼,才會(huì)理解該段代碼的步驟。如果想要快速了解代碼的邏輯,需要額外畫(huà)出流程圖或者時(shí)序圖。如果寫(xiě)代碼的時(shí)候把步驟從細(xì)節(jié)中分離出來(lái),就可以通過(guò)看步驟來(lái)快速了解邏輯,這里的步驟就像流程圖一樣,當(dāng)需要了解一個(gè)步驟的細(xì)節(jié)時(shí),再深入到這一步驟的代碼來(lái)看。例如下面這段代碼:

        public void storeOrderedFibonacciNumbers(

        List numberList) throws IOException {

        Collections.sort(numberList);

        for (int i=2;i

        numberList.set(i,numberList.get(i-1)+

        numberList.get(i-2));

        }

        FileOutputStream storeFile=null;

        try {

        storeFile=

        new FileOutputStream("resources/numbers.txt");

        StringBuilder txtbuilder=new StringBuilder();

        for(Integer number:numberList) {

        txtbuilder.append(number.toString()+" ");

        }

        storeFile.write(txtbuilder.toString().getBytes());

        storeFile.write(new Date().toString().getBytes());

        } finally {

        if (storeFile !=null) storeFile.close();

        }

        }

        通過(guò)閱讀代碼可以看出,一共有三個(gè)步驟:排序、形成斐波那契形式的列表、存儲(chǔ)到文件中。代碼中沒(méi)有顯式的步驟,要通過(guò)閱讀代碼來(lái)梳理這些步驟。這只是一個(gè)簡(jiǎn)單的例子,現(xiàn)實(shí)中有很多邏輯比這個(gè)復(fù)雜得多,要仔細(xì)閱讀很久才能理解代碼的步驟,所以為了便于理解,這里沒(méi)有選取復(fù)雜的例子。這里使用步驟從代碼中分離的模式來(lái)對(duì)其進(jìn)行修改,把梳理出的每一步放入一個(gè)單獨(dú)的方法中,方法名作為步驟名,這樣多個(gè)方法連起來(lái)就是邏輯的步驟了。修改后的代碼如下:

        //粗細(xì)隔離方法

        public void storeOrderedFibonacciNumbers(

        List list) throws IOException {

        sort(list);

        formFibonacciList(list);

        storeToFile(list);

        }

        protected void sort(List list) {

        Collections.sort(list);

        }

        protected void formFibonacciList(List list) {

        for (int i=2;i

        list.set(i,list.get(i-1)+list.get(i-2));

        }

        }

        protected void storeToFile(List list)

        throws FileNotFoundException, IOException {

        FileOutputStream storeFile=null;

        try {

        storeFile=

        new FileOutputStream("resources/numbers.txt");

        StringBuilder txtbuilder=new StringBuilder();

        for(Integer number:list) {

        txtbuilder.append(number.toString()+" ");

        }

        storeFile.write(txtbuilder.toString().getBytes());

        storeFile.write(new Date().toString().getBytes());

        } finally {

        if (storeFile !=null) storeFile.close();

        }

        }

        修改之后,在原來(lái)的方法里可以清晰地看到邏輯的步驟,可以快速理解邏輯的內(nèi)容,如果需要進(jìn)一步詳細(xì)地理解每一步的細(xì)節(jié),再仔細(xì)閱讀每一步對(duì)應(yīng)的方法即可。這樣在代碼中,步驟和細(xì)節(jié)就分離開(kāi)來(lái),步驟就像流程圖,每一步對(duì)應(yīng)的方法就是具體的邏輯細(xì)節(jié)。后續(xù)改動(dòng)時(shí)可以定位到更小的范圍進(jìn)行修改,降低了引入誤操作造成問(wèn)題的概率,同時(shí)每個(gè)步驟可以單獨(dú)進(jìn)行測(cè)試,也使得單元測(cè)試變得更加容易。

        3.4 實(shí)現(xiàn)從過(guò)程中分離

        實(shí)現(xiàn)了步驟從細(xì)節(jié)中分離之后,已經(jīng)為代碼的閱讀、維護(hù)和測(cè)試帶來(lái)了很大的便捷。不過(guò),這時(shí)如果想要替換其中的一個(gè)或多個(gè)步驟,還是要改動(dòng)很多代碼。為了讓代碼更符合開(kāi)閉原則[15],我們需要對(duì)代碼進(jìn)行進(jìn)一步調(diào)整,以能夠讓我們方便地替換其中的步驟?,F(xiàn)在,雖然從代碼結(jié)構(gòu)上每一個(gè)步驟和具體實(shí)現(xiàn)已經(jīng)分離開(kāi)來(lái),但是它們還處于同一個(gè)類(lèi)和文件之中,這樣就為滿(mǎn)足開(kāi)閉原則帶來(lái)了難處。

        接下來(lái)我們要做的是把具體的實(shí)現(xiàn)從現(xiàn)有的類(lèi)和文件中獨(dú)立出來(lái),形成可以變換的單獨(dú)的類(lèi)。也就是實(shí)現(xiàn)從過(guò)程中分離的模式。使用面向接口編程的思想,為每一個(gè)步驟創(chuàng)建一個(gè)單獨(dú)的接口,每一步的每一種實(shí)現(xiàn)方法就是具體的一種實(shí)現(xiàn),將其放入單獨(dú)的一個(gè)類(lèi)文件中。這樣如果未來(lái)想要修改一個(gè)步驟,我們只需要改動(dòng)其專(zhuān)門(mén)對(duì)應(yīng)的一個(gè)文件即可,避免了對(duì)其他代碼帶來(lái)影響的可能。同時(shí),如果想要替換一種實(shí)現(xiàn)方法,只需要?jiǎng)?chuàng)建一個(gè)新的類(lèi)文件來(lái)實(shí)現(xiàn)對(duì)應(yīng)的接口即可,對(duì)原有的邏輯除了替換新的實(shí)現(xiàn)之外沒(méi)有任何改動(dòng)。另外,這樣單元測(cè)試也變成測(cè)試獨(dú)立的類(lèi),使得單元測(cè)試更方便和整潔。使用該模式修改之后的代碼如下:

        //增強(qiáng)的粗細(xì)隔離方法

        public void storeOrderedFibonacciNumbers(

        List list) throws IOException {

        NumberSorter sorter=new DefaultSorter();

        FibonacciListBuilder fibonacciBuilder=

        new DefaultFibonacciBuilder();

        NumberStorage storage=new FileStorage();

        storeOrderedFibonacciNumbers(list,sorter,

        fibonacciBuilder,storage);

        }

        protected void storeOrderedFibonacciNumbers(

        List list,

        NumberSorter sorter,

        FibonacciListBuilder builder,

        NumberStorage storage) throws IOException {

        sorter.sort(list);

        builder.build(list);

        storage.store(list);

        }

        public interface NumberSorter {

        List sort(List list);

        }

        public class DefaultSorter implements NumberSorter {

        @Override

        public List sort(List list) {

        Collections.sort(list);

        return list;

        }

        }

        public interface FibonacciListBuilder {

        List build(List list);

        }

        public class DefaultFibonacciBuilder

        implements FibonacciListBuilder {

        @Override

        public List build(List list) {

        for (int i=2;i

        list.set(i,list.get(i-1)+list.get(i-2));

        }

        return list;

        }

        }

        public interface NumberStorage {

        void store(List list) throws IOException;

        }

        public class FileStorage implements NumberStorage {

        @Override

        public void store(List list)

        throws IOException {

        FileOutputStream storeFile=null;

        try {

        storeFile=

        new FileOutputStream("resources/numbers.txt");

        StringBuilder txtbuilder=new StringBuilder();

        for(Integer number:list) {

        txtbuilder.append(number.toString()+" ");

        }

        storeFile.write(txtbuilder.toString()

        .getBytes());

        storeFile.write(new Date().toString()

        .getBytes());

        } finally {

        if (storeFile !=null) storeFile.close();

        }

        }

        }

        修改之后,原來(lái)的類(lèi)里面不再有任何具體的細(xì)節(jié)實(shí)現(xiàn),只包含三個(gè)步驟,具體的實(shí)現(xiàn)轉(zhuǎn)移到不同的類(lèi)文件中。這樣就完成了步驟和細(xì)節(jié)的徹底分離。閱讀代碼的時(shí)候只需要閱讀步驟即可,需要了解細(xì)節(jié)時(shí)到對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)文件里查看?,F(xiàn)在如果想要替換其中一個(gè)步驟的實(shí)現(xiàn),比如存儲(chǔ)步驟由文件存儲(chǔ)改為數(shù)據(jù)庫(kù)存儲(chǔ),只需要新寫(xiě)一個(gè)類(lèi)來(lái)實(shí)現(xiàn)存儲(chǔ)接口,完成數(shù)據(jù)庫(kù)存儲(chǔ)功能,然后在隔離方法里面把為存儲(chǔ)創(chuàng)建的對(duì)象改為創(chuàng)建數(shù)據(jù)庫(kù)存儲(chǔ)對(duì)象即可。同樣,測(cè)試時(shí)我們可以使用一樣的方式來(lái)替換其中的一個(gè)或多個(gè)步驟,進(jìn)而方便完成對(duì)任何一個(gè)的步驟的測(cè)試。這里還可以使用諸如Mockito[19]這樣的框架來(lái)完成單元測(cè)試,這樣就不用額外再單獨(dú)為測(cè)試創(chuàng)建實(shí)現(xiàn)類(lèi)。替換數(shù)據(jù)庫(kù)存儲(chǔ)對(duì)應(yīng)的代碼如下:

        //隔離方法

        public void storeOrderedFibonacciNumbers(

        List list) throws IOException {

        NumberSorter sorter=new DefaultSorter();

        FibonacciListBuilder fibonacciBuilder=

        new DefaultFibonacciBuilder();

        NumberStorage storage=new DatabaseStorage();

        storeOrderedFibonacciNumbers(list,sorter,

        fibonacciBuilder,storage);

        }

        public class DatabaseStorage implements NumberStorage {

        @Override

        public void store(List list)

        throws IOException {

        //把數(shù)字列表存到數(shù)據(jù)庫(kù)之中

        //此處省略

        }

        }

        3.5 線程從邏輯中分離

        為了提升系統(tǒng)的性能和避免用戶(hù)等待,多線程已被頻繁地使用于軟件開(kāi)發(fā)中。在帶來(lái)益處的同時(shí),也給應(yīng)用代碼的維護(hù)和測(cè)試帶來(lái)新的挑戰(zhàn)。在應(yīng)用程序的代碼中,線程相關(guān)的代碼和業(yè)務(wù)邏輯的代碼耦合在一起,為代碼的修改增加了難度,也使得引入問(wèn)題的概率大幅提升。在這樣的方式下,既沒(méi)法單獨(dú)修改線程相關(guān)的代碼,也無(wú)法單獨(dú)修改業(yè)務(wù)邏輯相關(guān)的代碼。同樣,單元測(cè)試時(shí)必須考慮多線程的情況,使得單元測(cè)試的難度提升很多。例如下面這段代碼:

        public void executeBusinessLogicUnderOtherThread(

        final String parameter) {

        ExecutorService singleThreadExecutor=

        Executors.newSingleThreadExecutor();

        singleThreadExecutor.execute(new Runnable() {

        @Override

        public void run() {

        System.out.println("子線程Id: "+

        Thread.currentThread().getId());

        System.out.println("業(yè)務(wù)邏輯:"+parameter);

        }

        });

        }

        線程相關(guān)的代碼和邏輯相關(guān)的代碼緊密地耦合在一起,線程的代碼改動(dòng)和邏輯的代碼改動(dòng)修改的是同一個(gè)方法。同時(shí),測(cè)試這個(gè)方法的時(shí)候,每一個(gè)單元測(cè)試都必須要處理多線程的情況。這樣的情景下,使用線程從邏輯中分離的模式來(lái)解決此處存在的難題。修改后的代碼如下:

        //用于接收參數(shù)和執(zhí)行業(yè)務(wù)邏輯的接口

        public interface LogicExecutor {

        void execute();

        T getParameter();

        }

        //行為隔離方法

        public void executeBusinessLogicUnderOtherThread(

        final String parameter) {

        final LogicExecutor executor=

        new LogicExecutor() {

        @Override

        public String getParameter() {

        return parameter;

        }

        @Override

        public void execute() {

        businessLogic(getParameter());

        }

        };

        executeBusinessLogicUnderOtherThread(executor);

        }

        public void executeBusinessLogicUnderOtherThread(

        final LogicExecutor executor) {

        ExecutorService singleThreadExecutor=

        Executors.newSingleThreadExecutor();

        singleThreadExecutor.execute(new Runnable() {

        @Override

        public void run() {

        executor.execute();

        }

        });

        singleThreadExecutor.shutdown();

        }

        public void businessLogic(final String parameter) {

        System.out.println("子線程Id: "+

        Thread.currentThread().getId());

        System.out.println("業(yè)務(wù)邏輯:"+parameter);

        }

        為了提高普適性,本文創(chuàng)建一個(gè)接口用于接收業(yè)務(wù)邏輯需要的參數(shù)和執(zhí)行業(yè)務(wù)邏輯,把業(yè)務(wù)邏輯相關(guān)的代碼提取到一個(gè)單獨(dú)的方法中,然后在實(shí)現(xiàn)了上面定義接口的類(lèi)中調(diào)用邏輯對(duì)應(yīng)的方法。這樣就把線程這個(gè)非業(yè)務(wù)邏輯的行為和業(yè)務(wù)邏輯對(duì)應(yīng)的行為隔離開(kāi)來(lái),既可以單獨(dú)修改,又可以單獨(dú)進(jìn)行測(cè)試,對(duì)于多線程的測(cè)試,只需要模擬一個(gè)行為保證多行程工作正常即可。對(duì)于業(yè)務(wù)邏輯的行為,大部分測(cè)試用例都可以在單線程模式下完成,然后再做一些線程和邏輯的集成測(cè)試即可。

        4 結(jié) 語(yǔ)

        現(xiàn)有的提高代碼可維護(hù)性和可測(cè)試性的技術(shù)方案,其理解和運(yùn)用的難度,造成了目前開(kāi)發(fā)者在軟件開(kāi)發(fā)的過(guò)程中,依然在編寫(xiě)存在大量依賴(lài)的代碼,進(jìn)而在許多技術(shù)存在的基礎(chǔ)上,代碼的維護(hù)和測(cè)試仍然是開(kāi)發(fā)者面臨的一大難題。為了解決這一難題,本文提出代碼分離模型,通過(guò)隔離方法和隔離層,輔助開(kāi)發(fā)簡(jiǎn)單、方便地實(shí)現(xiàn)低耦合并且高度可測(cè)的代碼。代碼分離模式的引入,讓開(kāi)發(fā)者在大部分場(chǎng)景下都可以快速并高質(zhì)量地完成代碼分離模型的運(yùn)用。通過(guò)代碼分離模式中的實(shí)例代碼證明,代碼分離模型這一創(chuàng)新對(duì)于提高代碼的維護(hù)性和測(cè)試性簡(jiǎn)單高效,易于使用,能夠大幅提高開(kāi)發(fā)者的編程和測(cè)試水平,進(jìn)而可以顯著解決目前計(jì)算機(jī)軟件面臨的變化多和響應(yīng)快的困難和挑戰(zhàn)。

        猜你喜歡
        單元測(cè)試調(diào)用開(kāi)發(fā)者
        核電項(xiàng)目物項(xiàng)調(diào)用管理的應(yīng)用研究
        LabWindows/CVI下基于ActiveX技術(shù)的Excel調(diào)用
        基于系統(tǒng)調(diào)用的惡意軟件檢測(cè)技術(shù)研究
        16%游戲開(kāi)發(fā)者看好VR
        CHIP新電腦(2016年3期)2016-03-10 13:06:42
        iOS開(kāi)發(fā)者調(diào)查
        電腦迷(2015年8期)2015-05-30 12:27:10
        iOS開(kāi)發(fā)者調(diào)查
        電腦迷(2015年4期)2015-05-30 05:24:09
        一年級(jí)上冊(cè)第五單元測(cè)試
        一年級(jí)上冊(cè)一、二單元測(cè)試
        利用RFC技術(shù)實(shí)現(xiàn)SAP系統(tǒng)接口通信
        安卓開(kāi)發(fā)者之煩惱
        国产91AV免费播放| 提供最新的在線欧美综合一区| 少妇被又大又粗又爽毛片| 少妇人妻在线伊人春色| 老熟妇嗷嗷叫91九色| 人妻精品久久一区二区三区| 中文乱码字字幕在线国语| 天天做天天爱夜夜爽女人爽| 亚洲av永久无码精品放毛片| 品色永久免费| 精品久久无码中文字幕| 天天躁日日躁狠狠躁一区| 偷拍熟女亚洲另类| 精品自拍偷拍一区二区三区| 日本一区二区不卡在线| 国产av一区二区三区天堂综合网| 欧美丰满熟妇性xxxx| 久久精品国产亚洲av麻豆| 亚洲精品成人网站在线观看| 91av视频在线| 国产精品美女久久久浪潮av| 精品少妇人妻av一区二区蜜桃| 在线观看午夜视频一区二区| 久久久中文久久久无码| 超清纯白嫩大学生无码网站| 亚洲不卡av不卡一区二区| 在线无码国产精品亚洲а∨| 久久人妻少妇中文字幕| 一本色道88久久加勒比精品| 国产一区av男人天堂| 亚洲色精品三区二区一区| 亚洲精品乱码久久久久久蜜桃不卡 | 好吊色欧美一区二区三区四区| 国产无遮挡裸体免费视频| 国产精品密播放国产免费看| 中文乱码字幕高清在线观看| 亚洲精品一区二区三区蜜臀| 久久综合另类激情人妖| 体验区试看120秒啪啪免费| 国产成熟人妻换╳╳╳╳| AV教师一区高清|