很多类型的文件,其起始的几个字节的内容是固定的(或是有意填充,或是本就如此)。根据这几个字节的内容就可以确定文件类型,因此这几个字节的内容被称为魔数 (magic number)。此外在一些程序代码中,程序员常常将在代码中出现但没有解释的数字常量或字符串称为魔数 (magic number)或魔字符串。
所谓魔数和魔字串就是指在代码中出现但没有解释的数字量或字符串。如果在某个程序中使用了魔数,那么在几个月或几年后你将很可能不知道他的含义是什么。
魔数(magic number),即在编写程序时直接在程序中运用数字,而不是采用定义宏或是const变量的方式,图1是使用了魔数的一个示例程序。其中的64是指Msk的最大字节数。从这一程序中可以看出,Msk的最小字节数是MIN_MSK_LEN,即20。采用魔数的危害有:
1) 降低了程序的可读性。有人可能会提出,那加一些注释不就完了吗?如果真是采用加注释的方式,那为什么不将其定义成一个宏或是const常量呢?要知道查看注释的效率肯定没有直接看代码来得快和方便,也不存在不同步的问题(代码与注释有可能不同步)。
2) 如果下一次这个最大值要从64改为128,那得在adjustMask()中对每一处都进行更改。另外,当一个项目较大时,魔数的存在会使得程序维护非常、非常困难。
由此看来,这里的“魔”不应理解成象“魔法(magic)”那样神奇,而应理解为象“魔鬼(monster)”那样可怕。
example.c
00290: #define MIN_MSK_LEN 20
00291:
00292: int adjustMsk (MskContext* Context)
00293: {
00294: char temp [64] = {0};
00295:
00296: if (Context->lenMsk > 64) {
00297: memcpy (temp, Context->msk + (Context->lenMsk - 64), 64);
00298: ...
00399: memcpy (Context->msk, temp, 64);
00300: }
00301: else if (Context->lenMsk < MIN_MSK_LEN) {
00302: return ERROR;
00303: }
00304: ...
00305: }
00290: #define MIN_MSK_LEN 20
00291:
00292: int adjustMsk (MskContext* Context)
00293: {
00294: char temp [64] = {0};
00295:
00296: if (Context->lenMsk > 64) {
00297: memcpy (temp, Context->msk + (Context->lenMsk - 64), 64);
00298: ...
00399: memcpy (Context->msk, temp, 64);
00300: }
00301: else if (Context->lenMsk < MIN_MSK_LEN) {
00302: return ERROR;
00303: }
00304: ...
00305: }
图1
图2是采用宏之后的版本。其中定义了MAX_MSK_LEN的大小为64,如果其它函数中也需要用到Msk的最大值,那么也可以引用这一宏。如果下一次想将最大值从64改为128时,只要改MAX_MSK_LEN宏的定义就行了。另外,这种宏定义的存在有利于模块与模块之间共享,从而在一定程度上提高重用性。
example.c
00289: #define MIN_MSK_LEN 20
00290: #define MAX_MSK_LEN 64
00291:
00292: int adjustMsk (MskContext* Context)
00293: {
00294: char temp [MAX_MSK_LEN] = {0};
00295:
00296: if (Context->lenMsk > MAX_MSK_LEN) {
00297: memcpy (temp, Context->msk + (Context->lenMsk - MAX_MSK_LEN),
00298: MAX_MSK_LEN);
00399: ...
00300: memcpy (Context->msk, temp, MAX_MSK_LEN);
00301: }
00302: else if (Context->lenMsk < MIN_MSK_LEN) {
00303: return ERROR;
00304: }
00305: ...
00306: }
00289: #define MIN_MSK_LEN 20
00290: #define MAX_MSK_LEN 64
00291:
00292: int adjustMsk (MskContext* Context)
00293: {
00294: char temp [MAX_MSK_LEN] = {0};
00295:
00296: if (Context->lenMsk > MAX_MSK_LEN) {
00297: memcpy (temp, Context->msk + (Context->lenMsk - MAX_MSK_LEN),
00298: MAX_MSK_LEN);
00399: ...
00300: memcpy (Context->msk, temp, MAX_MSK_LEN);
00301: }
00302: else if (Context->lenMsk < MIN_MSK_LEN) {
00303: return ERROR;
00304: }
00305: ...
00306: }
图2
本文出自 “至简李云” 博客,请务必保留此出处http://yunli.blog.51cto.com/831344/265730
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呢?
在程序中除了0,1,2这几个有特殊用处的数字,其它的都要声明为常量。总之,任何策略的使用,还是一个度最重要。
很多类型的文件,其起始的几个字节的内容是固定的(或是有意填充,或是本就如此)。因此这几个字节的内容也被称为魔数 (magic number),因为根据这几个字节的内容就可以确定文件类型。
例如
1)FreeBSD 上 ELF 文件的 magic number 就是文件的前四个字节依次为"7f 45 4c 46",对应的ascii字符串即 "^?ELF"。
2)tar 文件的 magic number 是从第257个字节起为 "ustar"。
3)PE文件中,在DOS-根之后是一个32位的签名以及魔数0x00004550 (IMAGE_NT_SIGNATURE)(意为“NT签名”,也就是PE签名;十六进制数45和50分别代表ASCII码字母P和E,它使任何PE文件都是一个有效的MS-DOS可执行文件。
Unix 命令 "file" 应该就是利用这个原理工作的。
魔数是稳定核素"幻数"的另称。