• 1. 代码质量 意识、行为、方法、工具cantellow
  • 2. 开题武侠世界里,凡是登峰造极的人,要么是经历了一些奇遇,要么就是老老实实的靠时间积累,要么就练一些歹毒的功夫,吸取别人的武功,但他们的目的都是增强自己的内力,内功心法之所以很重要,那是因为像什么剑法、拳法之类的聪明的人看一遍就会,而内力却很难。 在我们Java程序员世界里,也有一些登峰造极的人,比如James Gosling(不解释)、 Joshua Bloch(google首席java架构师,effective java作者)、 Bruce Eckel (thinking in java作者)、 Doug Lea(JDK1.5并发包, Java Concurrency in Practice 作者)、 Kent Beck(敏捷之父)、martin fowler(重构之父)等等,这些人之所以登峰造极,并不是他们写了多少多少的代码,在于他们每写一行代码就思考,去感悟,为什么这些好,为什么那样写不好,他们把自己悟出的道写成了书,这些书就是所谓的武功秘籍,名扬天下;而且你也可以发现,武侠世界里出名的秘籍大部分都是修炼的内功,神马易筋经啊、九阳真经啊、九阴真经啊,反正离不开一个经字。 程序员的武功秘籍也不例外,神马《Java编程思想》、《敏捷软件开发》、《设计模式》、《重构》、《领域驱动设计》、《人月神话》、《代码大全》、《程序员修炼之道》、《简单之美》啊神马神马的无不讲的是程序员的首先要修炼好的是内功,今天主题:代码质量和重构就属于内功心法的一种,要练好它,没有几年甚至十年是不行的,这篇PPT能做的就是影响你正确的修炼道路,不至于走火入魔。
  • 3. 说明/遗憾的地方代码质量开始的部分带了个人情感,有点偏离了主题,为此我修改了一下,但是要完全不带个人感情去做这次培训,我是办不到的。 还有人建议,培训ppt理论太重了点,特别是重构,缺乏鲜活的例子,我承认,这跟我预期想的不一样,我开始想把XXXX功能的重构过程讲一遍,但是我一看CC,发现我已经修改了将近30个版本,要唤起那些古老的回忆,我觉得特别头疼,但是要我完全参照martin fowler的例子,我又是不太服气的,嘛,如果大家愿意看,我可以直接把PDF打开再看看那段例子。 培训的目的是什么?我自己也经常去想我听了大部分的培训最后得到了什么?自己是否真正的去行动过?如果我要做培训,我要给大家留下什么?带着这些思想,我精心准备着这次培训(但还是离我的理想太远),特别是我那份findbugs反模式,历时一个月之久,真心希望大家抽点时间看看。 我不知道有多少人事前看过PPT,大家应该知道最后的部分是“困惑的心里没底”,以我目前的视野来看,敏捷思想是解决这个困惑的银弹,由此带来的一系列工具和平台,比如maven、mylyn、JIRA、scrum、sonar、hudson等等都是我想去了解和学习的,其实之前这些都是纳入培训计划的,但是很遗憾,我无法给大家介绍更多的信息,至少现在是如此。
  • 4. 目录我为什么要投他隐藏 代码质量意识 代码质量和软件质量 代码质量特性 如何提高代码质量 Findbugs反模式
  • 5. 目录我为什么要投他隐藏 代码质量意识 代码质量和软件质量 代码质量特性 如何提高代码质量 Findbugs反模式
  • 6. 从一个帖子说起 大家先自由发表一下自己的观点 帖子的地址是:http://www.iteye.com/topic/856221
  • 7. 别人的观点看看common-lang的StringUtils中的实现,优秀的代码大家应该没什么意见  public static boolean isNumeric(String str) {          if (str == null) {              return false;          }          int sz = str.length();          for (int i = 0; i < sz; i++) {              if (Character.isDigit(str.charAt(i)) == false) {                  return false;              }          }          return true;      }   Integer中的public static int parseInt(String s, int radix)只是包含了大量的检测代码,然后实现和上面代码雷同,个人认为能捕获的异常一定要捕(非RuntimeException),能够用正常代码处理的就不要使用异常,不然既会影响性能,又不符合异常机制的设计初衷 。   LZ的代码没啥问题。  不过,LZ的态度就有点问题了,别老是那么愤青。什么工作5年的人对正则表达式一知半解,这种话以后少说,生活中也少说。每个人都不会面面俱到的,他可能对正则表达式不熟,但是可能对别的熟悉啊。还有,下面的人只要提一下质疑,LZ立马火起来(至少从言语上看),这样大家慢慢的都会懒的理你,所以投隐藏了。
  • 8. 别人的观点public static boolean validateInteger(String str) {   try {           Integer.parseInt(str);        } catch (Exception ex) {            return false;        }        return true;   }  这段代码的功能是什么?我的理解是判断转入字符串是否为整数,那好,判断这个的目的是什么呢?Integer.parseInt(str)的功能是什么,是解析字符串为Int值,而不是判断是不是为整数(虽然可以通过异常来实现这个功能),当然这只是我的理解。我的原则是代码要完全忠实于其目的。对我而言,在写方法的实现中,尽可能做到只专注自已的功能实现。      你的程序没有什么问题, 但是, 你这个程序存在局限性:  对于一个性能没有要求的地方, 无所谓的;  对于性能有要求, 但是钱不是问题的地方, 无所谓;  如果您刚从事这个行业, 对自己没有什么要求,其实程序代码能工作就可以了, 也是没有那么多的要求。 但是, 请不要误导他人。  如果你在互联网企业, 并且大规模的处理数据, 数据又不是那么规整的时候,你就知道你这段代码造成多大的悲剧了。  不懂其实不要紧的, 但是又人说你不对的时候, 请您好好想下为什么。  你可以看看我的blog写的一些内容。   http://sdh5724.iteye.com/blog/647930  某种处理方式是不是合适, 要看业务场景的。 对于桌面程序, 小规模程序大部分的处理方式都是对的。但是,也有很多程序员, 需要面对严酷的性能, 也有很多程序员需要面对严格的代码review, 还有些程序员写的是opensource代码,需要面对大伙的挑战。  如果, 你引用的opensource代码, 都完全象你这么做, 你还能相信他们的代码质量么?  写代码有很多“不建议”的方法, “不建议”不是说你不能用, 如果不影响的情况下, 你也可以用。 如果一个程序员经常使用“不建议”的方式做事, 那么这习惯就会被你自然的养成。  
  • 9. 我当时的观点我当时的语气也很强烈,现在再回头看比较幼稚。
  • 10. 我为什么要投他隐藏1.其实大多数人投他隐藏,都只是出于它的态度问题,我当时也是出于这个角度投他隐藏的。 点评:既然你能接受得了异常的性能,为什么就接受不了正则的性能?至少正则可以让代码看上去更好看。
  • 11. 我为什么要投他隐藏2.可读性、可维护性不好,基于异常的模式降低了代码的清晰度,感觉很暴力,方法里要做的事情尽量和方法名语义保持一致,如果有较大的差异,后来人维护怎么想? 3.还是从性能说起,建立一个异常对象,是建立一个普通Object耗时的约20倍,而抛出、接住一个异常对象,所花费时间大约是建立异常对象的4倍。这些数据有人专门测试过:透过JVM看Exception本质,话说在这篇帖子里,gdpglc的态度好多了,呵呵,JE水是如此的深。 4.只是不建议,没有说不能这样做,很固执的一个人,不过固执也不一定都是坏事。 总结:我们很高兴看到不同的思想,但是希望语气更加平和一些,不要搞得好好的讨论一片乌烟瘴气,也不要搞得大张旗鼓误导新人。有争论才有进步,虽然我投了他隐藏,但是心里还是佩服他敢于连发四贴,敢于争论敢于面对大伙挑战的勇气,至少,这种勇气我是没有的,至少,通过争论我们大家都学到了很多东西,至少,比从来不show出自己的观点的人更加有益。
  • 12. 目录我为什么要投他隐藏 代码质量意识 代码质量和软件质量 代码质量特性 如何提高代码质量 Findbugs反模式
  • 13. 代码质量意识在接下来我会讲到如何提高代码质量,会讲到一系列措施和工具,比如codereview、重构、findbugs、敏捷等等,这些东西对代码质量非常有用,但取决你是否行动了,你和你的团队是否具有强烈的代码质量意识,如果没有强烈的代码质量意识,这一切就像是在看我这个小丑在上演一场杯具,过往云烟,看过了就忘记了。 所以,代码质量意识很重要,那是你一切行动的源动力,每个人生来就是独一无二的,思想也是独一无二的,但在这方面,我希望大家统一思想:“XX出品,必出精品”,大家在心里默念三遍吧。
  • 14. 如何提高代码质量意识当你跟别人合租房子时,你会主动把厨房打扫干净吗?如果是你自己的房子呢?   当你要写一份给自己看的心得是,你会把它写得很漂亮很有深度吗?如果早知道这份心得会抄送给全部门看呢? 当你在修复一个bug时,你是否会首先确定这个bug的来源,如果是自己发现的?能解决就解决,不能解决或者太麻烦就这样吧,反正别人也没发现;如果是QA部门发现的?尽量吧,实在不行就推迟解决;如果是客户反映的,而且被领导盯着的?全力以赴,加班加点的干。 当进公司第一天写代码,是否有种要把自己的代码打造成完美的冲动?是不是敲入的第一字符不是代码而是自己的名字?是不是连一个变量名,怎么写注释都要纠结半天?不过一年之后呢?当你团队没有任何质量改进时,你是否还会这样严格要求自己? 本节同步发表在:http://cantellow.iteye.com/blog/1040261
  • 15. 如何提高代码质量意识主人翁精神 当你的努力能立马换来客户的赞扬,老板的赏识,更重要的是产品质量的日趋稳定,我们是不是有很大的成就感?我们是不是为自己的负责的产品或项目更加感到骄傲和自豪,我们需要这样的人,这种人会主动去干一些事情,最大的发挥程序员的生产力和创造力。 拥有主人翁精神的人会把自己发现的问题及时解决掉,但是要树立这种精神,会牵扯很多管理方面的科学,小而精悍的团队正是主人翁精神最大的受益者,它们会把自己的产品和项目上升到人生的高度,它们能立马看到它们负责的产品改进的效益,这是一个持续改进过程,良性的循环。   激励机制 我坚信,程序员是一个伟大的职业,它们在代码的世界里任意驰骋,构建属于自己的罗浮宫,我不知道有多少人认为程序员最大的成就感来源于它所负责的软件被用户所认可、被广泛的使用,但至少我是如此。 程序员希望被认可,不是通过悦耳动听的歌声,不是通过优美的诗歌,而是通过通宵达旦坐在电脑边日夜奋战编出来的代码,我们需要通过codereview得到同事和老板的认可,需要通过可运行的程序得到客户的赞扬,试问,如果连这些基本的途径都不具备,我们还有什么方式来表达我们心中的成就感?第一次挫败,第二次不会再像第一次那么努力和认真了,如果第二次也挫败,那么第三次,我们写代码还有什么期待?
  • 16. 如何提高代码质量意识问题所有化 大家有没有这样的经历? 发现有人早已经解决相同的bug;某些bug再次复现时,自己已经忘记了当初是怎么解决的;我们依赖其他的团队成果,他们改变了实现方式,但并未告知我们,等到QA部门发现问题并费了半天时间追查到底是什么原因时,才发现是这个原因。 每次我发现一个很诡异很复杂很有趣的问题并解决之后,就会发邮件全组告知,还可以在周例会上一起讨论更加完美的解决方法,我之所以这样做,一方面是让大家有个印象,另一方面是留一个记录,更重要的是,在这个过程中,大家一起讨论,一起出谋划策,不但彻底解决了这个问题,而且也提高了团队整体对项目代码的熟悉程度。 其实,最最重要的原因是,每当我觉得这就够了,我做的已经够多了,准备忍耐着不共享不舒服的煎熬时,我会对自己说,你要写一份心得发给全组哦,更加完美的解决这个问题吧。   团队代码质量氛围,破窗效应 相信大家小时候都发出过“豪言壮志”什么的吧,说什么要改变世界,可是绝大多数都是被世界所改变了。不是我们不坚持,只是长大之后才发现当时的幼稚,也发现了,人和社会是分不开的,我们经常在受别人的影响,也经常无意中影响着别人,特别是我们程序员。 我时常在想,如果从一开始,代码风格命名规则是严格统一的、每个字符都是通过了codereview的、单元测试覆盖率是100%的,谁还敢不按规范出牌?写bad code很容易,写good code就很难,只要有一个人不按规范出牌,规律就会向着破窗效应发展。
  • 17. 目录我为什么要投他隐藏 代码质量意识 代码质量和软件质量 代码质量特性 如何提高代码质量 Findbugs反模式
  • 18. 关于软件质量的思维导图
  • 19. 代码质量/软件质量我们的代码中有一个模块完美地工作了很多年了,只是代码太乱了。我说服了我的老板,我可以重写这个模块,于是我花了三个星期来重写这个模块。今天 ,我还记得,我的老板站在我的后面看着我,而我在在流着斗大的法汗珠去fix被我重写的“超级漂亮”的那个模块中一个接一个的bug。从那以后,我再也不重写代码了,除非有重大的利益。 点评:这就是所谓的屠宰式编程。这个案例告诉我们两个道理,1)维护代码要用最最最保守的方法来进行。2)重构代码前要像一个商人一样学会计算利益。当然,ThoughtWorks的咨询师一定会告诉你TDD,结对,极限等等方法告诉你如果实践重构。但我想告诉你,一个程序在生产环境里运行好几个年能没有问题是一件很不容易的事,那怕其中的代码再烂,你再看不过去,你都要有一个清醒的头脑明白这几点,1)软件的运行质量是远远大于代码质量的,2)你的测试案例是远远小于生产环境的,3)软件的完美质量,是靠长时间的运行、测试和错误堆出来的,而不是某种方法论。   这是Javaeye上面的一篇文章的摘抄,我发表一下我的看法: “维护代码要用最最最保守的方法来进行”这种说法太绝对,实际情况可能多种多样,不赞同。 “2)你的测试案例是远远小于生产环境的,3)软件的完美质量,是靠长时间的运行、测试和错误堆出来的,而不是某种方法论。”这种说法我比较赞同,检测软件的质量,只能依靠长时间的运行和测试,方法论不能用来论证,但是可以作为提高软件质量的一种手段。 “软件的运行质量是远远大于代码质量的”,软件的运行质量是对用户来说的,用户看不到代码质量,如果产品一经发布不在维护,那么这种说法成立。只要还要对用户作出更多的承诺,那么代码质量的好坏直接影响软件的运行质量,开发人员在对bad code修改添加功能时,极易出现新的bug,组织良好的代码结构,会让维护进行得更加顺利,从而软件质量也能得到更好的保障。
  • 20. 目录我为什么要投他隐藏 代码质量意识 代码质量和软件质量 代码质量特性 如何提高代码质量 Findbugs反模式
  • 21. 可维护性我的心态变化 第一次接触编程,学习C语言,交换两个数的值:  c = a  a = b  b = c  后来我我从师兄那学到了下面这段代码,觉得写的比我之前的更漂亮: a = a + b  b = a - b  a = a - b  最后参加工作了,看到很多别人的代码,最后又觉得最漂亮的代码是这样的:  c = a  a = b  b = c   请问大家,为什么我的心态有这样的变化?本节同步发表在博客:http://cantellow.iteye.com/blog/978201为什么强调可维护性?编译器永远不会关心你的代码好不好看,更不会关心你写的代码他人是否看得懂,但是当我们的打算修改系统特性或者添加新功能时,就涉及到了人,人在乎这些。 如果我真要使用第二种方法,怎样写最合适?为它新写一个方法,然后写上自解释的方法名就一下子让人清晰了。
  • 22. 可维护性软件开发的现实 一个软件生命周期中,80%的时间和精力花费在维护阶段。 几乎没有任何一个软件,在其整个生命周期中,均有最初的开发人员维护。 几乎没有任何一个软件,在其整个生命周期中,它的文档与代码是保持同步的。 场景再现 有两种人,第一种人维护的是自己的代码,但是,随着时间的流逝,以前的关于系统的理解和记忆已经逐渐消失了,每次修改bug或增加新功能时,要通过看文档、看代码来回忆当时开发系统的场景。 第二种人,代码不是自己写的,需要阅读相关文档和代码,为了弄明白某一处代码的意图,需要了解整个模块的逻辑流程,还要看相关需求和设计文档,但是很遗憾,这些文档都和实际代码不同步。 最终,没有彻底理解到代码意图的人们急于修改代码,只会给系统引入新的bug。
  • 23. 可维护性如何解决—提高软件的可维护性 影响可维护性的因素 1.是否是同一个开发人员 2.人的质量如何,技术功底,对业务领域的熟悉程度 3.是否有完善的文档,文档是否同步 4.代码是否自解释,是否有足够的注释   落地措施 1.提高自身的质量,技术功底(面向对象、重构),熟悉业务,多与同事face to face交流 2.关键地方一定要写单元测试,单元测试能够让你更快速回忆当初开发系统时的场景。 3.利用卡片、白板、或者excel(如果你喜欢敏捷,可以试试scrum),为你维护的项目或者模块写一个故事,每一个项目,每一个模块,甚至每一行代码都有自己的故事,我们大部分时间都只是它的过客。你为它们建立了历史档案,总有一天你用得着。 4.你可以写一个小的工具自动化完成相关维护任务( 常规性的,有规律),提高你的工作效率。 5.养成随时记录思维的好习惯,利用思维导图软件,我用的就是xmind。 6.代码要多些注释,改动代码要写名字和原因,写名字的意思表示个人对此次改动负责,能够提高代码主人翁精神,潜移默化的影响开发人员反复琢磨代码的好坏。
  • 24. 可维护性前提—可理解性大多数人都能写出计算机可以理解的代码,唯有写出人类容易理解的代码才是优秀的程序员。 其实,维护软件最头痛的是,随着时间的流逝,以前的关于系统的理解和记忆已经逐渐消失了。每次修改bug或增加新功能,要通过看文档,看代码来回忆当时开发系统时的场景。 软件质量特性中最重要的我认为是可维护性,而可维护性的前提是可理解性。   我们要理解谁? 1.理解人,我们每天的开会,日常交流,任务分配,培训都是在人和人的交流。 你是一个理解性强的人,还是一个表达性强的人?这都很重要。 2.理解文档,信息只有被用心组织才能有更好的可理解性。 3.理解代码,好的代码会自解释,会说话,坏的代码面目狰狞,五官发育不全,代码的可理解性是靠开发人员来控制的,是造一个天使还是一个怪物,全看我们的态度是怎么样的。   可理解性强的代码不是一门技术,而是一门艺术。
  • 25. 可复用性为什么Java能做大型的应用?就像硬件能在别人的芯片上进行二次开发一样,Java也有类似的组件式开发,这些和它的面向对象复用是分不开的,java里的复用有两种方式,继承(单继承,但可以用内部类实现多继承的效果)和组合,凡读过《effective java》的人应该都熟悉martin flower推荐优先使用组合,我们经常用的最多的JDK API,大部分也就是通过组合的方式实现的,比如JDK API提供的算法和数据结构,要实现业务逻辑无关性,一般采用的是抽象接口(顶层Object提供了强有力的支持)和泛型(1.5支持)。 可复用性和封装性息息相关,组合有四种访问限制,public、protected、包权限、private,封装性的好坏意味着你对外界做出多少的承诺,我们应该对外做出更少的承诺,只是一个类,一个接口,或者是一个方法,我不明白为什么有那么多人一开始就对外做出更多的承诺,我通常的做法是,能private的绝不protected,能不protected的绝不public,一开始就严格封装,即使以后有什么其他需求,我也可用轻易修改权限,不过我承认,要对外提供不可修改的jar包,这种控制的确难以取舍。
  • 26. 封装性对于封装性,我们再来看一个很有趣的例子: 这样写代码是瞎折腾吗?纠结 真是犀利的楼主啊,大家自己看看该怎么回帖呢?
  • 27. 封装性我觉得比较靠谱的回复,我们一起学习学习:  很简单,当你有一天需要加入一下条件或者钩子操作的时候你就知道好处啦。  比如现在你写成public的,所有的地方都直接访问,有一天,你需要在这个属性改变的时候做一些观察者的操作,比如发邮件通知,或者其他操作的时候你就杯具啦,你必须在工程中search到底多少地方调用过,并且在每个后面去加上一段sendEmailIfChange(userid,contents)代码,但是如果是封装了,就直接在set里面判断ifChange然后改了。 哪个好,你懂的。 这是java的风俗。就好比所有人都在放屁之前脱裤子的社会里,你不脱就是新手。 实际情况确实多数getter/setter都是没有用的。 以OO流派的说法,对象的状态都因该是私有的,对象之间只有message,这就是这个风俗的理论基础。   1.懂OOP的程序员,会告诉你,这样做为了封装.. 2.做过3-5年Java企业项目的人,并被客户需求虐待过的同学会告诉你,这样做为了程序易于修改、维护 3.懂模式设计的合格程序员,会告诉你,这样写能扩展成观察者模式,备忘录模式。。。 4.做过架构设计的牛人,会告诉你,一切为了卸耦..
  • 28. 可扩展性可扩展性的动力——不断变化的用户需求 世界上不变的是变化,软件需求不会变化?不可能!一个系统是否拥抱变化是由它的可维护性和可扩展性决定的,软件环境的变化(可能是业务环境,运行环境)导致软件要进行改动才能满足人们对它的要求,这种系统本身适应变化的能力就是可扩展性。   可扩展性的设计忠告 可扩展性对系统的性能和复杂度都有影响,复杂度很高的项目很难维护,亦很难测试,我比较推崇可预见的、简单的扩展设计,愈简单愈好,避免前期大量的设计,对后期的无法预见的扩展,我们可以通过持续的重构来达到目标。所以,可扩展性不是一蹴而就的,是需要随着你对业务领域理解的深入而不断重构获得,一般三次可以达到比较理想的程度。 对于可扩展性的预见就像买股票一样,如果你买中了,你将会受益颇多,如果你没有买中,那么你的代码质量将面临臃肿和沉沦。这对你今后的维护和开发都造成不小的麻烦。 本节同步发表在博客:http://cantellow.iteye.com/blog/978203
  • 29. 可扩展性如何构建灵活的系统? 白箱可扩展性 开放箱式可扩展性 含义:原始源代码可以查看和修改。 手段: 直接修改代码,没有足够的注释,需要阅读一大片相关联的代码,才能理解其中的含义;代码风格混乱,完全不按标准出牌;代码跟其他模块有牵连,需要其他模块修改,自己的工作才能进行。 直接修改代码,有足够的注释,良好的代码风格,容易阅读和理解,而且修改之处值局限在一个类或者一个模块中。 玻璃箱可扩展性 含义:可以查看源代码,但不能修改。 手段: 引入源代码工程和jar包,组合封装使用第三方的类库 引入源代码工程和jar包,利用继承,编写子类,在子类中添加新的业务逻辑
  • 30. 可扩展性黑箱可扩展性 含义:扩展现有系统而不直接扩展其原始代码,源代码通常不可见,提供文档和约定。到了黑箱这一步,才算真正满足OCP原则。 手段: 修改配置文件,需重新读取配置文件使其生效。通常来说,这些配置文件保存着技术参数或者业务逻辑等等。 通过配置向导,比如使用什么数据库,使用什么语言环境,一般是在程序安装时候或者初始化配置时候将用户的选择保存在硬盘上,程序启动时会自动读取这些文件。 对象动态装配,使用java反射技术,在运行时期装载类(class.forName),比较有名的是spring中的依赖注入,它其实也要配置bean文件。 基于模块的运行时动态扩展,前面所说的都只是在比较细粒度上的技术扩展方式,而且都需要重新编译项目,才可能使新功能生效,都没有达到模块级别的扩展。OSGi可以实现模块级别的动态扩展,而且是运行时的。所谓运行时模块的动态扩展,就是说你可以将新开发的类和文件按照Bundle进行组织,然后直接扔到运行时环境下,这些功能就可以用了。Eclipse的插件体系结构就是以equinox(一个OSGi规范的实现)为核心构建的。 基于平台,让开发人员自行扩展,平台和框架通常定义了一些Hotspot(热点),在这些点上,可以进行扩展,比如window提供数千个API供应用程序调用,还有firefox、chrome的插件,以及各种应用平台(facebook、taobao),它们大多数都是通过中间语言进行扩展,具体的技术细节还不太清楚。
  • 31. 可预见性深入挖掘业务对象,拒绝盲目的if else 每次我在业务跳转的十字路口徘徊时,别人以后开发会不会碰到同样的问题?我真的要在代码里写死if else吗?还是我要规划一个继承体系,利用多态性来跳转?或者说是我要提取出业务对象,封装其业务行为? 举了一个工作中实际的例子
  • 32. 目录我为什么要投他隐藏 代码质量意识 代码质量和软件质量 代码质量特性 如何提高代码质量 Findbugs反模式
  • 33. 如何提高代码质量关注基础,面向对象和设计模式等等 基础一定要踏实,我后面会讲到最近做了一份FindBugs反模式,里面有的问题真是啼笑皆非,千奇百怪,至今为止,居然有人实现单例模式,字段竟然是public或者protected的,而且不是一个两个,貌似很多都是一人写了,其他人直接就ctrl+paste,从来没有想过为什么,可能你会说,我们的网管不是跑得好好的吗?那你觉得bug库里那么多不能复现的bug都是测试人员眼睛看花了吗?其中80%我估计都是多线程的问题,可能我的语气比较激烈,但是事实确实是这样的,你可以质疑我,但不能质疑事实。   我一直认为,面向对象入门很简单,很多书上都是一些同样的例子,接口、抽象、封装、耦合、继承等等,我们每个人都能多少理解一点,但是像诸如充血模型、贫血模型、领域驱动设计、依赖注入、PO/VO/POJO等等概念(虽然这些经常出现在J2EE项目中,但是也同样是应用于我们的网管开发),我相信在座的很少有人能讲透彻的,包括我自己,当然要达到martin flower的理解深度,没有十年以上的磨练是不可能的。   曾经我花费了一个月的时间去研究设计模式,最后得出的结论让人啼笑皆非:这东西必须要实战,积累足够的项目经验才能深刻理解其中的好处,有句话说得好,经历过才会明白,仅仅想凭几本书就想搞透彻,有点太异想天开了。
  • 34. 如何提高代码质量重构 其实我们每天都在使用它,重构,是我们的空气和水,只要你的软件还在维护,你就离不开它! 个人觉得,是否积极重构,重构质量的好坏应该是影响代码质量好坏最重要因素之一,后面将详细介绍它。 Codereview 再智能的代码检查工具也不能检查出程序中的业务逻辑设计问题,而往往这方面的问题决定着软件质量的高低,所以花一些时间来进行codereview很必要,如何高效的进行codereview,这可以另开一次培训了。 方法学 测试驱动开发(TDD) —没写过,不发表言论  持续集成 —XX是专家  结对编程 —没实践过,不发表言论  敏捷 —正在学习之中
  • 35. 如何提高代码质量使用静态代码检查工具 上面的内容摘自我的博客:http://cantellow.iteye.com/blog/800491 后面有专门针对FindBugs的讲解。 沟通、交流、共享 埋头做事,抬头看路,更要看看周围的人。代码风格CheckStylehttp://eclipse-cs.sourceforge.net/update/它的饼图看着不错代码重复PMDhttp://pmd.sourceforge.net/eclipse/PMD也有CheckStyle的功能静态分析代码潜在BUGFindBugshttp://findbugs.cs.umd.edu/eclipse比较钟爱的一个插件,其总结的BUG模式也比较有趣代码覆盖率emmahttp://update.eclemma.org/Coverlipse也不错,能与junit很好的结合复杂度监控和依赖项分析metricshttp://metrics.sourceforge.net/updateMetrics都有这两种功能,但是 JDepend在包分析上更专业
  • 36. 目录我为什么要投他隐藏 代码质量意识 代码质量和软件质量 代码质量特性 如何提高代码质量 Findbugs反模式
  • 37. Findbugs反模式FindBugs 是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。之所以叫做静态分析工具,是因为它在不实际运行程序的情况对软件进行分析。使用findbugs有很多种方式,从 GUI、从命令行、使用 Ant、作为 Eclipse 插件程序和使用 Maven,甚至作为hudson持续集成的插件。  findbugs自己定义了一系列的检测器,1.3.9版本的检测器有83种Bad practice(不好的习惯),133种Correctness(正确性),2种Experimental(实验性问题),1种 Internationalization(国际化问题),12种Malicious code vulnerability(恶意的代码),41种Multithreaded correctness(线程问题),27种Performance(性能问题),9种Security(安全性问题),62种Dodgy(狡猾的问题)。 可能大家更感兴趣的是它的工作原理,我们可以首先看一下eclipse插件下面的lib包:   其中的bcel.jar和asm-xx.jar都是对java字节码(通过命令javap -c 类名 反编译class文件)的操作,原理都是在类被装载虚拟机之前,动态修改类(可以直接创造类,也就是说不经过java编译器那一步),bcel是apache下面的一个开源项目,而asm是由法国某电信公司的研发工程师负责。
  • 38. Findbugs反模式Findbugs只是一个工具,我们不应该把它变成敲打团队或者个人的政治武器。 很多人都说Findbugs的黄色臭虫没什么用,其实仔细研究一下,获益匪浅,我刚开始的打算是为XXX中的每一个臭虫都写一份解释,但是这个工作量实在是太大,遇到不太熟的臭虫,我自己都要想半天为什么是这样,还要查阅大量的资料,目前只做了将近50份解释,不到总数的1/3,我在后面列举出了一些很重要的而且经常容易出现的臭虫,更详细的资料请参考《Findbugs反模式.docx》。
  • 39. "." used for regular expression Bug: "." used for regular expression Pattern id: RE_POSSIBLE_UNINTENDED_PATTERN, type: RE, category: CORRECTNESS 解释: String的split方法传递的参数是正则表达式,正则表达式本身用到的字符需要转义,如:句点符号“.”,美元符号“$”,乘方符号“^”,大括号“{}”,方括号“[]”,圆括号“()” ,竖线“|”,星号“*”,加号“+”,问号“?”等等,这些需要在前面加上“\\”转义符。 解决方法: 在前面加上“\\”转义符。
  • 40. Comparison of String objects using == or != Bug: Comparison of String objects using == or != Pattern id: ES_COMPARING_STRINGS_WITH_EQ, type: ES, category: BAD_PRACTICE 解释: 你确定你已经了解string的全部了? 如果你不了解,请参考FX大神的博文:请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧 那么,接下来我就开始剥皮了: Object和StringBuilder的toString方法都是返回一个new String(),跟””不相等。 如果你之前是这样的定义的:String name = “”;OK,它们处于同一个class常量池,跟””相等。 如果在这之前,你使用了String. Intern方法,你是高手,跟””相等。 如果你没有意识到这些问题,却仍然使用==和!=去比较字符串,那么请不要告诉我是你手滑了= =! 解决方法: 老实使用equals方法吧,至少为了保持代码的清晰性。
  • 41. Invokes AsyncCentral$FireThread.start() Bug: new AsyncCentral() invokes AsyncCentral$FireThread.start() Pattern id: SC_START_IN_CTOR, type: SC, category: MT_CORRECTNESS 解释: 构造方法里重启新的线程,我还是第一次见过这样写的。 首先说明三点: 对象的创建一般分两步走,在堆上new对象操作,执行方法(包含构造方法),为什么我们开发人员看见的只有一步,那是因为JVM不想让开发人员在这个过程中插上一脚,破坏对象的初始化流程。 类的加载和初始化是由虚拟机保证同步的,但是对象的生成和初始化就没有任何同步机制来保证了。 构造器不能加synchronized,是一项程序语言设计上的选择(见:JLS 8.8.3 Constructor Modifiers),正常情况下,是不需要加上synchronized,但不代表所有的情况都不要加上synchronized,更不能认为一个构造器隐含的就是一个synchronized。 那什么时候构造方法需要同步呢?通常来说,方法在生成对象的时候只被执行一次,一般new对象的操作可能因为JVM自身的关系保证原子性操作(自己臆测的,没有任何根据),所以我们经常不用关心构造方法同步的问题。但是上述情况就不一样了,在构造方法中新启线程,如果AsyncCentral是一个状态类,FireThread线程极有可能对AsyncCentral的状态进行反复读取和写入,更严重的一种情况是,AsyncCentral有父类,极有可能在父类的构造方法还没开始前,FireThread线程就已经开始执行并对AsyncCentral的状态进行“破坏”了。这个时候,就有两个线程来对AsyncCentral的状态进行操作了(一个是执行方法的线程,一个是FireThread线程),自然而然,就会存在同步的问题了。 多数时候,我们没有发现,可能是AsyncCentral类没有状态,或者是时候未到,我想说的是,我们写的大部分程序都存在同步的问题,本例子就是其中一个,值得我们好好思考。 另一种理解(觉得更靠谱,来自于《Java.Concurrency.in.Practice》叫做“对象逃逸”,意思就是说在构造方法里,this是可以访问的到的,同一时间,FireThread线程而是可以访问到this对象的,所以这时候this就从方法线程逃逸到了FireThread线程中,这时候初始化就会存在并发问题。 解决方法: 不要再构造方法中新启线程,可以提供init方法,其他方法根据实际情况而定。
  • 42. forces garbage collection Bug: DBExportTask2.exportDBRecords(DBExportProperty, String) forces garbage collection; extremely dubious except in benchmarking code Pattern id: DM_GC, type: Dm, category: PERFORMANCE 解释: 有两点: System.gc()只是建议,不是命令,JVM不能保证立刻执行垃圾回收。 System.gc()被显示调用时,很大可能会触发Full GC。 GC有两种类型:Scavenge GC和Full GC,Scavenge GC一般是针对年轻代区(Eden区)进行GC,不会影响老年代和永生代(PerGen),由于大部分对象都是从Eden区开始的,所以Scavenge GC会频繁进行,GC算法速度也更快,效率更高。但是Full GC不同,Full GC是对整个堆进行整理,包括Young、Tenured和Perm,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。 解决方法: 去掉System.gc()
  • 43. locked 50% of time Bug: Inconsistent synchronization of URLAlarmMonitor.m_Counter; locked 50% of time Pattern id: IS2_INCONSISTENT_SYNC, type: IS, category: MT_CORRECTNESS 解释: m_Counter只锁住了50%,它还是处于线程不安全的状态,如果一个字段只被read,那么它是线程安全的,不需要提供额外的同步开销,可以定义为final的(参考不可变类的实现),如果既有read也有write,那么就必须保证每个get和set方法都同步,而不能像上面一样,只对set方法进行了同步。 解决方法: 对get和set方法都实行同步。
  • 44. Incorrect lazy initialization of static field Bug: Incorrect lazy initialization of static field TopoController.m_This Pattern id: LI_LAZY_INIT_STATIC, type: LI, category: MT_CORRECTNESS 解释: 为什么它存在多线程的bug,比如线程1进入到if语句内,被线程2打断,线程2同样进入了if语句内然后生成了一个对象a,随即被线程1打断,线程1又生成了另外一个对象b,这还是一个单例么? 更详细的解释请看:双重检查锁定以及单例模式 另外,关于单例模式更多的资料,参见单例模式的七种写法 如果你并发功底相当好,请看这篇文章:用happen-before规则重新审视DCL 解决方法: 我比较钟情于恶汉,如果需要传递参数,我会使用双重校验锁。
  • 45. makes inefficient use of keySet iterator instead of entrySet iterator Bug: Method JTAMainFrame.initView(JFrame) makes inefficient use of keySet iterator instead of entrySet iterator Pattern id: WMI_WRONG_MAP_ITERATOR, type: WMI, category: PERFORMANCE 解释: 很多人都这样遍历Map,没错,但是效率很低,先一个一个的把key遍历,然后在根据key去查找value,这不是多此一举么,为什么不遍历entry(桶)然后直接从entry得到value呢?它们的执行效率大概为1.5:1(有人实际测试过)。 我们看看HashMap.get方法的源代码: public V get(Object key) {       if (key == null)           return getForNullKey();       int hash = hash(key.hashCode());       for (Entry e = table[indexFor(hash, table.length)];            e != null;            e = e.next) {           Object k;           if (e.hash == hash && ((k = e.key) == key || key.equals(k)))               return e.value;       }       return null;   }   从这里可以看出查找value的原理,先计算出hashcode,然后散列表里取出entry,不管是计算hashcode,还是执行循环for以及执行equals方法,都是CPU密集运算,非常耗费CPU资源,如果对一个比较大的map进行遍历,会出现CPU迅速飚高的现象,直接影响机器的响应速度,在并发的情况下,简直就是一场灾难。 解决方法: for (Map.Entry entry : menuList.entrySet()) {       mb.add(entry.getValue()); }
  • 46. 感谢感谢JavaEye社区(虽然现在已改名为ItEye了),这里的文章和讨论是我思想的源泉。 感谢各位同事,能给我这次机会做培训,我知道,这里不乏高手的存在,但我更希望看到的是大家能多多的show出自己的观点。