`
Wingel
  • 浏览: 116515 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

敏捷开发的必要技巧第3章----消除代码异味

阅读更多
完整:
http://www.blogjava.net/Files/Wingel/第3章%20除去代码异味.rar

http://wingel.iteye.com/topics/download/2f7b5864-fca2-42e5-ba3e-453725fcb885
                第3章 除去代码异味 


    异味这个词,可能有点抽象,我们先看一下下面的例子

    这是一个CAD系统. 现在,它已经可以画三种形状了:线条,长方形,跟圆.
先认真的看一下下面的代码:

    class Shape {                                                                                  
       final static int TYPELINE = 0;                                                              
       final static int TYPERECTANGLE = 1;                                                         
       final static int TYPECIRCLE = 2;                                                            
       int shapeType;                                                                              
       //线条的开始点
       //长方形左下角的点
       //圆心
       Point p1;                                                                                   
       //线条的结束点
       //长方形的右上角的点
       //如果是圆的话,这个属性不用
       Point p2;                                                                                   
       int radius;                                                                                 
    }                                                                                              
    class CADApp {                                                                                 
       void drawShapes(Graphics graphics, Shape shapes[]) {                                        
           for (int i = 0; i < shapes.length; i++) {                                               
               switch (shapes[i].getType()) {                                                      
                  case Shape.TYPELINE:                                                             
                      graphics.drawLine(shapes[i].getP1(), shapes[i].getP2());                     
                      break;                                                                       
                  case Shape.TYPERECTANGLE:                                                        
                      //画四条边
                      graphics.drawLine(...);                                                      
                      graphics.drawLine(...);                                                      
                      graphics.drawLine(...);                                                      
                      graphics.drawLine(...);                                                      
                      break;                                                                       
                  case Shape.TYPECIRCLE:                                                           
                      graphics.drawCircle(shapes[i].getP1(), shapes[i].getRadius());               
                      break;                                                                       
               }                                                                                   
           }                                                                                       
       }                                                                                           
    }                                                                                              


    代码都是一直在改变的,而这也是上面的代码会碰到的一个问题.

    现在我们有一个问题: 如果我们需要支持更多的形状(比如三角形), 那么肯定要改动Shape这个类, CADApp里面的drawShapes这个方法也要改.
好,改为如下的样子:
    class Shape {     
       final static int TYPELINE = 0;
       final static int TYPERECTANGLE = 1;
       final static int TYPECIRCLE = 2;
       final static int TYPETRIANGLE = 3;
       int shapeType; 
       Point p1;      
       Point p2;      
       //三角形的第三个点.
       Point p3;      
       int radius;    
    }                 
    class CADApp {    
       void drawShapes(Graphics graphics, Shape shapes[]) {
           for (int i = 0; i < shapes.length; i++) {
               switch (shapes[i].getType()) {
                  case Shape.TYPELINE:
                      graphics.drawLine(shapes[i].getP1(), shapes[i].getP2());
                      break;
                  case Shape.TYPERECTANGLE:
                      //画四条边.
                      graphics.drawLine(...);
                      graphics.drawLine(...);
                      graphics.drawLine(...);
                      graphics.drawLine(...);
                      break;
                  case Shape.TYPECIRCLE:
                      graphics.drawCircle(shapes[i].getP1(), shapes[i].getRadius());
                      break;
                  case Shape.TYPETRIANGLE:
                      graphics.drawLine(shapes[i].getP1(), shapes[i].getP2());
                      graphics.drawLine(shapes[i].getP2(), shapes[i].getP3());
                      graphics.drawLine(shapes[i].getP3(), shapes[i].getP1());
                      break;
               }      
           }          
       }              
    }                 

    如果以后要支持更多的形状,这些类又要改动……,这可不是什么好事情!
理想情况下,我们希望当一个类,一个方法或其他的代码设计完以后,就不用再做修改了。它们应该稳定到不用修改就可以重用。
    现在的情况恰好相反!
    每当我们增加新的形状,都得修改Shape这个类,跟CADApp里面的drawShapes方法。

    怎么让代码稳定(也就是无需修改)?这个问题是个好问题!不过老规矩,先不说,我们以行动回答。
    我们先看看另外一个方法: 当给你一段代码,你怎么知道它是稳定的?


    怎么判断代码的稳定性?

    要判断代码的稳定性,我们可能会这样来判定:先假设一些具体的情况或者需求变动了,然后来看一看,要满足这些新的需求,代码是否需要被修改?
    可惜,这也是一件很麻烦的事,因为有那么多的可能性!我们怎么知道哪个可能性要考虑,哪些不用考虑?

    有个更简单的方法, 如果发现说,我们已经第三次修改这些代码了,那我们就认定这些代码是不稳定的。这个方法很“懒惰”,而且“被动”!我们被伤到了,才开始处理状况。不过至少这种方法还是一个很有效的方法。

    此外,还有一个简单,而且“主动”的方法:如果这段代码是不稳定或者有一些潜在问题的,那么代码往往会包含一些明显的痕迹。正如食物要腐坏之前,经常会发出一些异味一样(当然,食物如果有异味了,再怎么处理我们都不想吃了。但是代码可不行。)。我们管这些痕迹叫做“代码异味”。正如并不是所有的食物有异味都不能吃了,但大多数情况下,确实是不能吃了。并不是所有的代码异味都是坏事,但大多数情况下,它们确实是坏事情!因此,当我们感觉出有代码异味时,我们必须小心谨慎的检查了。

    现在,我们来看看上面例子中的代码异味吧。

    示例代码中的代码异味:

    第一种异味:代码用了类别代码(type code)。
    class Shape {                                                                                  
       final int TYPELINE = 0;                                                                     
       final int TYPERECTANGLE = 1;                                                                
       final int TYPECIRCLE = 2;                                                                   
       int shapeType;                                                                              
       ...                                                                                         
    }                                                                                              

    这样的异味,是一种严肃的警告:我们的代码可能有许多问题。     

    第二种异味:Shape这个类有很多属性有时候是不用的。例如,radius这个属性只有在这个Shape是个圆的时候才用到:

    class Shape {                                                                                  
       ...                                                                                         
       Point p1;                                                                                   
       Point p2;                                                                                   
       int radius; //有时候不用                                                            
    }                                                                                              

    第三种异味:我们想给p1,p2取个好一点的变量名都做不到,因为不同的情况下,它们有不同的含义:

    class Shape { 
       ...        
       Point p1; //要取作“起始点”,“左下点”,还是“圆心”?
       Point p2;  
    }             

    第四种异味:drawShapes这个方法里面,有个switch表达式。当我们用到switch(或者一大串的if-then-else-if)时,小心了。switch表达式经常是跟类别代码(type code)同时出现的。
    现在,让我们将这个示例中的代码异味消除吧。

    消除代码异味:怎么去掉类别代码(type code)

    大多数情况下,要想去掉一个类别代码,我们会为每一种类别建立一个子类,比如:
    (当然,并不是每次要去掉一个类别代码都要增加一个新类,我们下面的另一个例子里面会讲另一种解决方法)

    class Shape { 
    }             
    class Line extends Shape {
       Point startPoint;
       Point endPoint;
    }             
    class Rectangle extends Shape {
       Point lowerLeftCorner;
       Point upperRightCorner;
    }             
    class Circle extends Shape {
       Point center;
       int radius;
    }             

    因为现在没有类别代码了,drawShapes这个方法里面,就要用instanceof来判断对象是哪一种形状了。因此,我们不能用switch了,而要改用if-then-else:
    class CADApp {
       void drawShapes(Graphics graphics, Shape shapes[]) {
           for (int i = 0; i < shapes.length; i++) {
               if (shapes[i] instanceof Line) {
                  Line line = (Line)shapes[i];
                  graphics.drawLine(line.getStartPoint(),line.getEndPoint());
               } else if (shapes[i] instanceof Rectangle) {
                  Rectangle rect = (Rectangle)shapes[i];
                  graphics.drawLine(...);
                  graphics.drawLine(...);
                  graphics.drawLine(...);
                  graphics.drawLine(...);
               } else if (shapes[i] instanceof Circle) {
                  Circle circle = (Circle)shapes[i];
                  graphics.drawCircle(circle.getCenter(), circle.getRadius());                     
               }                                                                                   
           }                                                                                       
       }                                                                                           
    }                                                                                              

    因为没有类别代码了,现在每个类(Shape,Line,Rectangle,Circle)里面的所有属性就不会有时用得到有时用不到了。现在我们也可以给它们取一些好听点的名字了(比如在Line里面,p1这个属性可以改名为startPoint了)。现在四种异味只剩一种了,那就是,在drawShapes里面还是有一大串if-then-else-if。我们下一步,就是要去掉这长长的一串。
   

    消除代码异味:如何去掉一大串if-then-else-if(或者switch)       

    经常地,为了去掉if-then-else-if或者switch,我们需要先保证在每个条件分支下的要写的代码是一样的。在drawShapes这个方法里面,我们先以一个较抽象的方法(伪码)来写吧:
   
    class CADApp {                                                                                 
       void drawShapes(Graphics graphics, Shape shapes[]) {                                        
           for (int i = 0; i < shapes.length; i++) {                                               
               if (shapes[i] instanceof Line) {                                                    
                  画线条;                                                                   
               } else if (shapes[i] instanceof Rectangle) {                                        
                  画长方形;                                                              
               } else if (shapes[i] instanceof Circle) {                                           
                  画圆;                                                                 
               }                                                                                   
           }                                                                                       
       }                                                                                           
    }                                                                                              

    条件下的代码还是不怎么一样,不如再抽象一点:
    class CADApp {                                                                                 
       void drawShapes(Graphics graphics, Shape shapes[]) {                                        
           for (int i = 0; i < shapes.length; i++) {                                               
               if (shapes[i] instanceof Line) {                                                    
                  画出形状;                                                                  
               } else if (shapes[i] instanceof Rectangle) {                                        
                  画出形状;                                                                  
               } else if (shapes[i] instanceof Circle) {                                           
                  画出形状;                                                                  
               }                                                                                   
           }                                                                                       
       }                                                                                           
    }                                                                                              

    好,现在三个分支下的代码都一样了。我们也就不需要条件分支了:
   
    class CADApp {                                                                                 
       void drawShapes(Graphics graphics, Shape shapes[]) {
           for (int i = 0; i < shapes.length; i++) {
               画出形状;
           }   
       }       
    }          

    最后,将“画出形状”这个伪码写成代码吧:
    class CADApp {
       void drawShapes(Graphics graphics, Shape shapes[]) {
           for (int i = 0; i < shapes.length; i++) {
               shapes[i].draw(graphics);
           }   
       }       
    }          
   
    当然,我们需要在每种Shape的类里面提供draw这个方法:

    abstract class Shape {
       abstract void draw(Graphics graphics);
    }          
    class Line extends Shape {
       Point startPoint;
       Point endPoint;
       void draw(Graphics graphics) {
           graphics.drawLine(getStartPoint(), getEndPoint());
       }       
    }          
    class Rectangle extends Shape {
       Point lowerLeftCorner;
       Point upperRightCorner;
       void draw(Graphics graphics) {
           graphics.drawLine(...);
           graphics.drawLine(...);
           graphics.drawLine(...);
           graphics.drawLine(...);
       }       
    }          
    class Circle extends Shape {
       Point center;
       int radius;
       void draw(Graphics graphics) {
           graphics.drawCircle(getCenter(), getRadius());
       }       
    }          

    将抽象类变成接口

    现在,看一下Shape这个类,它本身没有实际的方法。所以,它更应该是一个接口:


    interface Shape {                                                                              
       void draw(Graphics graphics);                                                               
    }                                                                                              
    class Line implements Shape {                                                                  
       ...                                                                                         
    }                                                                                              
    class Rectangle implements Shape {                                                             
       ...                                                                                         
    }                                                                                              
    class Circle implements Shape {                                                                
       ...                                                                                         
    }                                                                                              

    改进后的代码

    改进后的代码就像下面这样:
   
    interface Shape {                                                                              
       void draw(Graphics graphics);                                                               
    }                                                                                              
    class Line implements Shape {                                                                  
       Point startPoint;                                                                           
       Point endPoint;                                     
分享到:
评论

相关推荐

    敏捷开发的必要技巧

    敏捷开发的必要技巧 目录 第 1 章 移除重复代码 第 2 章 将注释转换为代码 第 3 章 除去代码异味 第 4 章 保持代码简洁 第 5 章 慎用继承 第 6 章 处理不合适的依赖 第 7 章 将数据库访问,UI和域逻辑分离 第 8 章...

    敏捷软件开发的必要技巧

    ### 敏捷软件开发的必要技巧 #### 一、引言 随着信息技术的快速发展,软件开发的方法论也在不断地进步和完善。传统的瀑布模型等线性开发流程逐渐被更加灵活高效的敏捷开发所取代。敏捷开发强调快速迭代、持续交付...

    敏捷开发的必要技巧(完整版)

    ### 敏捷开发的必要技巧 #### 一、引言 敏捷开发是一种以人为本、迭代、循序渐进的开发方法论。它强调快速响应变化、持续交付可用软件、紧密合作与适应性规划。《敏捷开发的必要技巧》这本书由Wingel翻译自...

    敏捷软件开发的必要技巧完整版

    ### 敏捷软件开发的必要技巧 #### 一、引言 敏捷开发是一种以人为本、迭代、循序渐进的开发方法,在21世纪初开始流行起来。它强调快速响应变化,通过持续的反馈循环来提高项目的成功率。本文档旨在介绍一系列重要的...

    reek, ruby的代码异味 检测器.zip

    reek, ruby的代码异味 检测器 用于 ruby的 代码异味 检测器目录概述快速入门示例支持的红宝石固定气味警告源代码代码异味配置文件命令行接口配置文件配置加载程序配置选项生成一个'待办事项'列表注意:要注意多个...

    敏捷编程AGILE PROGRAM

    - **敏捷设计**:注重识别和消除“设计异味”,保持简洁的设计,避免过度设计带来的复杂性和冗余。 - **用户故事**:一种简洁地描述用户需求的方法,有助于团队更好地理解用户的实际需求。 ### 敏捷工具和技术 - *...

    基于排序损失的ECC多标签代码异味检测方法.docx

    味的定义可以看出,代码异味(Code Smell)是软件开发中的一种不良编程习惯或设计问题,它降低了软件的可读性、可维护性和可靠性。Fowler 提出了22种常见的代码异味,包括数据类、大类、长方法、特征依恋等。针对...

    01-Python基础语法v5.0(第1节 开发环境安装介绍)06-PyCharm的作用.ev4.rar

    2. **代码分析和错误检查**:通过内置的静态代码分析器,PyCharm能检测潜在的错误和代码异味,提高代码质量。 3. **调试支持**:强大的调试工具允许设置断点,查看变量值,单步执行代码,方便问题排查。 4. **版本...

    第一章-食品腐败变质的因素及分析.pptx

    #### 第三节 食品败坏的控制因素 为了有效控制食品的败坏,需要从生物学因素、化学因素以及物理因素三个方面入手。 ##### 1. 生物学因素 - **微生物因素**:微生物是导致食品腐败变质的主要原因,包括细菌、霉菌...

    Laravel开发-laravel-inspect

    通过`laravel-inspect`,开发者可以在早期阶段发现并消除这些问题,提升代码质量。 **4. Laravel Artisan CLI集成** `laravel-inspect`将这三个工具无缝集成到Laravel的Artisan命令行中。开发者只需在终端运行`...

    敏捷开发中高质量Java代码开发实践.docx

    【标题】: "敏捷开发中高质量Java代码开发实践" 【描述】: 在敏捷开发环境中,保证和提升Java代码质量是至关重要的。本文旨在探讨如何在敏捷开发过程中通过一系列策略和工具来实现这一目标,包括统一编码规范、静态...

    deploy-sonar代码扫描分析

    3. 分析完成后,SonarQube服务器会生成报告,展示代码质量指标,如复杂度、漏洞、代码异味和代码覆盖率等。 4. 开发者可以根据SonarQube的报告进行代码优化,修复高优先级的问题。 在实际开发中,我们还可以利用...

    让开发自动化持续重构--使用静态分析工具识别代码味道

    我写过新的代码也维护过其他开发人员的源代码。我喜欢编写新的代码,但也喜欢采用一些现有的代码,以某种方法将其简化或将重复的代码提取到一个公共类中。在我早期的工作生涯中,许多人都认为如果不编写新的代码就...

Global site tag (gtag.js) - Google Analytics