偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一堂如何提高代碼質(zhì)量的培訓(xùn)課

開(kāi)發(fā) 開(kāi)發(fā)工具
我們?cè)u(píng)價(jià)高質(zhì)量代碼有三要素:可讀性、可維護(hù)性、可變更性。我們的代碼要一個(gè)都不能少地達(dá)到了這三要素的要求才能算高質(zhì)量的代碼。

今天這堂培訓(xùn)課講什么呢?我既不講Spring,也不講Hibernate,更不講Ext,我不講任何一個(gè)具體的技術(shù)。我們拋開(kāi)任何具體的技術(shù),來(lái)談?wù)勅绾翁岣叽a質(zhì)量。如何提高代碼質(zhì)量,相信不僅是在座所有人苦惱的事情,也是所有軟件項(xiàng)目苦惱的事情。如何提高代碼質(zhì)量呢,我認(rèn)為我們首先要理解什么是高質(zhì)量的代碼。

[[102089]]

 

高質(zhì)量代碼的三要素

我們?cè)u(píng)價(jià)高質(zhì)量代碼有三要素:可讀性、可維護(hù)性、可變更性。我們的代碼要一個(gè)都不能少地達(dá)到了這三要素的要求才能算高質(zhì)量的代碼。

1. 可讀性強(qiáng)

一提到可讀性似乎有一些老生常談的味道,但令人沮喪的是,雖然大家一而再,再而三地強(qiáng)調(diào)可讀性,但我們的代碼在可讀性方面依然做得非常糟糕。由于工作的需要,我常常需要去閱讀他人的代碼,維護(hù)他人設(shè)計(jì)的模塊。每當(dāng)我看到大段大段、密密麻麻的代碼,而且還沒(méi)有任何的注釋時(shí)常常感慨不已,深深體會(huì)到了這項(xiàng)工作的重要。由于分工的需要,我們寫(xiě)的代碼難免需要?jiǎng)e人去閱讀和維護(hù)的。而對(duì)于許多程序員來(lái)說(shuō),他們很少去閱讀和維護(hù)別人的代碼。正因?yàn)槿绱?,他們很少關(guān)注代碼的可讀性,也對(duì)如何提高代碼的可讀性缺乏切身體會(huì)。有時(shí)即使為代碼編寫(xiě)了注釋?zhuān)渤3J亲⑨屨Z(yǔ)言晦澀難懂形同天書(shū),令閱讀者反復(fù)斟酌依然不明其意。針對(duì)以上問(wèn)題,我給大家以下建議:

1)不要編寫(xiě)大段的代碼

如果你有閱讀他人代碼的經(jīng)驗(yàn),當(dāng)你看到別人寫(xiě)的大段大段的代碼,而且還不怎么帶注釋?zhuān)闶窃鯓拥母杏X(jué),是不是“嗡”地一聲頭大。各種各樣的功能糾纏在一個(gè)方法中,各種變量來(lái)回調(diào)用,相信任何人多不會(huì)認(rèn)為它是高質(zhì)量的代碼,但卻頻繁地出現(xiàn)在我們編寫(xiě)的程序了。如果現(xiàn)在你再回顧自己寫(xiě)過(guò)的代碼,你會(huì)發(fā)現(xiàn),稍微編寫(xiě)一個(gè)復(fù)雜的功能,幾百行的代碼就出去了。一些比較好的辦法就是分段。將大段的代碼經(jīng)過(guò)整理,分為功能相對(duì)獨(dú)立的一段又一段,并且在每段的前端編寫(xiě)一段注釋。這樣的編寫(xiě),比前面那些雜亂無(wú)章的大段代碼確實(shí)進(jìn)步了不少,但它們?cè)诠δ塥?dú)立性、可復(fù)用性、可維護(hù)性方面依然不盡人意。從另一個(gè)比較專(zhuān)業(yè)的評(píng)價(jià)標(biāo)準(zhǔn)來(lái)說(shuō),它沒(méi)有實(shí)現(xiàn)低耦合、高內(nèi)聚。我給大家的建議是,將這些相對(duì)獨(dú)立的段落另外封裝成一個(gè)又一個(gè)的函數(shù)。

許多大師在自己的經(jīng)典書(shū)籍中,都鼓勵(lì)我們?cè)诰帉?xiě)代碼的過(guò)程中應(yīng)當(dāng)養(yǎng)成不斷重構(gòu)的習(xí)慣。我們?cè)诰帉?xiě)代碼的過(guò)程中常常要編寫(xiě)一些復(fù)雜的功能,起初是寫(xiě)在一個(gè)類(lèi)的一個(gè)函數(shù)中。隨著功能的逐漸展開(kāi),我們開(kāi)始對(duì)復(fù)雜功能進(jìn)行歸納整理,整理出了一個(gè)又一個(gè)的獨(dú)立功能。這些獨(dú)立功能有它與其它功能相互交流的輸入輸出數(shù)據(jù)。當(dāng)我們分析到此處時(shí),我們會(huì)非常自然地要將這些功能從原函數(shù)中分離出來(lái),形成一個(gè)又一個(gè)獨(dú)立的函數(shù),供原函數(shù)調(diào)用。在編寫(xiě)這些函數(shù)時(shí),我們應(yīng)當(dāng)仔細(xì)思考一下,為它們?nèi)∫粋€(gè)釋義名稱,并為它們編寫(xiě)注釋?zhuān)ê竺孢€將詳細(xì)討論這個(gè)問(wèn)題)。另一個(gè)需要思考的問(wèn)題是,這些函數(shù)應(yīng)當(dāng)放到什么地方。這些函數(shù)可能放在原類(lèi)中,也可能放到其它相應(yīng)職責(zé)的類(lèi)中,其遵循的原則應(yīng)當(dāng)是“職責(zé)驅(qū)動(dòng)設(shè)計(jì)”(后面也將詳細(xì)描述)。

