• 1. 重构-卓越程序员修炼之道 @呐喊科技 2012-03-28
  • 2. 内容大纲代码重构 引 言 何为优秀代码 代码的坏味道 如何重构 设计重构 重构到模式
  • 3. 代码重构-引言技术债务与破窗效应引 言
  • 4. 你是否遇到过某种严重到要花好几天来做本来只需数小时即可完成的事的混乱状况?你是否见过本来只需做一行修改,结果却涉及数十个模块的情况?这种事太常见了。 怎么会发生这种事?为什么好代码会这么快就变质成糟糕的代码?理由多得很。我们抱怨需求变化背离了初期设计。我们哀叹进度太紧张,没法干好活。我们把问题归咎于那些执行项目的经理、苛求的用户…… 代码沼泽地
  • 5. 勒布朗法则勒布朗(LeBlanc)法则:勒布朗(LeBlanc)法则:稍后等于永不(Later equals never)。
  • 6. 数据库设计—所在环节破窗效应
  • 7. 数据范式 数据库范式 简单的说,范式是为了消除重复数据减少冗余数据,从而让数据库内的数据更好的组织,让磁盘空间得到更有效利用的一种标准化标准,满足高等级的范式的先决条件是满足低等级范式 范式的作用 1.减少数据冗余(最主要的好处) 2.消除异常(插入异常,更新异常,删除异常) 3.让数据组织得更加合理以至于更易于管理维护垃圾效应
  • 8. 第一范式 (1st NF)第一范式的目标是确保每列的原子性 内容相似的数据列必须消除 必须为每一组相关数据分别创建一个数据表 每条数据记录必须用一个主键来标识(TB_MYLIB)(TB_MYLIB1)
  • 9. 破窗效应(Break Pane Law)破窗效应:一个房子如果窗户破了,没有人去修补,隔不久,其它的窗户也会莫名其妙的被人打破。没修复的破窗,导致更多的窗户被打破。 垃圾效应:一个很干净的地方,人会不好意思丢垃圾,但是一旦地上有垃圾出现之后,人就会毫不犹疑的拋,丝毫不觉羞愧 同样,在烂代码情况下,你也不停的制造垃圾代码!!! -------环境对人们心理造成暗示性或诱导性影响的一种认识。
  • 10. 技术债务(Technical Debt )开发团队在设计或架构选型时从短期效应的角度选择了一个易于实现的方案,但从长远来看,这种方案会带来更消极的影响,亦即开发团队所欠的债务。 --- Ward Cunningham 技术债务类似于金融债务,它也会产生利息,这里的利息其实就是指由于鲁莽的设计决策导致需要在未来的开发中付出更多努力的后果。我们可以选择继续支付利息,也可以通过重构之前鲁莽的设计来将本金一次付清。虽然一次性付清本金需要代价,但却可以降低未来的利息。 --- Martin Fowler
  • 11. 生产力 vs 时间
  • 12. 空中楼阁---华丽新设计
  • 13. 代码/设计质量的评价标准-价值观
  • 14. 评价标准的背后动机-----关注开发总成本 Edward Yourdon&Larry L. Constantine 30年前
  • 15. 软件系统维护工作量所占的比重超出想象!!
  • 16. 代码要人能够读懂任何一个傻瓜都能写出机器能懂的代码,好的程序员应该写出人能懂的代码 Martin Fowler 《重构》
  • 17. 软件模式3项职责第1职责:运行起来所完成的功能,这是模块存在的原因. 第2职责:它要应对变化,因为软件要变化,开发者保证应该尽可能的简单. 第3职责:要和阅读它的人进行沟通,对模块不熟悉的人员应该能够比较容易理解.软件模式3项职责---Robert C Martin <敏捷设计原则>
  • 18. 软件可维护性—残酷的现实程序员脑子里原先那些漂亮的设计随着时间的推移会慢慢“发出腐化的臭味”.去年才构建的漂亮小巧的系统,到了今年却变成了由一堆纠缠不清的函数和变量搅和在一起的“代码浆糊”. 为什么会这样? 迄今为止人们构建出的几乎所有软件系统都遭遇了缓慢的.不可抗拒的腐化.这种现象是如此的普遍, …
  • 19. 是什么原因导致的呢 都是客户和老板的错?? 有时候,我们会把原因归咎于客户,责怪他们总是改变需求.我们自我安慰地认为,只要客户的需求仅限于他们最初所声明的,那么我们的设计就是没问题的,所以错就错在客户改变了他们的需求. 有时候,我们也会埋怨老板,是他们没有给我们时间,进行充分分析.其实根本不存在充分分析这种东西.无论花费多少时间试图去找出完美的软件结构,客户总是引入一个变化破坏这个结构,不存在完美结构,只存在那些试图平衡当前的代价和收益的结构.
  • 20. 软件不可预测性!!!!!!!重构的根本原因---软件不可预测性
  • 21. 如何应需求变化?软件系统的功能需求都是会变化的,难道变化的要求一定会导致系统"腐烂"吗? 为什么一个系统的设计不可以为日后的变化留出足够的空间?
  • 22. 软件变更基本法则软件不断变更法则:真实世界中使用的程序必须进行变更,否则它在环境中的作用就会越来越小. 软件复杂增加性法则:随着一个不断演进程序的变更,它的结构会变得更复杂,除非通过积极的工作来避免这一现象. -Lenhman & Belady
  • 23. 开放-封闭原则(OCP)软件组成实体(类,模块,函数,等等)应该是可扩展的,但是不可修改的。 即软件实体可以通过增加新代码实现扩展,但是尽量不能修改已有的代码 --Bertrand Meyer 1998<面向对象软件构造> 任何系统在其生命周期中都会发生变化。如果我们希望开发出的系统不会在第一版本后就被抛弃,那么我们就必须牢牢记住这一点。 --IvarJacobson
  • 24. 如何实现开闭原则—2个基本的思想实现抽象:在OOPL中,可以创建出固定却能描述一组任意个可能行为的抽象体,这个抽象就像抽象基类或者接口,任意个可能的行为就是可能的派生类. 针对接口(抽象)设计:由于模块子间依赖一个固定的抽象,所以它对于更改可以是关闭的.
  • 25. 优秀设计实现---发现变化并将其封装, 隔离变化优秀设计精髓就是封装变化.就是将变化进行抽象,封装变化.这样才能保证软件的可扩展性以及模块间的松耦合. 一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里(比如常量的定义) 一种可变性不应当与另一个可变性混合在一起.
  • 26. 优秀设计实现之道优秀设计需要不断重构 遵守敏捷实践去发现/诊断问题—设计的坏味道 应用设计原则去解决问题— 重构 应用适当的面向对象原则和模式去解决问题—重构到优秀设计
  • 27. 代码重构 代码的新思维 何为优秀代码 代码的坏味道 如何重构 设计重构 设计的基本原理 重构到模式 架构重构
  • 28. 重构的定义 动词定义:使用一系列重构准则,在不改变软件功 能的前提下,调整其结构. 名词定义: 重构是对软件内部结构的一种调整,目的是在不改变外部行为的前提下,提高可理解性,降低修改成本 重构的3次法则(事不过三): 第一次做某事时只管去做; 第二次做类似的事情会产生反感,但无论如何还是做了; 第三次再做类似的事,你就应该重构
  • 29. 重构概述 利用重构技术开发软件时会把时间分配给两种行为: [重 构]与[添加新功能] 添加新功能时,不应该修改既有代码,只管添加 新功能。 重构时你就不能再添加功能,只管改进程序结构。 两顶“帽子”可交替进行,一会重构,一会添加 新功能。 两顶帽子
  • 30. 重构概述 改进程序设计 程序员为了快速完成任务,在没有完全理解整体 架构之前就修改代码,导致程序逐渐失去自己的结构。 重构则帮助重新组织代码,重新清晰的体现程序结构和进一步改进设计。 提高程序可读性 容易理解的代码很容易维护和增加新功能。代码首先是写给人看的,然后才是计算机看的。 为何重构?
  • 31. 重构概述 助你找到程序错误 重构是一个Code Review 和反馈的过程。在另 一个时段重新审视代码,会容易发现问题和加深对代码的理解。 助你提高编程速度 设计和代码的改进都可以提高开发效率,好的设计和代码都提高开发效率的根本。 提高设计和编码水平 对代码的重构,是快速提高设计和编码水平的方法。 为何重构?
  • 32. 重构概述 增加新功能时一并重构 增加功能前需要理解修改的代码,如果发现代码不易理解且无法轻松增加功能,此时就需要对代码进行重构。 修补错误时一并重构 通过重构改善代码结构,能够帮助你找出BUG原因。 Review 代码时一并重构 有经验的开发人员Review代码时能够提出一些代码重构的建议。 何时重构?
  • 33. 重构概述 代码实在太混乱,重构还不如重写 项目即将结束时避免重构 此时已经没有时间进行重构了,应该在早些时候进行重构。如果程序有必要重构,说明该项目已经欠下“债务”,需要项目完成后进行偿还。 何时不该重构?
  • 34. 重构概述 重构与设计彼此互补 良好的设计是重构的目标,重构弥补设计的不足。 重构使得设计方案更简单 如果选择重构,预先设计时候只需找出足够合理的解决方案,实现的时候对问题会进一步加深,此时可以重构成最佳的解决方案。 重构能够避免过度设计 设计人员需要考虑将简单方案重构成灵活方案的难度。如果容易,只需实现简单方案。 重构与设计
  • 35. 重构面临的问题如何发现重构点 如何知道重构的目标 如何去重构 如何保证重构的正确性
  • 36. 坏味道代码坏味道(code smell),是指在代码之中潜在问题的警示信号.并非所有的坏味道所指示的确实是问题,但是对于大多数坏味道,均很有必要加以查看,并做出相应决定 代码坏味道是需要重构的症状。或者潜在的问题(Potential Problem)或者缺陷(Flaw) 代码坏味道和拙劣设计相比是低的层次,重点在代码的层次
  • 37. 21种代码坏味道-1Duplicated Code 重复代码  Long Method 过长方法  Large Class 过长类  Long Parameter List 过长参数列表  Divergent Change 发散式变化  Shotgun Surgery 霰弹式修改  Feature Envy 特性依恋  Data Clumps 数据泥团  Primitive Obsession 基本类型偏执  
  • 38. 21种代码坏味道-2 Switch Statements switch语句 Data Class 数据类   Refused Bequest 拒绝继承   Comments 注释过多  Temporary Field 临时字段   Message Chains 消息链   Middle Man 中间人   Inappropriate Intimacy 过度亲密  Lazy Class 冗余类  
  • 39. 21种代码坏味道-3 Parallel Inheritance Hierarchies 平行继承体系   Speculative Generality 理论上的一般性   Alternative Classes with Different Interfaces 接口不同的等效类  Incomplete Library Class 不完整的库类
  • 40. 重复的代码 (Duplicated Code) 重复代码是最常见的异味,往往是由于Copy & Paste 造成的。 重构方法: 重复代码在同一个类中的不同方法中,则直接提炼为一个方法 如果重复代码在两个互为兄弟的子类中,则将重复的代码提到父类中 如果代码类似,则将相同部分构成单独函数,或者用 Template Method 设计模式 重复代码出现在不相干的类中,则将代码提炼成函数或者放在独立的类中 代码的坏味道
  • 41. 代码的坏味道 过长的函数(Long Method) 是面向结构程序开发带来的 “后遗症”,过长的函数降低可读性。 重构方法: 将独立的功能提炼成新函数 3. 过大类(Large Class) 过大的类使得责任不清晰。 重构方法 将过大类的功能拆分成多个功能单一的小类
  • 42. 代码的坏味道 4. 过长的参数列(Long Parameter List) 过长的参数列难以理解,而且容易传错参数。 重构方法: 将参数列表用参数对象替换
  • 43. 代码的坏味道 5. 发散式变化(Divergent Change) 一个类由于不同的原因而被修改。 重构方法: 将类拆分成多个,每个类只因为一种变化而修改
  • 44. 代码的坏味道 6. 霰弹式修改(Shotgun Surgery) 与发散式变化相反,遇到变化时需要修改许多不同的类。 重构方法: 将类似的功能放到一个类中
  • 45. 代码的坏味道 7. 依恋情结(Feature Envy) 函数对某个类的兴趣高过对自己所处的类,通常是为了取其他类中的数据。 重构方法: 将函数部分功能移到它感兴趣的类中 8. 数据泥团(Data Clumps) 在多个地方看到相同的数据项。例如: 多个类中相同的变量,多个函数中相同的参数列表,并且这些数据总是一起出现。 重构方法: 将这些数据项放到独立的类中
  • 46. 代码的坏味道 9. 分支语句(Swtich Statements) 大量的分支、条件语句导致过长的函数,并且可读性差。 重构方法: 应将它变成子类或者使用 State和 Strategy模式
  • 47. 代码的坏味道 10. 过度耦合的消息链(Message Chains) 一个对象请求另一个对象,后者又请求另外的对象,然后继续。。。。,形成耦合的消息链。 重构方法: 公布委托对象供调用
  • 48. 代码的坏味道 11. 过多的注释(Comments) 代码有着长长的注释,但注释之所以多是因为代码很糟糕。 重构方法: 先重构代码,再写上必要的注释 12. 夸夸其谈未来性(Speculative Generality) 现在用不到,觉得未来可以用到的代码,要警惕。 重构方法: 将用不上的代码去掉
  • 49. 代码的坏味道 13. 纯粹的数据类(Data Class) 将数据类中数据以Public方式公布,没对数据访问进行保护。 重构方法: 将数据封装起来,提供Get/Set方法 以上是代码开发和程序维护过程中经常遇到的问题,并不是坏味道的全部。在开发中应避免出现坏味道。
  • 50. 1、提炼函数 (Extract Methods)String name = request.getParameter("Name"); if( name != null && name.length() > 0 ){ ...... } String age = request.getParameter("Age"); if( age != null && age.length() > 0 ){ ...... } String name = request.getParameter("Name"); if( !isNullOrEmpty( name ) ){ ...... } String age = request.getParameter("Age"); if( !isNullOrEmpty( age ) ){ ...... } private boolean isNullOrEmpty( final String string ){ if( string != null && string.length() > 0 ){ return true; }else{ return false; } } 重构名录实例 将代码段放入函数中,让函数名称解释该函数的用途
  • 51. 2、将函数内联化 (Inline Method)重构名录 如果函数的逻辑太简单,则把其移到调用它的代码中,取消这个函数
  • 52. 3、将临时变量内联化(Inline Temp )重构名录 变量被一个简单的表达式赋值一次,则将变量替换成那个表达式
  • 53. 4、以查询取代临时变量(Replace Temp with Query )重构名录 double basePrice = _quantity * _itemPrice; if (basePrice > 1000) return basePrice * 0.95; else return basePrice * 0.98;if (basePrice() > 1000) return basePrice() * 0.95; else return basePrice() * 0.98; ... double basePrice() { return _quantity * _itemPrice; } 临时变量保存表达式的结果,将这个表达式提炼到独立的函数中。
  • 54. 5、引入解释性变量( Introduce Explaining Variable )重构名录 将复杂表达式结果放入临时变量,用变量名来解释表达式用途boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1; boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1; boolean wasResized = resize > 0; if (isMacOs && isIEBrowser && wasInitialized() && wasResized) { // do something }if ( (platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0 ) { // do something }
  • 55. 6、剖解临时变量( Split Temporary Variable )重构名录 一个临时变量多次被赋值(不在循环中),应该针对每次赋值,创造独立的临时变量。double temp = 2 * (_height + _width); System.out.println (temp); temp = _height * _width; System.out.println (temp);double perimeter = 2 * (_height + _width); System.out.println (perimeter); double area = _height * _width; System.out.println (area);
  • 56. 7、一维语句取代嵌套条件语句(Replace Nested Conditional with Guard Clauses) 重构名录 函数中条件语句使人难以看清正常的执行路径,用卫语句替换嵌套条件 double getPayAmount() { double result; if (_isDead) result = deadAmount(); else { if (_isSeparated) result = separatedAmount(); else { if (_isRetired) result = retiredAmount(); else result = normalPayAmount(); }; }     return result; }; double getPayAmount() { if (_isDead) return deadAmount(); if (_isSeparated) return separatedAmount(); if (_isRetired) return retiredAmount(); return normalPayAmount(); };
  • 57. 8、分解条件表达式( Decompose Conditional )重构名录 从复杂的条件语句分支中分别提炼出独立函数if(date.before(SUMMER_START) || date.after(SUMMER_END)) charge = quantity * _winterRate + _winterServiceCharge; else charge = quantity * _summerRateif(notSummer(date)) charge = winterCharge(quantity); else charge = summerCharge(quantity);
  • 58. 重构名录 在 Martin Fowler 著的《重构 –改善既有代码的设计》中列出了长达70条的重构名录,提供了具体重构的方法和重构的技巧。将帮助开发人员一次一小步地修改代码,减少了开发过程中的风险。 http://www.cnblogs.com/technology/archive/2011/05/10/2042255.html
  • 59. 伟大程序员的标准“我不是什么伟大的程序员,我只是一个有着很多好习惯的程序员”----Kent Beck语。
  • 60. 谢谢!