声明:关于代码阅读的研究,很多思想和文字是来自《代码阅读》这本书,再加上自己的学习和工作经验。可以说是类似读书笔记的,我把它作为了毕业论文的第8章,并结合了自己的毕设作品进行解释,毕设源代码github下载地址:https://github.com/chinaran/A-LL1-Compiler。
8.3 编码规范和约定重大的编码工作,或是在大型、有组织的体系,例如GNU和BSD中进行的项目,都会采用一套编码规范、指导准则或约定。在阅读代码时,对于特定代码元素的含义,规范和约定为我们提供了额外的指导,也因此提高了我们阅读代码的效率。尤其在企业中,如果一个项目的编码不规范,其维护的费用将是巨大的,因为项目大都是多人并行开发的,需要经常互相查看和修改别人的代码。如果你自己不按照规范写代码,不仅会带来别人的痛苦,也会带去自己的前途。
8.3.1 文件的名称和组织大多数规范都对文件如何命名,以及使用什么扩展名进行了详细说明。我们可以考虑使用文件名和后缀名约定来优化对代码的搜索。例如编译系统中词法分析产生的二元式保存在dual.lex中,语法规则配置在gra.grammar中。
还有其他常见的文件名:README(项目概况),INSTALL(安装指示),TODO(希望将来进行扩展的清单),ChangeLog(代码更改日志),configure(平台配置脚本);
常见文件扩展名:.asm .s(汇编语言源文件),.cgi .asp .jsp .php(Web服务器上可执行的源文件),.c.cpp .java .cs .py .rb(C、C++、Java、C#、Python、Ruby源文件),.h .hpp(C、C++头文件),.class(Java的编译文件),.jar(Java库文件)。
另外,许多风格指南对于源文件中不同程序之间如何进行排序给了建议。例如,Java代码约定规定,Java类中的元素应该按照如下次序进行排列:①类变量;②实例变量③构造函数;④方法。
图 8-4 符合Java规范的类(详见compilers.util包中的Tree.java文件)
8.3.2 格式排版现代块结构语言编写的程序使用缩进来强调每个代码块的嵌套级别。同时风格指南通常会对用来缩进程序块的空白数量类型进行规定,例如,Java代码约束规定使用4字符宽的制表符(Tab键),而BSD风格指南则规定使用8字符宽的制表符。不幸的是,很多同学学了一两年编程也没有学会正确使用缩进,用空格或干脆顶格写,其代码体验是极其糟糕的。
由于大多数风格指南精确规定了如何对程序元素进行缩进,以便反映程序的块结构。因而可以使用代码块的缩进快速掌握其整体结构,从而帮助我们更快的阅读代码。
所有的代码规范都会对声明,以及每条具体语句的编排方式进行详细说明,其中包含空格及换行符的分布,对于花括号的放置位置有两个不同的学派。GNU、Linux,以及许多Windows程序,通常倾向于以缩进的形式将花括号放置在单独的行中,这样的好处是可以使编程元素之间更清晰。而BSD、Java程序在语句的同一行中打开花括号,并使用该语句的缩进级别来关闭花括号,这样好处是消耗的垂直空间较小,因为代码块更可能放置在单一页面中。下图显示了两种风格的代码,花括号的默认显示风格是可以在Eclipse中自由设置的。
图 8-5 两种不同的花括号风格(详见compilers.scanners包中的LexicalAnalysis.java文件)
风格指南还规定了注释的编排和内容。程序的注释真的很重要,对开发者和阅读者来说都是福音。所以在开发过程中要认真写注释,尤其是在编写的功能逻辑比较复杂的时候,先写上思路和步骤再根据它编代码。下面介绍编译系统常用的注释风格,也可以在任何系统中使用。
● 以/**序列开头的Java注释会由javadoc工具处理,并自动生成源代码文档。在这样的注释中,javadoc关键字以@字符开头,如下图。
图 8-6 函数前的声明注释(详见compilers.util包中的Tree.java文件)
● 以/* TODO或// TODO 序列开头的用来表示那些未来需要增强的地方,而且在Eclipse中可以索引到TODO列表,相当于标签的功能。
图 8-7 TODO 注释和 Eclipse中的 TODO Tasks 列表
● 以/* XXX或// XXX序列开头的用来表示那些不正确但大多数时候可以工作的代码如:/* XXX is this correct*/。
● 以/* FIXME或// FIXME序列开头的用来表示那些错误的并需要修复的代码,如:/* FIXME 暂时不处理语义错误 */。
● 最后介绍下自定义的注释格式,如://step 1:…,还有下图的记录思考过程和步骤的。
图 8-8 记录编程思考过程和步骤的注释
8.3.3 命名约定大多数编码规范中,标识符命名都是一个重要的部分。在一个项目中,命名要整体规范统一,但由于一些历史原因或随着开发人员的水平不断提高可能造成混乱,所以在项目开始规划时就要约定好,或者等待出新版本时统一调整。
命名规范主要涉及变量名、类名和函数名。不同编程语言的约定不同,有首字母大写,如TreeNode;使用下划线分割单词,如tree_node,还有只使用单词首字母,如C语言的strcmp函数(stringcompare)。但对于常量的命名规范都是一样的,使用大写字母来命名,单词与单词之间使用下划线进行分格,如ANALYSIS_STATE。编译系统是遵循Java编程约定的,包名总以一个顶级域名或项目名开始,类名和接口名义一个大写字母开始,方法和变量名以一个小写字母开始。;
还有一个稍微复杂但也是经常使用的匈牙利命名法,笔者公司用的就是这个。基本原则是:变量名 = 属性 + 类型 + 对象描述。例如一个静态的整型变量可以命名为s_nName。常见的属性部分有:全局变量 g_、常量 c_、C++类成员变量 m_、静态变量 s_;类型部分有:指针 p、函数 fn、长整型 l、布尔类型 b、浮点型 d、字符串 s、整型 n、字符 c等;描述部分有:最大 Max、最小 Min、初始化 Init、临时变量 T(或Temp)、源对象 Src、目标对象 Dest等。
当然,没有必要完全按照匈牙利法去命名,像一些循环中用到的局部变量i、j、a、b等就非常简洁。但我们还是尽量规范好,举个例子,之前在写C++程序时觉得完全没必要类成员变量前加上“m_”,真麻烦!但是工作后才发现,这是很有必要的,因为当程序比较复杂时,你可以清晰的分辨出哪个是成员变量(成员变量可以在整个类中传递状态,但不可乱用),哪个是局部变量,这对维护和扩展都是有好处的。因此,不管项目中用的是哪个命名规范,都要始终如一的使用。