下面是我編寫(xiě)的一個(gè)從XML文件中讀取數(shù)據(jù),將其生成工廠的一個(gè)類(lèi)。這個(gè)類(lèi)最主要的一段程序就是初始化工廠,該功能歸納起來(lái)就是三部分功能:用各種方式嘗試讀取文件、以DOM的方式解析XML數(shù)據(jù)流、生成工廠。而這些功能被我歸納整理后封裝在一個(gè)不同的函數(shù)中,并且為其取了釋義名稱和編寫(xiě)了注釋?zhuān)?/p>

  1. /**  
  2.   * 初始化工廠。根據(jù)路徑讀取XML文件,將XML文件中的數(shù)據(jù)裝載到工廠中  
  3.   * @param path XML的路徑  
  4.   */ 
  5. public void initFactory(String path){  
  6.     if(findOnlyOneFileByClassPath(path)){return;}  
  7.     if(findResourcesByUrl(path)){return;}  
  8.     if(findResourcesByFile(path)){return;}  
  9.     this.paths = new String[]{path};  
  10. }  
  11. /**  
  12. * 初始化工廠。根據(jù)路徑列表依次讀取XML文件,將XML文件中的數(shù)據(jù)裝載到工廠中  
  13. * @param paths 路徑列表  
  14. */ 
  15. public void initFactory(String[] paths){  
  16.     for(int i=0; i<paths.length; i++){  
  17.         initFactory(paths[i]);  
  18.     }  
  19.     this.paths = paths;  
  20. }  
  21. /**  
  22. * 重新初始化工廠,初始化所需的參數(shù),為上一次初始化工廠所用的參數(shù)。  
  23. */ 
  24. public void reloadFactory(){  
  25. initFactory(this.paths);  
  26. }  
  27. /**  
  28. * 采用ClassLoader的方式試圖查找一個(gè)文件,并調(diào)用<code>readXmlStream()</code>進(jìn)行解析  
  29. * @param path XML文件的路徑  
  30. * @return 是否成功  
  31. */ 
  32. protected boolean findOnlyOneFileByClassPath(String path){  
  33.     boolean success = false;  
  34.     try {  
  35.         Resource resource = new ClassPathResource(path, this.getClass());  
  36.         resource.setFilter(this.getFilter());  
  37.         InputStream is = resource.getInputStream();  
  38.         if(is==null){return false;}  
  39.         readXmlStream(is);  
  40.         success = true;  
  41.     } catch (SAXException e) {  
  42.         log.debug("Error when findOnlyOneFileByClassPath:"+path,e);  
  43.    } catch (IOException e) {  
  44.         log.debug("Error when findOnlyOneFileByClassPath:"+path,e);  
  45.     } catch (ParserConfigurationException e) {  
  46.         log.debug("Error when findOnlyOneFileByClassPath:"+path,e);  
  47.     }  
  48.     return success;  
  49. }  
  50. /**  
  51. * 采用URL的方式試圖查找一個(gè)目錄中的所有XML文件,并調(diào)用<code>readXmlStream()</code>進(jìn)行解析  
  52. * @param path XML文件的路徑  
  53. * @return 是否成功  
  54. */ 
  55. protected boolean findResourcesByUrl(String path){  
  56.     boolean success = false;  
  57.     try {  
  58.         ResourcePath resourcePath = new PathMatchResource(path, this.getClass());  
  59.         resourcePath.setFilter(this.getFilter());  
  60.         Resource[] loaders = resourcePath.getResources();  
  61.         for(int i=0; i<loaders.length; i++){  
  62.             InputStream is = loaders[i].getInputStream();  
  63.             if(is!=null){  
  64.                 readXmlStream(is);  
  65.                 success = true;  
  66.             }  
  67.         }  
  68.     } catch (SAXException e) {  
  69.         log.debug("Error when findResourcesByUrl:"+path,e);  
  70.     } catch (IOException e) {  
  71.        log.debug("Error when findResourcesByUrl:"+path,e);  
  72.     } catch (ParserConfigurationException e) {  
  73.         log.debug("Error when findResourcesByUrl:"+path,e);  
  74.     }  
  75.     return success;  
  76. }  
  77. /**  
  78. * 用File的方式試圖查找文件,并調(diào)用<code>readXmlStream()</code>解析  
  79. * @param path XML文件的路徑  
  80. * @return 是否成功  
  81. */ 
  82. protected boolean findResourcesByFile(String path){  
  83.     boolean success = false;  
  84.     FileResource loader = new FileResource(new File(path));  
  85.     loader.setFilter(this.getFilter());  
  86.     try {  
  87.         Resource[] loaders = loader.getResources();  
  88.         if(loaders==null){return false;}  
  89.     for(int i=0; i<loaders.length; i++){  
  90.         InputStream is = loaders[i].getInputStream();  
  91.         if(is!=null){  
  92.             readXmlStream(is);  
  93.             success = true;  
  94.         }  
  95.     }  
  96. catch (IOException e) {  
  97.     log.debug("Error when findResourcesByFile:"+path,e);  
  98. catch (SAXException e) {  
  99.     log.debug("Error when findResourcesByFile:"+path,e);  
  100. catch (ParserConfigurationException e) {  
  101.     log.debug("Error when findResourcesByFile:"+path,e);  
  102. }  
  103.   return success;  
  104. }  
  105. /**  
  106. * 讀取并解析一個(gè)XML的文件輸入流,以Element的形式獲取XML的根,  
  107. * 然后調(diào)用<code>buildFactory(Element)</code>構(gòu)建工廠  
  108. * @param inputStream 文件輸入流  
  109. * @throws SAXException  
  110. * @throws IOException  
  111. * @throws ParserConfigurationException  
  112. */ 
  113. protected void readXmlStream(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException{  
  114.     if(inputStream==null){  
  115.         throw new ParserConfigurationException("Cann't parse source because of InputStream is null!");  
  116.     }  
  117.     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
  118.     factory.setValidating(this.isValidating());  
  119.     factory.setNamespaceAware(this.isNamespaceAware());  
  120.     DocumentBuilder build = factory.newDocumentBuilder();  
  121.     Document doc = build.parse(new InputSource(inputStream));  
  122.     Element root = doc.getDocumentElement();  
  123.     buildFactory(root);  
  124. }  
  125. /**  
  126. * 用從一個(gè)XML的文件中讀取的數(shù)據(jù)構(gòu)建工廠  
  127. * @param root 從一個(gè)XML的文件中讀取的數(shù)據(jù)的根  
  128. */ 
  129. protected abstract void buildFactory(Element root); 

在編寫(xiě)代碼的過(guò)程中,通常有兩種不同的方式。一種是從下往上編寫(xiě),也就是按照順序,每分出去一個(gè)函數(shù),都要將這個(gè)函數(shù)編寫(xiě)完,才回到主程序,繼續(xù)往下編寫(xiě)。而一些更有經(jīng)驗(yàn)的程序員會(huì)采用另外一種從上往下的編寫(xiě)方式。當(dāng)他們?cè)诰帉?xiě)程序的時(shí)候,每個(gè)被分出去的程序,可以暫時(shí)只寫(xiě)一個(gè)空程序而不去具體實(shí)現(xiàn)功能。當(dāng)主程序完成以后,再一個(gè)個(gè)實(shí)現(xiàn)它的所有子程序。采用這樣的編寫(xiě)方式,可以使復(fù)雜程序有更好的規(guī)劃,避免只見(jiàn)樹(shù)木不見(jiàn)森林的弊病。

有多少代碼就算大段代碼,每個(gè)人有自己的理解。我編寫(xiě)代碼,每當(dāng)達(dá)到15~20行的時(shí)候,我就開(kāi)始考慮是否需要重構(gòu)代碼。同理,一個(gè)類(lèi)也不應(yīng)當(dāng)有太多的函數(shù),當(dāng)函數(shù)達(dá)到一定程度的時(shí)候就應(yīng)該考慮分為多個(gè)類(lèi)了;一個(gè)包也不應(yīng)當(dāng)有太多的類(lèi)······

2)釋義名稱與注釋

我們?cè)诿兞?、函?shù)、屬性、類(lèi)以及包的時(shí)候,應(yīng)當(dāng)仔細(xì)想想,使名稱更加符合相應(yīng)的功能。我們常常在說(shuō),設(shè)計(jì)一個(gè)系統(tǒng)時(shí)應(yīng)當(dāng)有一個(gè)或多個(gè)系統(tǒng)分析師對(duì)整個(gè)系統(tǒng)的包、類(lèi)以及相關(guān)的函數(shù)和屬性進(jìn)行規(guī)劃,但在通常的項(xiàng)目中這都非常難于做到。對(duì)它們的命名更多的還是程序員來(lái)完成。但是,在一個(gè)項(xiàng)目開(kāi)始的時(shí)候,應(yīng)當(dāng)對(duì)項(xiàng)目的命名出臺(tái)一個(gè)規(guī)范。譬如,在我的項(xiàng)目中規(guī)定,新增記錄用new或add開(kāi)頭,更新記錄用edit或mod開(kāi)頭,刪除用del開(kāi)頭,查詢用 find或query開(kāi)頭。使用最亂的就是get,因此我規(guī)定,get開(kāi)頭的函數(shù)僅僅用于獲取類(lèi)屬性。

注釋是每個(gè)項(xiàng)目組都在不斷強(qiáng)調(diào)的,可是依然有許多的代碼沒(méi)有任何的注釋。為什么呢?因?yàn)槊總€(gè)項(xiàng)目在開(kāi)發(fā)過(guò)程中往往時(shí)間都是非常緊的。在緊張的代碼開(kāi)發(fā)過(guò)程中,注釋往往就漸漸地被忽略了。利用開(kāi)發(fā)工具的代碼編寫(xiě)模板也許可以解決這個(gè)問(wèn)題。

用我們常用的MyEclipse為例,在菜單“window>>Preferences>>Java>>Code Style>>Code Templates>>Comments”中,可以簡(jiǎn)單的修改一下。

一堂如何提高代碼質(zhì)量的培訓(xùn)課

