posts - 257, comments - 1336, trackbacks - 63, articles - 8
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

也说魔数与魔字符串

Posted on 2005-07-06 23:22 FantasySoft 阅读(1608) 评论(9)  编辑 收藏 所属分类: All About SoftThought Ware

         看了叙远兄写的.net中的魔字符串,还有birdsome的评论,颇有启发。
         所谓魔数和魔字符串就是在代码中直接使用某一个数字或者字符串,而不是常量。譬如一个很简单的根
据职位计算薪水的方法:

public int getSalary(String title, int grade) {
    
if ("Programmer"
.equals(title)) 
        
return grade * 500 + 700
;
    
else if ("Tester"
.equals(title))
        
return grade * 500 + 800
;
    
else if ("Analyst"
.equals(title))
        
return grade * 800 + 1000
;
}

        在这个方法里面,"Programmer","Tester"和"Analyst"是所谓的魔字符串(Magic String),而500, 700,800和1000就是所谓的魔数(Magic Number)了。 咋一看,代码这样写也没有什么问题,但是,仔细思考一下就会发现,如果这种随手捻来的字符串和数字散布于程序当中,随处可见的话,是会有很多弊病的。我们先来分析三个魔字符串。虽然三个Magic String的意义很明显,并不影响到代码的可读性,但是这样却增加了出错的概率,并且忽略了具体的语义环境。我们很容易就会想到,像"Programmer"这个单词散布在多个方法中,一个大小写的笔误就会产生bug。同时,"Programmer"在计算薪水的方法中代表着职位,但是在统计公司订阅的杂志的方法中,也许就要代表一本杂志的名称了。然而这种语义环境是无法通过一个单纯的"Programmer"就能体现出来的。
        而Magic Number的问题就更大了,首先是影响了代码的可读性,谁会知道500和800是薪水基数,700是补
贴呢?而且更糟糕的是,如果薪水基数发生改变的时候,那么就得找人把这些500,700,800的数字找出来一个一个地update,那可是一件够郁闷的事情了。
        如果我们拥有一个常量定义的interface,代码就会变漂亮起来了:

public int getSalary(String title, int grade) {
    
if
 (Constants.TITLE_PROGRAMMER.equals(title)) 
        
return grade * Constants.BASE_SALARY_LOW +
 Constants.ALLOWANCE_LOW;
    
else if (Constants.TITLE_TESTER
.equals(title))
        
return grade * Constants.BASE_SALARY_LOW +
 Constants.ALLOWANCE_MEDIUM;
    
else if (Constants.TITLE_ANALYST
.equals(title))
        
return grade * Constants.BASE_SALARY_HIGH +
 Constants.ALLOWANCE_HIGH;
}

        从以上的分析,在一个Project里面,避免使用魔数(Magic Number)和魔字符串(Magic String)是相当必要的。通过定义的常量去access特定的字符串和数字也已经是软件开发的standard。那么是不是所有的数字和字符串都应该定义成常量呢?或许有朋友会认为所有的数字和字符串都应该定义成常量,但是我觉得,每个字符串确实是应该定义成常量的,但是对于数字而言,如果数字本身的语义没有得到延伸,那么就不应该定义成常量。譬如数组的index就不应该定义成变量。 像这样的代码:

String building = address[Constants.ONE]; 
            //
 在Constants这个interface中,ONE的定义为 final int ONE = 1;

        你一定会觉得这样的代码就是画蛇添足, 因为ONE就是1,它没有其他特别的含义,不像上面代码中的500和700。而且如果真的要这样定义的话,出现了有上百个元素的数组的时候,那么你就得定义上百个没有任何意义的常量了。是不是很FT呢?
        总之,任何策略的使用,还是一个度最重要。

Feedback

#1楼    回复  引用  查看    

2005-07-07 00:55 by XiaoHui      
string * 500 + 700?

挑刺...呵呵

#2楼    回复  引用    

2005-07-07 08:51 by byrybye [未注册用户]
感觉我自己功力不到吧,看了感觉就是把一些 字符或者数字定义成常量吗,??

#3楼    回复  引用  查看    

2005-07-07 08:55 by 小陆      
文中的Constants这种只保存常量的类是不应该存在的,任何一个数据都应该有一个主体。
Constants事实上已经成为了全局变量,任何依赖全局变量的模块都不可重用。

#4楼 [楼主]   回复  引用  查看    

2005-07-07 09:23 by FantasySoft      
To XiaoHui: 谢谢您的提醒。 还好没有丢脸太久。 :)
To byrybye: 简单地说,确实是这样的。也许自己开发一个软件的时候,这样的问题并不会凸现出来。但是如果是团队合作完成一个project,这些standard的问题就尤为重要了。因为自己的代码要需要给别人阅读,需要和其他模块的代码保持一致,更重要的是为这些Magic Number和Magic String赋上一个有意义的名字,可以强化代码的语义环境。

#5楼 [楼主]   回复  引用  查看    

2005-07-07 09:49 by FantasySoft      
To 小陆: 模块化并非完全割裂模块之间的关系。如果一个软件是多个模块构建起来的,那么它们之间必然要发生联系,一个公用的常量类的存在就不可避免,而且是必需的。

#6楼    回复  引用  查看    

2005-07-07 09:59 by linkcd      
我倒是明白为何要用constant
只是光看title还没明白魔数是啥意思,还以为是啥新科技呢,呵呵

最后一看,是magic number.... 不太习惯中文译法呀

#7楼    回复  引用  查看    

2005-07-07 10:20 by Allen Lee      

通常来说职位(Job)的设置不见得比较稳定[1],我建议使用多态性来处理它:




abstract class Job
{
    
// 

    
public abstract int GetSalary(int grade);
}


class Programmer : Job
{
    
// 

    
public override int GetSalary(int grade)
    
{
        
return grade * (int)BaseSalary.Low + (int)Allowance.Low;
    }

}


class Tester : Job
{
    
// 

    
public override int GetSalary(int grade)
    
{
        
return grade * (int)BaseSalary.Low + (int)Allowance.Middle;
    }

}


class Analyst : Job
{
    
// 

    
public override int GetSalary(int grade)
    
{
        
return grade * (int)BaseSalary.High + (int)Allowance.High;
    }

}


enum BaseSalary
{
    Low 
= 500,
    High 
= 800
}


enum Allowance
{
    Low 
= 700,
    Middle 
= 800,
    High 
= 100
}


这样的话,将来要添加新的职位或者删除现有职位都比较灵活。要说明的是,在真实的工作设计中,BaseSalary 和 Allowance 都是精心设计并相对稳定的,结合枚举使用就最好不过了(当然,这里我是按照 FantasySoft 的代码给出的数据来设计的)。对于这类的设计,使用多态性还是枚举关键在于“分类”的稳定性如何。





#8楼    回复  引用  查看    

2005-07-07 13:35 by 小陆      
任何数据都必须属于某个主体, 不应该有类似Constants这种全局变量的存在. 如果发现一个变量或者常量只能放在这样的地方, 一定是缺少了某个业务实体的定义. 比如文中的BASE_SALARY_LOW, 一定可以根据其含义可以定义在某个实体中, 比如Emplyee.BASE_SALARY_LOW

消灭全局变量和全局常量只会使程序更加简洁易懂. 全局变量会跨越多个namespace, 混淆模块的界限.

#9楼    

by 妖居      

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-11-07 13:46 编辑过


相关链接: