|
今天這堂培訓(xùn)課講什么呢?我既不講Spring,也不講Hibernate,更不講Ext,我不講任何一個(gè)具體的技術(shù)。我們拋開任何具體的技術(shù),來談?wù)勅绾翁岣叽a質(zhì)量。如何提高代碼質(zhì)量,相信不僅是在座所有人苦惱的事情,也是所有軟件項(xiàng)目苦惱的事情。如何提高代碼質(zhì)量呢,我認(rèn)為我們首先要理解什么是高質(zhì)量的代碼。
高質(zhì)量代碼的三要素
我們?cè)u(píng)價(jià)高質(zhì)量代碼有三要素:可讀性、可維護(hù)性、可變更性。我們的代碼要一個(gè)都不能少地達(dá)到了這三要素的要求才能算高質(zhì)量的代碼。
1. 可讀性強(qiáng)
一提到可讀性似乎有一些老生常談的味道,但令人沮喪的是,雖然大家一而再,再而三地強(qiáng)調(diào)可讀性,但我們的代碼在可讀性方面依然做得非常糟糕。由于工作的需要,我常常需要去閱讀他人的代碼,維護(hù)他人設(shè)計(jì)的模塊。每當(dāng)我看到大段大段、密密麻麻的代碼,而且還沒有任何的注釋時(shí)常常感慨不已,深深體會(huì)到了這項(xiàng)工作的重要。由于分工的需要,我們寫的代碼難免需要?jiǎng)e人去閱讀和維護(hù)的。而對(duì)于許多程序員來說,他們很少去閱讀和維護(hù)別人的代碼。正因?yàn)槿绱耍麄兒苌訇P(guān)注代碼的可讀性,也對(duì)如何提高代碼的可讀性缺乏切身體會(huì)。有時(shí)即使為代碼編寫了注釋,也常常是注釋語言晦澀難懂形同天書,令閱讀者反復(fù)斟酌依然不明其意。針對(duì)以上問題,我給大家以下建議:
1)不要編寫大段的代碼
如果你有閱讀他人代碼的經(jīng)驗(yàn),當(dāng)你看到別人寫的大段大段的代碼,而且還不怎么帶注釋,你是怎樣的感覺,是不是“嗡”地一聲頭大。各種各樣的功能糾纏在一個(gè)方法中,各種變量來回調(diào)用,相信任何人多不會(huì)認(rèn)為它是高質(zhì)量的代碼,但卻頻繁地出現(xiàn)在我們編寫的程序了。如果現(xiàn)在你再回顧自己寫過的代碼,你會(huì)發(fā)現(xiàn),稍微編寫一個(gè)復(fù)雜的功能,幾百行的代碼就出去了。一些比較好的辦法就是分段。將大段的代碼經(jīng)過整理,分為功能相對(duì)獨(dú)立的一段又一段,并且在每段的前端編寫一段注釋。這樣的編寫,比前面那些雜亂無章的大段代碼確實(shí)進(jìn)步了不少,但它們?cè)诠δ塥?dú)立性、可復(fù)用性、可維護(hù)性方面依然不盡人意。從另一個(gè)比較專業(yè)的評(píng)價(jià)標(biāo)準(zhǔn)來說,它沒有實(shí)現(xiàn)低耦合、高內(nèi)聚。我給大家的建議是,將這些相對(duì)獨(dú)立的段落另外封裝成一個(gè)又一個(gè)的函數(shù)。
許多大師在自己的經(jīng)典書籍中,都鼓勵(lì)我們?cè)诰帉懘a的過程中應(yīng)當(dāng)養(yǎng)成不斷重構(gòu)的習(xí)慣。我們?cè)诰帉懘a的過程中常常要編寫一些復(fù)雜的功能,起初是寫在一個(gè)類的一個(gè)函數(shù)中。隨著功能的逐漸展開,我們開始對(duì)復(fù)雜功能進(jìn)行歸納整理,整理出了一個(gè)又一個(gè)的獨(dú)立功能。這些獨(dú)立功能有它與其它功能相互交流的輸入輸出數(shù)據(jù)。當(dāng)我們分析到此處時(shí),我們會(huì)非常自然地要將這些功能從原函數(shù)中分離出來,形成一個(gè)又一個(gè)獨(dú)立的函數(shù),供原函數(shù)調(diào)用。在編寫這些函數(shù)時(shí),我們應(yīng)當(dāng)仔細(xì)思考一下,為它們?nèi)∫粋€(gè)釋義名稱,并為它們編寫注釋(后面還將詳細(xì)討論這個(gè)問題)。另一個(gè)需要思考的問題是,這些函數(shù)應(yīng)當(dāng)放到什么地方。這些函數(shù)可能放在原類中,也可能放到其它相應(yīng)職責(zé)的類中,其遵循的原則應(yīng)當(dāng)是“職責(zé)驅(qū)動(dòng)設(shè)計(jì)”(后面也將詳細(xì)描述)。
下面是我編寫的一個(gè)從XML文件中讀取數(shù)據(jù),將其生成工廠的一個(gè)類。這個(gè)類最主要的一段程序就是初始化工廠,該功能歸納起來就是三部分功能:用各種方式嘗試讀取文件、以DOM的方式解析XML數(shù)據(jù)流、生成工廠。而這些功能被我歸納整理后封裝在一個(gè)不同的函數(shù)中,并且為其取了釋義名稱和編寫了注釋:
Java代碼
/** * 初始化工廠。根據(jù)路徑讀取XML文件,將XML文件中的數(shù)據(jù)裝載到工廠中 * @param path XML的路徑 */public void initFactory(String path){ if(findOnlyOneFileByClassPath(path)){return;} if(findResourcesByUrl(path)){return;} if(findResourcesByFile(path)){return;} this.paths = new String[]{path};}/*** 初始化工廠。根據(jù)路徑列表依次讀取XML文件,將XML文件中的數(shù)據(jù)裝載到工廠中* @param paths 路徑列表*/public void initFactory(String[] paths){ for(int i=0; i<paths.length; i++){ initFactory(paths[i]); } this.paths = paths;}/*** 重新初始化工廠,初始化所需的參數(shù),為上一次初始化工廠所用的參數(shù)。*/public void reloadFactory(){initFactory(this.paths);}/*** 采用ClassLoader的方式試圖查找一個(gè)文件,并調(diào)用<code>readXmlStream()</code>進(jìn)行解析* @param path XML文件的路徑* @return 是否成功*/protected boolean findOnlyOneFileByClassPath(String path){ boolean success = false; try { Resource resource = new ClassPathResource(path, this.getClass()); resource.setFilter(this.getFilter()); InputStream is = resource.getInputStream(); if(is==null){return false;} readXmlStream(is); success = true; } catch (SAXException e) { log.debug("Error when findOnlyOneFileByClassPath:"+path,e); } catch (IOException e) { log.debug("Error when findOnlyOneFileByClassPath:"+path,e); } catch (ParserConfigurationException e) { log.debug("Error when findOnlyOneFileByClassPath:"+path,e); } return success;}/*** 采用URL的方式試圖查找一個(gè)目錄中的所有XML文件,并調(diào)用<code>readXmlStream()</code>進(jìn)行解析* @param path XML文件的路徑* @return 是否成功*/protected boolean findResourcesByUrl(String path){ boolean success = false; try { ResourcePath resourcePath = new PathMatchResource(path, this.getClass()); resourcePath.setFilter(this.getFilter()); Resource[] loaders = resourcePath.getResources(); for(int i=0; i<loaders.length; i++){ InputStream is = loaders[i].getInputStream(); if(is!=null){ readXmlStream(is); success = true; } } } catch (SAXException e) { log.debug("Error when findResourcesByUrl:"+path,e); } catch (IOException e) { log.debug("Error when findResourcesByUrl:"+path,e); } catch (ParserConfigurationException e) { log.debug("Error when findResourcesByUrl:"+path,e); } return success;}/*** 用File的方式試圖查找文件,并調(diào)用<code>readXmlStream()</code>解析* @param path XML文件的路徑* @return 是否成功*/protected boolean findResourcesByFile(String path){ boolean success = false; FileResource loader = new FileResource(new File(path)); loader.setFilter(this.getFilter()); try { Resource[] loaders = loader.getResources(); if(loaders==null){return false;} for(int i=0; i<loaders.length; i++){ InputStream is = loaders[i].getInputStream(); if(is!=null){ readXmlStream(is); success = true; } }} catch (IOException e) { log.debug("Error when findResourcesByFile:"+path,e);} catch (SAXException e) { log.debug("Error when findResourcesByFile:"+path,e);} catch (ParserConfigurationException e) { log.debug("Error when findResourcesByFile:"+path,e);} return success;}/*** 讀取并解析一個(gè)XML的文件輸入流,以Element的形式獲取XML的根,* 然后調(diào)用<code>buildFactory(Element)</code>構(gòu)建工廠* @param inputStream 文件輸入流* @throws SAXException* @throws IOException* @throws ParserConfigurationException*/protected void readXmlStream(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException{ if(inputStream==null){ throw new ParserConfigurationException("Cann't parse source because of InputStream is null!"); } DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(this.isValidating()); factory.setNamespaceAware(this.isNamespaceAware()); DocumentBuilder build = factory.newDocumentBuilder(); Document doc = build.parse(new InputSource(inputStream)); Element root = doc.getDocumentElement(); buildFactory(root);}/*** 用從一個(gè)XML的文件中讀取的數(shù)據(jù)構(gòu)建工廠* @param root 從一個(gè)XML的文件中讀取的數(shù)據(jù)的根*/protected abstract void buildFactory(Element root);
it知識(shí)庫:一堂如何提高代碼質(zhì)量的培訓(xùn)課,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。