“Files”代表的是我們每新建一個(gè)文件(可能是類(lèi)也可能是接口)時(shí)編寫(xiě)的注釋?zhuān)彝ǔTO(shè)定為:

  1. /*  
  2. * created on ${date}  
  3. */ 

“Types”代表的是我們新建的接口或類(lèi)前的注釋?zhuān)彝ǔTO(shè)定為:

  1. /**  
  2. *  
  3. * @author ${user}  
  4. */ 

第一行為一個(gè)空行,是用于你寫(xiě)該類(lèi)的注釋。如果你采用“職責(zé)驅(qū)動(dòng)設(shè)計(jì)”,這里首先應(yīng)當(dāng)描述的是該類(lèi)的職責(zé)。如果需要,你可以寫(xiě)該類(lèi)一些重要的方法及其用法、該類(lèi)的屬性及其中文含義等。

${user}代表的是你在windows中登陸的用戶名。如果這個(gè)用戶名不是你的名稱,你可以直接寫(xiě)死為你自己的名稱。

其它我通常都保持為默認(rèn)值。通過(guò)以上設(shè)定,你在創(chuàng)建類(lèi)或接口的時(shí)候,系統(tǒng)將自動(dòng)為你編寫(xiě)好注釋?zhuān)缓竽憧梢栽谶@個(gè)基礎(chǔ)上進(jìn)行修改,大大提高注釋編寫(xiě)的效率。

同時(shí),如果你在代碼中新增了一個(gè)函數(shù)時(shí),通過(guò)Alt+Shift+J快捷鍵,可以按照模板快速添加注釋。

在編寫(xiě)代碼時(shí)如果你編寫(xiě)的是一個(gè)接口或抽象類(lèi),我還建議你在@author后面增加@see注釋?zhuān)瑢⒃摻涌诨虺橄箢?lèi)的所有實(shí)現(xiàn)類(lèi)列出來(lái),因?yàn)殚喿x者在閱讀的時(shí)候,尋找接口或抽象類(lèi)的實(shí)現(xiàn)類(lèi)比較困難。

  1. /**  
  2. * 抽象的單表數(shù)組查詢實(shí)現(xiàn)類(lèi),僅用于單表查詢  
  3. * @author 范鋼  
  4. * @see com.htxx.support.query.DefaultArrayQuery  
  5. * @see com.htxx.support.query.DwrQuery  
  6. */ 
  7. public abstract class ArrayQuery implements ISingleQuery {  
  8. ... 

#p#

2. 可維護(hù)性

軟件的可維護(hù)性有幾層意思,首先的意思就是能夠適應(yīng)軟件在部署和使用中的各種情況。從這個(gè)角度上來(lái)說(shuō),它對(duì)我們的軟件提出的要求就是不能將代碼寫(xiě)死。

1)代碼不能寫(xiě)死

我曾經(jīng)見(jiàn)我的同事將系統(tǒng)要讀取的一個(gè)日志文件指定在C盤(pán)的一個(gè)固定目錄下,如果系統(tǒng)部署時(shí)沒(méi)有這個(gè)目錄以及這個(gè)文件就會(huì)出錯(cuò)。如果他將這個(gè)決定路徑下的目錄改為相對(duì)路徑,或者通過(guò)一個(gè)屬性文件可以修改,代碼豈不就寫(xiě)活了。一般來(lái)說(shuō),我在設(shè)計(jì)中需要使用日志文件、屬性文件、配置文件,通常都是以下幾個(gè)方式:將文件放到與類(lèi)相同的目錄,使用ClassLoader.getResource()來(lái)讀取;將文件放到classpath目錄下,用File的相對(duì)路徑來(lái)讀?。皇褂脀eb.xml或另一個(gè)屬性文件來(lái)制定讀取路徑。

我也曾見(jiàn)另一家公司的軟件要求,在部署的時(shí)候必須在C:/bea目錄下,如果換成其它目錄則不能正常運(yùn)行。這樣的設(shè)定常常為軟件部署時(shí)帶來(lái)許多的麻煩。如果服務(wù)器在該目錄下已經(jīng)沒(méi)有多余空間,或者已經(jīng)有其它軟件,將是很撓頭的事情。

2)預(yù)測(cè)可能發(fā)生的變化

除此之外,在設(shè)計(jì)的時(shí)候,如果將一些關(guān)鍵參數(shù)放到配置文件中,可以為軟件部署和使用帶來(lái)更多的靈活性。要做到這一點(diǎn),要求我們?cè)谲浖O(shè)計(jì)時(shí),應(yīng)當(dāng)有更多的意識(shí),考慮到軟件應(yīng)用中可能發(fā)生的變化。比如,有一次我在設(shè)計(jì)財(cái)務(wù)軟件的時(shí)候,考慮到一些單據(jù)在制作時(shí)的前置條件,在不同企業(yè)使用的時(shí)候,可能要求不一樣,有些企業(yè)可能要求嚴(yán)格些而有些要求松散些??紤]到這種可能的變化,我將前置條件設(shè)計(jì)為可配置的,就可能方便部署人員在實(shí)際部署中進(jìn)行靈活變化。然而這樣的配置,必要的注釋說(shuō)明是非常必要的。

軟件可維護(hù)性的另一層意思就是軟件的設(shè)計(jì)便于日后的變更。這一層意思與軟件的可變更性是重合的。所有的軟件設(shè)計(jì)理論的發(fā)展,都是從軟件的可變更性這一要求逐漸展開(kāi)的,它成為了軟件設(shè)計(jì)理論的核心。

#p#

3. 可變更性

前面我提到了,軟件的變更性是所有軟件理論的核心,那么什么是軟件的可變更性呢?按照現(xiàn)在的軟件理論,客戶對(duì)軟件的需求時(shí)時(shí)刻刻在發(fā)生著變化。當(dāng)軟件設(shè)計(jì)好以后,為應(yīng)對(duì)客戶需求的變更而進(jìn)行的代碼修改,其所需要付出的代價(jià),就是軟件設(shè)計(jì)的可變更性。由于軟件合理的設(shè)計(jì),修改所付出的代價(jià)越小,則軟件的可變更性越好,即代碼設(shè)計(jì)的質(zhì)量越高。一種非常理想的狀態(tài)是,無(wú)論客戶需求怎樣變化,軟件只需進(jìn)行適當(dāng)?shù)男薷木湍軌蜻m應(yīng)。但這之所以稱之為理想狀態(tài),因?yàn)榭蛻粜枨笞兓怯写笥行〉摹H绻蛻粜枨笞兓浅4?,即使再好的設(shè)計(jì)也無(wú)法應(yīng)付,甚至重新開(kāi)發(fā)。然而,客戶需求的適當(dāng)變化,一個(gè)合理的設(shè)計(jì)可以使得變更代價(jià)最小化,延續(xù)我們?cè)O(shè)計(jì)的軟件的生命力。

1)通過(guò)提高代碼復(fù)用提高可維護(hù)性

我曾經(jīng)遇到過(guò)這樣一件事,我要維護(hù)的一個(gè)系統(tǒng)因?yàn)閼?yīng)用范圍的擴(kuò)大,它對(duì)機(jī)關(guān)級(jí)次的計(jì)算方式需要改變一種策略。如果這個(gè)項(xiàng)目統(tǒng)一采用一段公用方法來(lái)計(jì)算機(jī)關(guān)級(jí)次,這樣一個(gè)修改實(shí)在太簡(jiǎn)單了,就是修改這個(gè)公用方法即可。但是,事實(shí)卻不一樣,對(duì)機(jī)關(guān)級(jí)次計(jì)算的代碼遍布整個(gè)項(xiàng)目,甚至有些還寫(xiě)入到了那些復(fù)雜的SQL語(yǔ)句中。在這樣一種情況下,這樣一個(gè)需求的修改無(wú)異于需要遍歷這個(gè)項(xiàng)目代碼。這樣一個(gè)實(shí)例顯示了一個(gè)項(xiàng)目代碼復(fù)用的重要,然而不幸的是,代碼無(wú)法很好復(fù)用的情況遍布我們所有的項(xiàng)目。代碼復(fù)用的道理十分簡(jiǎn)單,但要具體運(yùn)作起來(lái)非常復(fù)雜,它除了需要很好的代碼規(guī)劃,還需要持續(xù)地代碼重構(gòu)。

