我们评价高质量代码有三要素:可读性、可维护性、可变更性。
我们的代码要一个都不能少地达到了这三要素的要求才能算高质量的代码。
今天这堂培训课讲什么呢?我既不讲Spring,也不讲Hibernate,更不讲Ext,我不讲任何一个具体的技术。
我们抛开任何具体的技术,来谈谈如何提高代码质量。
如何提高代码质量,相信不仅是在座所有人苦恼的事情,也是所有软件项目苦恼的事情。
如何提高代码质量呢,我认为我们首先要理解什么是高质量的代码。
高质量代码的三要素我们评价高质量代码有三要素:可读性、可维护性、可变更性。
我们的代码要一个都不能少地达到了这三要素的要求才能算高质量的代码。
1. 可读性强一提到可读性似乎有一些老生常谈的味道,但令人沮丧的是,虽然大家一而再,再而三地强调可读性,但我们的代码在可读性方面依然做得非常糟糕。
由于工作的需要,我常常需要去阅读他人的代码,维护他人设计的模块。
每当我看到大段大段、密密麻麻的代码,而且还没有任何的注释时常常感慨不已,深深体会到了这项工作的重要。
由于分工的需要,我们写的代码难免需要别人去阅读和维护的。
而对于许多程序员来说,他们很少去阅读和维护别人的代码。
正因为如此,他们很少关注代码的可读性,也对如何提高代码的可读性缺乏切身体会。
有时即使为代码编写了注释,也常常是注释语言晦涩难懂形同天书,令阅读者反复斟酌依然不明其意。
针对以上问题,我给大家以下建议:1)不要编写大段的代码如果你有阅读他人代码的经验,当你看到别人写的大段大段的代码,而且还不怎么带注释,你是怎样的感觉,是不是“嗡”地一声头大。
各种各样的功能纠缠在一个方法中,各种变量来回调用,相信任何人多不会认为它是高质量的代码,但却频繁地出现在我们编写的程序了。
如果现在你再回顾自己写过的代码,你会发现,稍微编写一个复杂的功能,几百行的代码就出去了。
一些比较好的办法就是分段。
将大段的代码经过整理,分为功能相对独立的一段又一段,并且在每段的前端编写一段注释。
这样的编写,比前面那些杂乱无章的大段代码确实进步了不少,但它们在功能独立性、可复用性、可维护性方面依然不尽人意。
从另一个比较专业的评价标准来说,它没有实现低耦合、高内聚。
我给大家的建议是,将这些相对独立的段落另外封装成一个又一个的函数。
许多大师在自己的经典书籍中,都鼓励我们在编写代码的过程中应当养成不断重构的习惯。
我们在编写代码的过程中常常要编写一些复杂的功能,起初是写在一个类的一个函数中。
随着功能的逐渐展开,我们开始对复杂功能进行归纳整理,整理出了一个又一个的独立功能。
这些独立功能有它与其它功能相互交流的输入输出数据。
当我们分析到此处时,我们会非常自然地要将这些功能从原函数中分离出来,形成一个又一个独立的函数,供原函数调用。
在编写这些函数时,我们应当仔细思考一下,为它们取一个释义名称,并为它们编写注释(后面还将详细讨论这个问题)。
另一个需要思考的问题是,这些函数应当放到什么地方。
这些函数可能放在原类中,也可能放到其它相应职责的类中,其遵循的原则应当是“职责驱动设计”(后面也将详细描述)。
下面是我编写的一个从XML文件中读取数据,将其生成工厂的一个类。
这个类最主要的一段程序就是初始化工厂,该功能归纳起来就是三部分功能:用各种方式尝试读取文件、以DOM的方式解析XML数据流、生成工厂。
而这些功能被我归纳整理后封装在一个不同的函数中,并且为其取了释义名称和编写了注释:1./**2. * 初始化工厂。
根据路径读取XML文件,将XML文件中的数据装载到工厂中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.* 初始化工厂。
根据路径列表依次读取XML文件,将XML文件中的数据装载到工厂中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.* 重新初始化工厂,初始化所需的参数,为上一次初始化工厂所用的参数。
23.*/24.public void reloadFactory(){25.initFactory(this.paths);26.}27./**28.* 采用ClassLoader的方式试图查找一个文件,并调用<code>readXmlStream()</code>进行解析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的方式试图查找一个目录中的所有XML文件,并调用<code>readXmlStream()</code>进行解析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的方式试图查找文件,并调用<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.* 读取并解析一个XML的文件输入流,以Element的形式获取XML的根,107.* 然后调用<code>buildFactory(Element)</code>构建工厂108.* @param inputStream 文件输入流109.* @throws SAXException110.* @throws IOException111.* @throws ParserConfigurationException112.*/113.protected void readXmlStream(InputStream inputStream) throws SAXException, IOException, ParserConfigur ationException{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.* 用从一个XML的文件中读取的数据构建工厂127.* @param root 从一个XML的文件中读取的数据的根128.*/129.protected abstract void buildFactory(Element root);在编写代码的过程中,通常有两种不同的方式。