转载请标明出处:blog.csdn.net/zhangxingping
做编程工作也有些年头了,有过许多的经验和教训,也有很多的体会和心得。俗话说“好记性不如烂笔头”所以打算将平日工作中的一些体会记录下来,与大家一起共勉。
关于全局变量
之前有看过在.c文件中出现多达42个全局变量的代码!这些全局变量错综出现,而且命名也很不规范,加上注释少的可怜,一般人很难轻易看出这些全局变量的作用和含义。这其中有十多个全局变量是大小超过256字节的数组。后来,多次阅读代码后发现这些变量中有绝大多数都是和局部相关的,甚至有的全局变量只是在一个函数中出现过。所以很搞不懂作者当初为什么要这样写。
全局变量的存在当然有其道理。但是在使用的时候我们还是要考虑一下使用全局变量的优点和缺点的:
优点:全局变量的作用域为整个程序空间,在任何函数中都可以访问这些变量,避免了函数调用时候的参数传递。
缺点:过多的全局变量会破坏程序的结构化,增加了模块间的耦合性。正是由于上述优点,使得在一个地方对全局变量的错误修改可能导致整个程序不能正常工作,而且由于全局变量可能出现在程序的任何地方,这种问题一般很难快速准确的定位。另外,全局变量和局部变量容易冲突,进而混淆视听,影响程序的可阅读性和可维护性。
规则1:全局变量的使用要谨慎,尽量避免之。一旦决定要使用全局变量,一定好通过规范命名,注释等手段避免全局变量带来的副作用。
关于魔鬼数字(magic number)
很多次在程序中看到有常量数字的重复出现。其中绝大部分都是有实际含义的。程序中的魔鬼数字本身是不会影响程序的正确运行的。但是对于代码的维护和阅读来讲却是大忌。为什么这么说了?个人认为,首先从阅读性来讲,初次接触某个模块代码的维护人员往往对程序的理解不是很深入,这些莫名其妙的数字会更使人觉得云里雾里,看不懂代码。表达式中为什么要出现这个数字,而不是别的数字呢?这是维护人员经常遇到的问题。有人可能说这个问题可以通过注释解决。那么是不是有更好的方式来规避这个问题呢? 个人认为通过定义宏或者const常量,并且采用有意义的宏名称或者变量名称完全可以避免这个问题,自我注释的变量名称可以省去注释。很多技术人员其实是很不喜欢写注释的。另外从维护的角度来讲,这些具有同一个含义的常量如果多次出现在程序中的很多不同地方,那么如果日后需要修改这个值,操作起来就很不方便。一处的遗漏就可以导致程序功能的错误。而如果采用宏或者const常量来定义则只需要修改一处即可。操作简单,还不会出错。
规则2:避免使用魔鬼数字,而是用宏或者const常量代替之,首推const常量。
关于for循环
for循环的圆括号中通常由三部分组成:变量初始化;循环控制条件;循环控制变量自增或者自减。这是常用的形式。C语言中允许在这三部分分别写多条用逗号分隔的语句。我曾经见过有代码在其中第一部分写了3条以上的变量初始化语句和在第3部分写了多条多个变量自增或者自减的语句。虽然这不影响程序的正确运行。但是这种写法使得for循环阅读起来要么头重脚轻,要么头轻脚重,总之感觉很不平衡。
规则3:建议for循环的()中只写简单的和控制相关的初始化,或者控制条件,或者自增自减运算。其他变量的初始化完全可以放置在for循环之前进行。如果控制条件比较复杂,可能编写一个内联函数来代替之。
关于注释
很多技术人员在编程的时候都不喜欢写注释。大多数情况下不写注释是可以的,但是程序必须是自我注释的。个人感觉在一些核心算法或者复杂逻辑的情况下,写注释还是有必要的。并且注释写为什么要这样做比写成做了些什么要好。注释的目的就是让程序的阅读者能快速明白程序在干什么。一般程序员都能很容易地从代码中看懂程序在做什么,但是至于为什么要这样做恐怕就不那么直接了当了。当然既描述了程序在做什么又描述了程序为什么这么做的注释更容易让阅读者深入了解代码。那还要设计文档干什么?哈哈,现实是很多技术人员讨厌写文档,所以要么没有设计文档,要么文档只是为了应付检查,完全没有可读性。或者在设计修改后,文档已经落伍了,完全和代码对应不上。
规则4:代码最好是自我注释的,这样可阅读性好,也方便后期维护。注释尽量能描述为什么这样做则更好。
关于函数
函数的名称应该简洁并能体现函数的具体功能。笔者曾经见过Check_XXX()的一个函数。从函数的名称来看其功能应该是检测某种条件或者状态的。没有人会想到这个函数会修改某种状态或者变量的值得。但是仔细阅读该函数,却发现有一个全局变量ftp_root_dir在该函数中被修改了!另外:函数的命名一般采用动词+名称形式。其中的动词应该避免没有意义的词汇,比如process, do 等,而采用自我注释性的动词。有时候其中的名词可以省略。
函数的功能最好也是单一的,尽量避免功能大而全的函数。例如,有函数check_file_type(char * path, int * size)。从这个函数的名称来看应该是检查path所指示的路径的类型是目录还是一个文件。但是第二个参数size又有什么作用呢?在仔细阅读了所有该函数调用的地方和该函数的实现后发现size作为出参,是用来获取path指定目录或者文件的大小的。很明显,要么是函数的名称不够准确,要么是函数的该功能不够单一。如果按照函数名称应该能准确反映函数功能的原则来说,该函数最好命名为Get_file_type_and_size(char * path, int* type, int * size);如果按照函数功能尽量单一来讲,可以设计成两个函数:GetType()用来查询path的类型;GetSize()用来查询path指定目录或者文件的大小。笔者更倾向于第二种设计。如果通常在获取路径对应的类型的同时也需要获取大小,则可以把上述的GetType()h和 GetSize()重新封装成一个函数。功能单一原则能很好的保证函数有良好的扇入和扇出比例。特别是公用模块或者底层模块中的函数一定要具有较大的扇入才能有效地提高代码的复用性。
关于函数的返回值:函数的返回值的含义应该是很明确的。曾经见过analyse_cmdline(char * cmd)函数的注释如下:
/* return: 1: analyse succeed */
/* 0: analyse error */
/* -1: needn't to exec */
从注释中可以看出这个函数的返回值的含义很不明确,至少可以说是注释写的很不明确。返回1表示解析成功;返回0表示解析失败,除此之外还有别的情况?函数操作既不成功也不失败时返回-1 ?什么叫做"不需要执行"? 那需要执行的时候有返回什么值呢?通读所有调用该函数的地方以及该函数的实现才发现实际情况是这样的:当命令解析成功,并且该条命令需要执行的时候返回1;解析成功,但是该条命令不需要执行则返回-1;解析失败时返回0。那么为什么不能这样设计或者说是描述函数的返回值了:
/* return: 1: cmd dose need to be executed */
/* -1: cmd dose not need to be executed */
/* 0: fail to analyse the cmd */
最好定义enum类型来表示函数的返回值:
typedef enum
{
CMD_ANALYSE_EXE,
CMD_ANALYSE_NO_EXE,
CMD_ANALYSE_FAIL,
}enCmdAnalyseResult;
这样函数返回值的类型及其取值就能很好的描述函数的功能,代码的可阅读性和可维护性也会有很大的提高。此时甚至完全可以不需要注释。毕竟很多编码人员是不乐意写注释的。
关于函数的参数。较老版本的C语言编译器可能不支持const关键字。目前大部分的编译器都是支持const关键字的。此时适当正确地,统一风格地在函数的参数前面加上const关键字也可以在很大程度上提高代码的可阅读性和可维护性。因为形参前面一旦加上了const关键字,读者就很快会明白这是一个入参。 一旦这样的编码风格形成, 那么那些没有加const的关键字的参数明显就成了出参了;而且,const关键字能保证参数不会在函数体内部被错误地修改。
规则5:函数的名称应该简洁并如实反映函数的功能;函数的参数含义要明确,入参和出参的定义也要明确表达;函数的返回值含义也要明确;函数的功能尽量单一,避免大而全的函数。