對(duì)整個(gè)系統(tǒng)的整體分析與合理規(guī)劃可以根本地保證代碼復(fù)用。系統(tǒng)分析師通過(guò)用例模型、領(lǐng)域模型、分析模型的一步一步分析,最后通過(guò)正向工程,生成系統(tǒng)需要設(shè)計(jì)的各種類(lèi)及其各自的屬性和方法。采用這種方法,功能被合理地劃分到這個(gè)類(lèi)中,可以很好地保證代碼復(fù)用。

采用以上方法雖然好,但技術(shù)難度較高,需要有高深的系統(tǒng)分析師,并不是所有項(xiàng)目都能普遍采用的,特別是時(shí)間比較緊張的項(xiàng)目。通過(guò)開(kāi)發(fā)人員在設(shè)計(jì)過(guò)程中的重構(gòu),也許更加實(shí)用。當(dāng)某個(gè)開(kāi)發(fā)人員在開(kāi)發(fā)一段代碼時(shí),發(fā)現(xiàn)該功能與前面已經(jīng)開(kāi)發(fā)功能相同,或者部分相同。這時(shí),這個(gè)開(kāi)發(fā)人員可以對(duì)前面已經(jīng)開(kāi)發(fā)的功能進(jìn)行重構(gòu),將可以通用的代碼提取出來(lái),進(jìn)行相應(yīng)的改造,使其具有一定的通用性,便于各個(gè)地方可以使用。

一些比較成功的項(xiàng)目組會(huì)指定一個(gè)專(zhuān)門(mén)管理通用代碼的人,負(fù)責(zé)收集和整理項(xiàng)目組中各個(gè)成員編寫(xiě)的、可以通用的代碼。這個(gè)負(fù)責(zé)人同時(shí)也應(yīng)當(dāng)具有一定的代碼編寫(xiě)功力,因?yàn)閷?zhuān)用代碼提升為通用代碼,或者以前使用該通用代碼的某個(gè)功能,由于業(yè)務(wù)變更,而對(duì)這個(gè)通用代碼的變更要求,都對(duì)這個(gè)負(fù)責(zé)人提出了很高的能力要求。

雖然后一種方式非常實(shí)用,但是它有些亡羊補(bǔ)牢的味道,不能從整體上對(duì)項(xiàng)目代碼進(jìn)行有效規(guī)劃。正因?yàn)閮煞N方法各有利弊,因此在項(xiàng)目中應(yīng)當(dāng)配合使用。

2)利用設(shè)計(jì)模式提高可變更性

對(duì)于初學(xué)者,軟件設(shè)計(jì)理論常常感覺(jué)晦澀難懂。一個(gè)快速提高軟件質(zhì)量的捷徑就是利用設(shè)計(jì)模式。這里說(shuō)的設(shè)計(jì)模式,不僅僅指經(jīng)典的32個(gè)模式,是一切前人總結(jié)的,我們可以利用的、更加廣泛的設(shè)計(jì)模式。

a. if…else…

這個(gè)我也不知道叫什么名字,最早是哪位大師總結(jié)的,它出現(xiàn)在Larman的《UML與模式應(yīng)用》,也出現(xiàn)在出現(xiàn)在Mardin的《敏捷軟件開(kāi)發(fā)》。它是這樣描述的:當(dāng)你發(fā)現(xiàn)你必須要設(shè)計(jì)這樣的代碼:“if…elseif…elseif…else…”時(shí),你應(yīng)當(dāng)想到你的代碼應(yīng)當(dāng)重構(gòu)一下了。我們先看看這樣的代碼有怎樣的特點(diǎn)。

  1. if(var.equals("A")){ doA(); }  
  2. else if(var.equals("B")){ doB(); }  
  3. else if(var.equals("C")){ doC(); }  
  4. else{ doD(); } 

這樣的代碼很常見(jiàn),也非常平常,我們大家都寫(xiě)過(guò)。但正是這樣平常才隱藏著我們永遠(yuǎn)沒(méi)有注意的問(wèn)題。問(wèn)題就在于,如果某一天這個(gè)選項(xiàng)不再僅僅是A、 B、C,而是增加了新的選項(xiàng),會(huì)怎樣呢?你也許會(huì)說(shuō),那沒(méi)有關(guān)系,我把代碼改改就行。然而事實(shí)上并非如此,在大型軟件研發(fā)與維護(hù)中有一個(gè)原則,每次的變更盡量不要去修改原有的代碼。如果我們重構(gòu)一下,能保證不修改原有代碼,僅僅增加新的代碼就能應(yīng)付選項(xiàng)的增加,這就增加了這段代碼的可維護(hù)性和可變更性,提高了代碼質(zhì)量。那么,我們應(yīng)當(dāng)如何去做呢?

經(jīng)過(guò)深入分析你會(huì)發(fā)現(xiàn),這里存在一個(gè)對(duì)應(yīng)關(guān)系,即A對(duì)應(yīng)doA(),B對(duì)應(yīng)doB()...如果將doA()、doB()、doC()...與原有代碼解耦,問(wèn)題就解決了。如何解耦呢?設(shè)計(jì)一個(gè)接口X以及它的實(shí)現(xiàn)A、B、C...每個(gè)類(lèi)都包含一個(gè)方法doX(),并且將doA()的代碼放到 A.doX()中,將doB()的代碼放到B.doX()中...經(jīng)過(guò)以上的重構(gòu),代碼還是這些代碼,效果卻完全不一樣了。我們只需要這樣寫(xiě):

  1. X x = factory.getBean(var); x.doX(); 

這樣就可以實(shí)現(xiàn)以上的功能了。我們看到這里有一個(gè)工廠,放著所有的A、B、C...并且與它們的key對(duì)應(yīng)起來(lái),并且寫(xiě)在配置文件中。如果出現(xiàn)新的選項(xiàng)時(shí),通過(guò)修改配置文件就可以無(wú)限制的增加下去。

這個(gè)模式雖然有效提高了代碼質(zhì)量,但是不能濫用,并非只要出現(xiàn)if…else…就需要使用。由于它使用了工廠,一定程度上增加了代碼復(fù)雜度,因此僅僅在選項(xiàng)較多,并且增加選項(xiàng)的可能性很大的情況下才可以使用。另外,要使用這個(gè)模式,繼承我在附件中提供的抽象類(lèi) XmlBuildFactoryFacade就可以快速建立一個(gè)工廠。如果你的項(xiàng)目放在spring或其它可配置框架中,也可以快速建立工廠。設(shè)計(jì)一個(gè) Map靜態(tài)屬性并使其V為這些A、B、C...這個(gè)工廠就建立起來(lái)了。

b. 策略模式

也許你看過(guò)策略模式(strategy model)的相關(guān)資料但沒(méi)有留下太多的印象。一個(gè)簡(jiǎn)單的例子可以讓你快速理解它。如果一個(gè)員工系統(tǒng)中,員工被分為臨時(shí)工和正式工并且在不同的地方相應(yīng)的行為不一樣。在設(shè)計(jì)它們的時(shí)候,你肯定設(shè)計(jì)一個(gè)抽象的員工類(lèi),并且設(shè)計(jì)兩個(gè)繼承類(lèi):臨時(shí)工和正式工。這樣,通過(guò)下溯類(lèi)型,可以在不同的地方表現(xiàn)出臨時(shí)工和正式工的各自行為。在另一個(gè)系統(tǒng)中,員工被分為了銷(xiāo)售人員、技術(shù)人員、管理人員并且也在不同的地方相應(yīng)的行為不一樣。同樣,我們?cè)谠O(shè)計(jì)時(shí)也是設(shè)計(jì)一個(gè)抽象的員工類(lèi),并且設(shè)計(jì)數(shù)個(gè)繼承類(lèi):銷(xiāo)售人員、技術(shù)人員、管理人員。現(xiàn)在,我們要把這兩個(gè)系統(tǒng)合并起來(lái),也就是說(shuō),在新的系統(tǒng)中,員工既被分為臨時(shí)工和正式工,又被分為了銷(xiāo)售人員、技術(shù)人員、管理人員,這時(shí)候如何設(shè)計(jì)。如果我們還是使用以往的設(shè)計(jì),我們將不得不設(shè)計(jì)很多繼承類(lèi):銷(xiāo)售臨時(shí)工、銷(xiāo)售正式工、技術(shù)臨時(shí)工、技術(shù)正式工。。。如此的設(shè)計(jì),在隨著劃分的類(lèi)型,以及每種類(lèi)型的選項(xiàng)的增多,呈笛卡爾增長(zhǎng)。通過(guò)以上一個(gè)系統(tǒng)的設(shè)計(jì),我們不得不發(fā)現(xiàn),我們以往學(xué)習(xí)的關(guān)于繼承的設(shè)計(jì)遇到了挑戰(zhàn)。

解決繼承出現(xiàn)的問(wèn)題,有一個(gè)最好的辦法,就是采用策略模式。在這個(gè)應(yīng)用中,員工之所以要分為臨時(shí)工和正式工,無(wú)非是因?yàn)樗鼈兊囊恍┬袨椴灰粯?,比如,發(fā)工資時(shí)的計(jì)算方式不同。如果我們?cè)谠O(shè)計(jì)時(shí)不將員工類(lèi)分為臨時(shí)工類(lèi)和正式工類(lèi),而僅僅只有員工類(lèi),只是在類(lèi)中增加“工資發(fā)放策略”。當(dāng)我們創(chuàng)建員工對(duì)象時(shí),根據(jù)員工的類(lèi)型,將“工資發(fā)放策略”設(shè)定為“臨時(shí)工策略”或“正式工策略”,在計(jì)算工資時(shí),只需要調(diào)用策略類(lèi)中的“計(jì)算工資”方法,其行為的表現(xiàn),也設(shè)計(jì)臨時(shí)工類(lèi)和正式工類(lèi)是一樣的。同樣的設(shè)計(jì)可以放到銷(xiāo)售人員策略、技術(shù)人員策略、管理人員策略中。一個(gè)通常的設(shè)計(jì)是,我們將某一個(gè)影響更大的、或者選項(xiàng)更少的屬性設(shè)計(jì)成繼承類(lèi),而將其它屬性設(shè)計(jì)成策略類(lèi),就可以很好的解決以上問(wèn)題。

一堂如何提高代碼質(zhì)量的培訓(xùn)課

使用策略模式,你同樣把代碼寫(xiě)活了,因?yàn)槟憧梢詿o(wú)限制地增加策略。但是,使用策略模式你同樣需要設(shè)計(jì)一個(gè)工廠——策略工廠。以上實(shí)例中,你需要設(shè)計(jì)一個(gè)發(fā)放工資策略工廠,并且在工廠中將“臨時(shí)工”與“臨時(shí)工策略”對(duì)應(yīng)起來(lái),將“正式工”與“正式工策略”對(duì)應(yīng)起來(lái)。

c. 適配器模式

我的筆記本是港貨,它的插頭與我們常用的插座不一樣,所有我出差的時(shí)候我必須帶一個(gè)適配器,才能使用不同地方的插座。這是一個(gè)對(duì)適配器模式最經(jīng)典的描述。當(dāng)我們?cè)O(shè)計(jì)的系統(tǒng)要與其它系統(tǒng)交互,或者我們?cè)O(shè)計(jì)的模塊要與其它模塊交互時(shí),這種交互可能是調(diào)用一個(gè)接口,或者交換一段數(shù)據(jù),接受方常常因發(fā)送方對(duì)協(xié)議的變更而頻繁變更。這種變更,可能是接受方來(lái)源的變更,比如原來(lái)是A系統(tǒng),現(xiàn)在變成B系統(tǒng)了;也可能是接受方自身的代碼變更,如原來(lái)的接口現(xiàn)在增加了一個(gè)參數(shù)。由于發(fā)送方的變更常常導(dǎo)致接受方代碼的不穩(wěn)定,即頻繁跟著修改,為接受方的維護(hù)帶來(lái)困難。

遇到這樣的問(wèn)題,一個(gè)有經(jīng)驗(yàn)的程序員馬上想到的就是采用適配器模式。在設(shè)計(jì)時(shí),我方的接口按照某個(gè)協(xié)議編寫(xiě),并且保持固定不變。然后,在與真正對(duì)方接口時(shí),在前段設(shè)計(jì)一個(gè)適配器類(lèi),一旦對(duì)方協(xié)議發(fā)生變更,我可以換個(gè)適配器,將新協(xié)議轉(zhuǎn)換成原協(xié)議,問(wèn)題就解決了。適配器模式應(yīng)當(dāng)包含一個(gè)接口和它的實(shí)現(xiàn)類(lèi)。接口應(yīng)當(dāng)包含一個(gè)本系統(tǒng)要調(diào)用的方法,而它的實(shí)現(xiàn)類(lèi)分別是與A系統(tǒng)接口的適配器、與B系統(tǒng)接口的適配器...

一堂如何提高代碼質(zhì)量的培訓(xùn)課

我曾經(jīng)在一個(gè)項(xiàng)目中需要與另一個(gè)系統(tǒng)接口,起初那個(gè)系統(tǒng)通過(guò)一個(gè)數(shù)據(jù)集的方式為我提供數(shù)據(jù),我寫(xiě)了一個(gè)接收數(shù)據(jù)集的適配器;后來(lái)改為用一個(gè)XML數(shù)據(jù)流的形式,我又寫(xiě)了一個(gè)接收XML的適配器。雖然為我提供數(shù)據(jù)的方式不同,但是經(jīng)過(guò)適配器轉(zhuǎn)換后,輸出的數(shù)據(jù)是一樣的。通過(guò)在spring中的配置,我可以靈活地切換到底是使用哪個(gè)適配器。

d. 模板模式

32個(gè)經(jīng)典模式中的模板模式,對(duì)開(kāi)發(fā)者的代碼規(guī)劃能力提出了更高的要求,它要求開(kāi)發(fā)者對(duì)自己開(kāi)發(fā)的所有代碼有一個(gè)相互聯(lián)系和從中抽象的能力,從各個(gè)不同的模塊和各個(gè)不同的功能中,抽象出其過(guò)程比較一致的通用流程,最終形成模板。譬如說(shuō),讀取XML并形成工廠,是許多模塊常常要使用的功能。它們雖然有各自的不同,但是總體流程都是一樣的:讀取XML文件、解析XML數(shù)據(jù)流、形成工廠。正因?yàn)橛羞@樣的特征,它們可以使用共同的模板,那么,什么是模板模式呢?

模板模式(Template Model)通常有一個(gè)抽象類(lèi)。在這個(gè)抽象類(lèi)中,通常有一個(gè)主函數(shù),按照一定地順序去調(diào)用其它函數(shù)。而其它函數(shù)往往是某這個(gè)連續(xù)過(guò)程中的各個(gè)步驟,如以上實(shí)例中的讀取XML文件、解析XML數(shù)據(jù)流、形成工廠等步驟。由于這是一個(gè)抽象類(lèi),這些步驟函數(shù)可以是抽象函數(shù)。抽象類(lèi)僅僅定義了整個(gè)過(guò)程的執(zhí)行順序,以及一些可以通用的步驟(如讀取XML文件和解析XML數(shù)據(jù)流),而另一些比較個(gè)性的步驟,則由它的繼承類(lèi)自己去完成(如上例中的“形成工廠”,由于各個(gè)工廠各不一樣,因此由各自的繼承類(lèi)自己去決定它的工廠是怎樣形成的)。

一堂如何提高代碼質(zhì)量的培訓(xùn)課

各個(gè)繼承類(lèi)可以根據(jù)自己的需要,通過(guò)重載重新定義各個(gè)步驟函數(shù)。但是,模板模式要求不能重載主函數(shù),因此正規(guī)的模板模式其主函數(shù)應(yīng)當(dāng)是 final(雖然我們常常不這么寫(xiě))。另外,模板模式還允許你定義的這個(gè)步驟中,有些步驟是可選步驟。對(duì)與可選步驟,我們通常稱為“鉤子(hood)”。它在編寫(xiě)時(shí),在抽象類(lèi)中并不是一個(gè)抽象函數(shù),但卻是一個(gè)什么都不寫(xiě)的空函數(shù)。繼承類(lèi)在編寫(xiě)時(shí),如果需要這個(gè)步驟則重載這個(gè)函數(shù),否則就什么也不寫(xiě),進(jìn)而在執(zhí)行的時(shí)候也如同什么都沒(méi)有執(zhí)行。

通過(guò)以上對(duì)模板模式的描述可以發(fā)現(xiàn),模板模式可以大大地提高我們的代碼復(fù)用程度。

以上一些常用設(shè)計(jì)模式,都能使我們快速提高代碼質(zhì)量。還是那句話,設(shè)計(jì)模式不是什么高深的東西,恰恰相反,它是初學(xué)者快速提高的捷徑。然而,如果說(shuō)提高代碼復(fù)用是提高代碼質(zhì)量的初階,使用設(shè)計(jì)模式也只能是提高代碼質(zhì)量的中階。那么,什么是高階呢?我認(rèn)為是那些分析設(shè)計(jì)理論,更具體地說(shuō),就是職責(zé)驅(qū)動(dòng)設(shè)計(jì)和領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。

3)職責(zé)驅(qū)動(dòng)設(shè)計(jì)和領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)

前面我提到,當(dāng)我們嘗試寫(xiě)一些復(fù)雜功能的時(shí)候,我們把功能分解成一個(gè)個(gè)相對(duì)獨(dú)立的函數(shù)。但是,應(yīng)當(dāng)將這些函數(shù)分配到哪個(gè)類(lèi)中呢?也就是系統(tǒng)中的所有類(lèi)都應(yīng)當(dāng)擁有哪些函數(shù)呢?或者說(shuō)應(yīng)當(dāng)表現(xiàn)出哪些行為呢?答案就在這里:以職責(zé)為中心,根據(jù)職責(zé)分配行為。我們?cè)诜治鱿到y(tǒng)時(shí),首先是根據(jù)客戶需求進(jìn)行用例分析,然后根據(jù)用例繪制領(lǐng)域模式和分析模型,整個(gè)系統(tǒng)最主要的類(lèi)就形成了。通過(guò)以上分析形成的類(lèi),往往和現(xiàn)實(shí)世界的對(duì)象是對(duì)應(yīng)的。正因?yàn)槿绱耍浖澜绲倪@些類(lèi)也具有了與現(xiàn)實(shí)世界的對(duì)象相對(duì)應(yīng)的職責(zé),以及在這些職責(zé)范圍內(nèi)的行為。

職責(zé)驅(qū)動(dòng)設(shè)計(jì)(Responsibility Drive Design,RDD)是Craig Larman在他的經(jīng)典著作《UML和模式應(yīng)用》中提出的。職責(zé)驅(qū)動(dòng)設(shè)計(jì)的核心思想,就是我們?cè)趯?duì)一個(gè)系統(tǒng)進(jìn)行分析設(shè)計(jì)的時(shí)候,應(yīng)當(dāng)以職責(zé)為中心,根據(jù)職責(zé)分配行為。這種思想首先要求我們?cè)O(shè)計(jì)的所有軟件世界的對(duì)象,應(yīng)當(dāng)與現(xiàn)實(shí)世界盡量保持一致,他稱之為“低表示差異”。有了低表示差異,一方面提高了代碼的可讀性,另一方面,當(dāng)業(yè)務(wù)發(fā)生變更的時(shí)候,也可以根據(jù)實(shí)際情況快速應(yīng)對(duì)變更。

Craig Larman在提出職責(zé)驅(qū)動(dòng)設(shè)計(jì)理論的同時(shí),還提出了GRASP設(shè)計(jì)模式,來(lái)豐富這個(gè)理論。在GRASP設(shè)計(jì)模式中,我認(rèn)為,低耦合、高內(nèi)聚、信息專(zhuān)家模式最有用。

繼Craig Larman提出的職責(zé)驅(qū)動(dòng)設(shè)計(jì)數(shù)年之后,另一位大師提出了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain Drive Design,DDD)是Eric Evans在他的同名著作《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中提出的。在之前的設(shè)計(jì)理論中,領(lǐng)域模型是從用例模型到分析模型之間的一種中間模型,也就是從需求分析到軟件開(kāi)發(fā)之間的一種中間模型。這么一個(gè)中間模型,既不是需求階段的重要產(chǎn)物,在開(kāi)發(fā)階段也不以它作為標(biāo)準(zhǔn)進(jìn)行開(kāi)發(fā),僅僅是作為參考,甚至給人感覺(jué)有一些多余。但是,Evans在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,將它放到了一個(gè)無(wú)比重要的位置。按照領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的理論,在需求分析階段,需求分析人員使用領(lǐng)域模型與客戶進(jìn)行溝通;在設(shè)計(jì)開(kāi)發(fā)階段,開(kāi)發(fā)人員使用領(lǐng)域模型指導(dǎo)設(shè)計(jì)開(kāi)發(fā);在運(yùn)行維護(hù)和二次開(kāi)發(fā)階段,維護(hù)和二次開(kāi)發(fā)人員使用領(lǐng)域模型理解和熟悉系統(tǒng),并指導(dǎo)他們進(jìn)行維護(hù)和二次開(kāi)發(fā)。總之,在整個(gè)軟件開(kāi)發(fā)的生命周期中,領(lǐng)域模型都成為了最核心的內(nèi)容。

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)繼承了職責(zé)驅(qū)動(dòng)設(shè)計(jì)。在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中強(qiáng)調(diào)的,依然是低表示差異,以及職責(zé)的分配。但是,如何做到低表示差異呢?如何完成職責(zé)分配呢?領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)給了我們完美的答案,那就是建立領(lǐng)域模型。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)改變了我們的設(shè)計(jì)方式。在需求分析階段,用例模型已不再是這個(gè)階段的核心,而是建立領(lǐng)域模型。在開(kāi)發(fā)和二次開(kāi)發(fā)階段,開(kāi)發(fā)人員也不再是一埋頭地猛扎進(jìn)程序堆里開(kāi)始編程,而是首先細(xì)致地進(jìn)行領(lǐng)域模型分析。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)強(qiáng)調(diào)持續(xù)精化,使領(lǐng)域模型不再是一旦完成分析就扔在一邊不再理會(huì)的圖紙,而是在不斷理解業(yè)務(wù)的基礎(chǔ)上不斷修改和精化領(lǐng)域模型,進(jìn)而驅(qū)動(dòng)我們代碼的精化。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)強(qiáng)調(diào)的不再是一次軟件開(kāi)發(fā)過(guò)程中我們要做的工作,它看得更加長(zhǎng)遠(yuǎn),它強(qiáng)調(diào)的是一套軟件在相當(dāng)長(zhǎng)一段時(shí)間內(nèi)持續(xù)升級(jí)的過(guò)程中我們應(yīng)當(dāng)做的工作。我認(rèn)為,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)是提高代碼質(zhì)量的最高等級(jí)。當(dāng)時(shí),使用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)進(jìn)行軟件開(kāi)發(fā)是一場(chǎng)相當(dāng)巨大的改革,它顛覆了我們過(guò)去的所有開(kāi)發(fā)模式,我們必須腳踏實(shí)地地一步一步去實(shí)踐和改變。

#p#

職責(zé)驅(qū)動(dòng)設(shè)計(jì)

隨著軟件業(yè)的不斷發(fā)展,隨著軟件需求的不斷擴(kuò)大,軟件所管理的范圍也在不斷拓寬。過(guò)去一個(gè)軟件僅僅管理一臺(tái)電腦的一個(gè)小小的功能,而現(xiàn)在被擴(kuò)展到了一個(gè)企業(yè)、一個(gè)行業(yè)、一個(gè)產(chǎn)業(yè)鏈。過(guò)去我們開(kāi)發(fā)一套軟件,只有少量的二次開(kāi)發(fā),當(dāng)它使用到一定時(shí)候我們就拋棄掉重新又開(kāi)發(fā)一套。現(xiàn)在,隨著用戶對(duì)軟件依賴程度的不斷加大,我們很難說(shuō)拋棄一套軟件重新開(kāi)發(fā)了,更多的是在一套軟件中持續(xù)改進(jìn),使這套軟件的生命周期持續(xù)數(shù)年以及數(shù)個(gè)版本。正是因?yàn)檐浖I(yè)面臨著如此巨大的壓力,我們的代碼質(zhì)量,我們開(kāi)發(fā)的軟件擁有的可變更性和持續(xù)改進(jìn)的能力,成為軟件制勝的關(guān)鍵因素,令我們不能不反思。

代碼質(zhì)量評(píng)價(jià)的關(guān)鍵指標(biāo):低耦合,高內(nèi)聚

耦合就是對(duì)某元素與其它元素之間的連接、感知和依賴的量度。耦合包括:

1.元素B是元素A的屬性,或者元素A引用了元素B的實(shí)例(這包括元素A調(diào)用的某個(gè)方法,其參數(shù)中包含元素B)。

2.元素A調(diào)用了元素B的方法。

3.元素A直接或間接成為元素B的子類(lèi)。

4.元素A是接口B的實(shí)現(xiàn)。

如果一個(gè)元素過(guò)于依賴其它元素,一旦它所依賴的元素不存在,或者發(fā)生變更,則該元素將不能再正常運(yùn)行,或者不得不相應(yīng)地進(jìn)行變更。因此,耦合將大大影響代碼的通用性和可變更性。

內(nèi)聚,更為專(zhuān)業(yè)的說(shuō)法叫功能內(nèi)聚,是對(duì)軟件系統(tǒng)中元素職責(zé)相關(guān)性和集中度的度量。如果元素具有高度相關(guān)的職責(zé),除了這些職責(zé)內(nèi)的任務(wù),沒(méi)有其它過(guò)多的工作,那么該元素就具有高內(nèi)聚性,反之則為低內(nèi)聚性。內(nèi)聚就像一個(gè)專(zhuān)橫的管理者,它只做自己職責(zé)范圍內(nèi)的事,而將其它與它相關(guān)的事情,分配給別人去做。

高質(zhì)量的代碼要求我們的代碼保持低耦合、高內(nèi)聚。但是,這個(gè)要求是如此的抽象與模糊,如何才能做到這些呢?軟件大師們告訴我們了許多方法,其中之一就是Craig Larman的職責(zé)驅(qū)動(dòng)設(shè)計(jì)。

職責(zé)驅(qū)動(dòng)設(shè)計(jì)(Responsibility Drive Design,RDD)是Craig Larman在他的經(jīng)典著作《UML和模式應(yīng)用》中提出的。要理解職責(zé)驅(qū)動(dòng)設(shè)計(jì),我們首先要理解“低表示差異”。

低表示差異

我們開(kāi)發(fā)的應(yīng)用軟件實(shí)際上是對(duì)現(xiàn)實(shí)世界的模擬,因此,軟件世界與現(xiàn)實(shí)世界存在著必然的聯(lián)系。當(dāng)我們?cè)谶M(jìn)行需求分析的時(shí)候,需求分析員實(shí)際上是從客戶那里在了解現(xiàn)實(shí)世界事物的規(guī)則、工作的流程。如果我們?cè)谲浖治龊驮O(shè)計(jì)的過(guò)程中,將軟件世界與現(xiàn)實(shí)世界緊密地聯(lián)系到一起,我們的軟件將更加本色地還原事物最本質(zhì)的規(guī)律。這樣的設(shè)計(jì),就稱之為“低表示差異”。

一堂如何提高代碼質(zhì)量的培訓(xùn)課

采用“低表示差異”進(jìn)行軟件設(shè)計(jì),現(xiàn)實(shí)世界有什么事物,就映射為軟件世界的各種對(duì)象(類(lèi));現(xiàn)實(shí)世界的事物擁有什么樣的職責(zé),在軟件世界里的對(duì)象就擁有什么樣的職責(zé);在現(xiàn)實(shí)世界中的事物,因?yàn)樗穆氊?zé)而產(chǎn)生的行為,在軟件世界中就反映為對(duì)象所擁有的函數(shù)。

低表示差異,使分析設(shè)計(jì)者對(duì)軟件的分析和設(shè)計(jì)更加簡(jiǎn)單,思路更加清晰;使代碼更加可讀,閱讀者更加易于理解;更重要的是,當(dāng)需求發(fā)生變更,或者業(yè)務(wù)產(chǎn)生擴(kuò)展時(shí),設(shè)計(jì)者只需要遵循事物本來(lái)的面貌去思考和修改軟件,使軟件更加易于變更和擴(kuò)展。

角色、職責(zé)、協(xié)作

理解了“低表示差異”,現(xiàn)在我們來(lái)看看我們應(yīng)當(dāng)如何運(yùn)用職責(zé)驅(qū)動(dòng)設(shè)計(jì)進(jìn)行分析和設(shè)計(jì)。首先,我們通過(guò)與客戶的溝通和對(duì)業(yè)務(wù)需求的了解,從中提取出現(xiàn)實(shí)世界中的關(guān)鍵事物以及相互之間的關(guān)系。這個(gè)過(guò)程我們通常通過(guò)建立領(lǐng)域模型來(lái)完成。領(lǐng)域模型建立起來(lái)以后,通過(guò)諸如Rational Rose這樣的設(shè)計(jì)軟件的正向工程,生成了我們?cè)谲浖到y(tǒng)中最初始的軟件類(lèi)。這些軟件類(lèi),由于每個(gè)都扮演著現(xiàn)實(shí)世界中的一個(gè)具體的角色,因而賦予了各自的職責(zé)。前面我已經(jīng)提到,如果你的系統(tǒng)采用職責(zé)驅(qū)動(dòng)設(shè)計(jì)的思想進(jìn)行設(shè)計(jì)開(kāi)發(fā),作為一個(gè)好的習(xí)慣,你應(yīng)當(dāng)在每一個(gè)軟件類(lèi)的注釋首行,清楚地描述該軟件類(lèi)的職責(zé)。

當(dāng)我們完成了系統(tǒng)中軟件類(lèi)的制訂,分配好了各自的職責(zé),我們就應(yīng)該開(kāi)始根據(jù)軟件需求,編寫(xiě)各個(gè)軟件類(lèi)的功能。在前面我給大家提出了一個(gè)建議,就是不要在一個(gè)函數(shù)中編寫(xiě)大段的代碼。編寫(xiě)大段的代碼,通常會(huì)降低代碼的內(nèi)聚度,因?yàn)檫@些代碼中將包含不是該軟件類(lèi)應(yīng)當(dāng)完成的工作。作為一個(gè)有經(jīng)驗(yàn)的開(kāi)發(fā)人員,在編寫(xiě)一個(gè)功能時(shí),首先應(yīng)當(dāng)對(duì)功能進(jìn)行分解。一段稍微復(fù)雜的功能,通常都可以被分解成一個(gè)個(gè)相對(duì)獨(dú)立的步驟。步驟與步驟之間存在著交互,那就是數(shù)據(jù)的輸入輸出。通過(guò)以上的分解,每一個(gè)步驟將形成一個(gè)獨(dú)立的函數(shù),并且使用一個(gè)可以表明這個(gè)步驟意圖的釋義函數(shù)名。接下來(lái),我們應(yīng)當(dāng)考慮的,就是應(yīng)當(dāng)將這些函數(shù)交給誰(shuí)。它們有可能交給原軟件類(lèi),也有可能交給其它軟件類(lèi),其分配的原則是什么呢?答案是否清楚,那就是職責(zé)。每個(gè)軟件類(lèi)代表現(xiàn)實(shí)世界的一個(gè)事物,或者說(shuō)一個(gè)角色。在現(xiàn)實(shí)世界中這個(gè)任務(wù)應(yīng)當(dāng)由誰(shuí)來(lái)完成,那么在軟件世界中,這個(gè)函數(shù)就應(yīng)當(dāng)分配給相應(yīng)的那個(gè)軟件類(lèi)。

通過(guò)以上步驟的分解,一個(gè)功能就分配給了多個(gè)軟件類(lèi),相互協(xié)作地完成這個(gè)功能。這樣的分析和設(shè)計(jì),其代碼一定是高內(nèi)聚的和高可讀性的。同時(shí),當(dāng)需求發(fā)生變更的時(shí)候,設(shè)計(jì)者通過(guò)對(duì)現(xiàn)實(shí)世界的理解,可以非常輕松地找到那個(gè)需要修改的軟件類(lèi),而不會(huì)影響其它類(lèi),因而也就變得易維護(hù)、易變更和低耦合了。

說(shuō)了這么多,舉一個(gè)實(shí)例也許更能幫助理解。拿一個(gè)員工工資系統(tǒng)來(lái)說(shuō)吧。當(dāng)人力資源在發(fā)放一個(gè)月工資的時(shí)候,以及離職的員工肯定不能再發(fā)放工資了。在系統(tǒng)設(shè)計(jì)的期初,開(kāi)發(fā)人員商量好,在員工信息中設(shè)定一個(gè)“離職標(biāo)志”字段。編寫(xiě)工資發(fā)放的開(kāi)發(fā)人員通過(guò)查詢,將“離職標(biāo)志”為false的員工查詢出來(lái),并為他們計(jì)算和發(fā)放工資。但是,隨著這個(gè)系統(tǒng)的不斷使用,編寫(xiě)員工管理的開(kāi)發(fā)人員發(fā)現(xiàn),“離職標(biāo)志”字段已經(jīng)不能滿足客戶的需求,因而將“離職標(biāo)志”字段廢棄,并增加了一個(gè)“離職時(shí)間”字段來(lái)管理離職的員工。然而,編寫(xiě)工資發(fā)放的開(kāi)發(fā)人員并不知道這樣的變更,依然使用著“離職標(biāo)志”字段。顯然,這樣的結(jié)果就是,軟件系統(tǒng)開(kāi)始對(duì)離職員工發(fā)放工資了。仔細(xì)分析這個(gè)問(wèn)題的原因,我們不難發(fā)現(xiàn),確認(rèn)員工是否離職,并不是“發(fā)放工資”軟件類(lèi)應(yīng)當(dāng)完成的工作,而應(yīng)當(dāng)是 “員工管理”軟件類(lèi)應(yīng)當(dāng)完成的。如果將“獲取非離職員工”的任務(wù)交給“員工管理”軟件類(lèi),而“發(fā)放工資”軟件類(lèi)僅僅只是去調(diào)用,那么離職功能由“離職標(biāo)志”字段改為了“離職時(shí)間”字段,其實(shí)就與“發(fā)放工資”軟件類(lèi)毫無(wú)關(guān)系。而作為“員工管理”的開(kāi)發(fā)人員,一旦發(fā)生這樣的變更,他當(dāng)然知道去修改自己相應(yīng)的 “獲取非離職員工”函數(shù),這樣就不會(huì)發(fā)生以上問(wèn)題。通過(guò)這樣一個(gè)實(shí)例,也許你能夠理解“職責(zé)驅(qū)動(dòng)設(shè)計(jì)”的精要與作用了吧。

職責(zé)分配與信息專(zhuān)家

通過(guò)以上對(duì)職責(zé)驅(qū)動(dòng)設(shè)計(jì)的講述,我們不難發(fā)現(xiàn),職責(zé)驅(qū)動(dòng)設(shè)計(jì)的精要就是職責(zé)分配。但是,在紛繁復(fù)雜的軟件設(shè)計(jì)中,如何進(jìn)行職責(zé)分配常常令我們迷惑。幸運(yùn)的是,Larman大師清楚地認(rèn)識(shí)到了這一點(diǎn)。在他的著作中,信息專(zhuān)家模式為我們提供了幫助。

信息專(zhuān)家模式(又稱為專(zhuān)家模式)告訴我們,在分析設(shè)計(jì)中,應(yīng)當(dāng)將職責(zé)分配給軟件系統(tǒng)中的這樣一個(gè)軟件類(lèi),它擁有實(shí)現(xiàn)這個(gè)職責(zé)所必須的信息。我們稱這個(gè)軟件類(lèi),叫“信息專(zhuān)家”。用更加簡(jiǎn)短的話說(shuō),就是將職責(zé)分配給信息專(zhuān)家。

為什么我們要將職責(zé)分配給信息專(zhuān)家呢?我們用上面的例子來(lái)說(shuō)明吧。當(dāng)“發(fā)放工資”軟件類(lèi)需要獲取非離職員工時(shí),“員工管理”軟件類(lèi)就是“獲取非離職員工”任務(wù)的信息專(zhuān)家,因?yàn)樗莆罩袉T工的信息。假設(shè)我們不將“獲取非離職員工”的任務(wù)交給“員工管理”軟件類(lèi),而是另一個(gè)軟件類(lèi)X,那么,為了獲取員工信息,軟件類(lèi)X不得不訪問(wèn)“員工管理”軟件類(lèi),從而使“發(fā)放工資”與X耦合,X又與“員工管理”耦合。這樣的設(shè)計(jì),不如直接將“獲取非離職員工”的任務(wù)交給“員工管理”軟件類(lèi),使得“發(fā)放工資”僅僅與“員工管理”耦合,從而有效地降低了系統(tǒng)的整體耦合度。

總之,采用“職責(zé)驅(qū)動(dòng)設(shè)計(jì)”的思路,為我們提高軟件開(kāi)發(fā)質(zhì)量、可讀性、可維護(hù)性,以及保持軟件的持續(xù)發(fā)展,提供了一個(gè)廣闊的空間。

原文鏈接:http://fangang.iteye.com/blog/564662

責(zé)任編輯:張偉 來(lái)源: fangang的博客
相關(guān)推薦

2014-10-15 10:41:17

喬布斯管理蘋(píng)果

2010-03-18 13:44:45

Java JVM進(jìn)程

2023-01-06 18:31:46

準(zhǔn)確命名

2022-08-04 09:01:45

TypeScriptMicrosoft

2014-04-03 17:16:08

紅帽

2016-02-23 11:03:03

代碼質(zhì)量編寫(xiě)函數(shù)

2016-02-24 16:03:34

代碼質(zhì)量編寫(xiě)函數(shù)

2012-11-07 09:48:26

2020-06-01 17:56:23

華為教育兒童節(jié)

2017-08-18 13:02:15

大數(shù)據(jù)數(shù)據(jù)質(zhì)量

2017-12-22 10:55:35

教育云

2019-04-03 18:09:24

智慧教室教育信息化銳捷

2015-05-06 09:20:34

代碼質(zhì)量代碼審查實(shí)踐

2018-06-27 16:12:13

智慧課堂

2009-06-03 15:31:40

Eclipse插件提高代碼質(zhì)量

2012-04-09 15:40:31

PHP

2012-07-30 13:15:18

代碼

2015-07-15 10:27:48

Android代碼質(zhì)量工具

2011-07-28 10:24:52

2011艾瑞深圳峰會(huì)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)