声明:关于代码阅读的研究,很多思想和文字是来自《代码阅读》这本书,再加上自己的学习和工作经验。可以说是类似读书笔记的,我把它作为了毕业论文的第8章,并结合了自己的毕设作品进行解释,毕设源代码github下载地址:https://github.com/chinaran/A-LL1-Compiler

8.2项目的整体结构

在刚接触一个新项目或系统时,不必急着深入去看。就像刚拿到一本新书要先去查看目录一样。所以我们要从整体自顶向下的关注,同时要了解一些常用的项目结构和目录含义。

8.2.1项目的组织

通过浏览源码树(包含项目源代码的层次目录结构)。能够反映项目的架构和软件的处理结构。例如下图,该编译系统的源码树。

编译系统的源码树   图 8-1 编译系统的源码树

其中Compiler_4为项目根目录,src中为源代码文件,以包的形式组织,如compile.scanners包下的LexicalAnalysis.java,JRE System Library中存放的是项目中用到jar包,相当于库文件,data中放的是配置文件和数据文件,doc中有项目相关的文档,images中为界面中用到的图片文件。

以上为Java项目中常见的组织方式,不全。还有常见的例如:main(主程序的源代码,平台无关的外部库),lib(库的源代码),common(应用程序间共享的代码),rc/res(Windows中原始的或经过编译的资源文件,位图、资源对话框、图标等),os(操作系统相关的代码),tools(构建过程中使用的工具),test(测试脚本和输入/输出文件),eg(示例),bin(存储可执行文件和脚本的仓库),等等。所以,源码中不仅包含源代码,还有技术规范、开发者文档、测试脚本、多媒体资源、示例等。

如果项目涉及到多平台,那么开发跨平台应用程序的一个常见策略是,将为每个平台编写的代码独立起来,放在不同的目录下(例如os),以便不同平台编译程序时,可以根据配置使用其中一个。由于Java的跨平台性非常好,“一次编译,到处执行”。所以该编译系统也是可以在除Windows系列操作系统以外执行的,如各种Linux的发行版。涉及到平台不同的就有如下代码段,作用为获取所使用的操作系统,然后设置相应的窗体组件外观。

跨平台代码   图 8-2 跨平台代码(详见compilers.main_ui包中MainUI.java文件)

另外,历史原因常常对一个项目结构有着深刻的影响,因为项目的开发者和维护者都不太愿意去重构他们已经熟悉的代码。例如,笔者公司的数据库表都以“g_”开头,就是历史原因,也不知懂为什么这样。既然不能改变,就去适应吧。

8.2.2版本控制

我们可以将系统的源代码想象成在时间和空间两个方面的延伸:通过文件和目录形式的组织占据着空间,同时会随着时间进行演变。版本控制系统可以跟踪代码的演变,标记重大事件,以及记录改变原因,从而可查看控制时间要素。当前可用的版本管理系统种类比较多,如基于开源社区广泛使用的CVS,Linux下的Git和笔者公司使用适合多人并行开发的SVN。

有时,给定文件上的开发工作可能分为两个不同的分支(例如一个稳定分支和一个维护分支);后期,两个分支可能会再次合并。虽然在学校用不到什么版本控制,但我们要和社会接轨,为将来工作做准备,学校不可能什么都教给你。所以平时编写项目时可以试着用用,其实对软件开发帮助很大。笔者公司的SVN就有两个分支,一个稳定分支用于发布稳定版本和当新版本出现问题时应急;另一个开发分支用于开发一些新的需求和对之前的版本进行修改。每当要发布新版本时,这两个分支还要合并。

SVN的另一个作用就是备份了,可以查看之前的代码,如果出错,还可以回滚回去。由于当时我还不知道版本控制系统,所以用了一个笨方法。“Compiler_4”相当于版本4,每次要写新模块或做较大的改动时,就复制一份出来(相当于备份了),加一下版本号。忠告:做软件开发的一定要按时备份,否则可能出现灾难性的后果。

8.2.3自制的工具

项目中可能会包含自制的工具(tool目录下),它们可用在软件开发过程中的各个方面,包括配置、编译过程管理、代码生成、测试、以及文档的编写(例如javadoc工具,可用于生成Java API的帮助文档,不过源代码中的注释要按照给定的格式写)。笔者的同事负责维护软件的权限管理工作,为了方便其他人添加权限,他就为之写了一个小工具。所以,为了方便软件开发,写一些小工具是必要的。而且如果比较通用的话,你可以开源出去,帮助他人,推进中国软件产业的发展(虽然像百度、腾讯这样的大公司方便着我们的生活,但是它们大多用一些开源软件,但是对其真正的贡献很少)。

8.2.4测试

设计良好的项目,都会预先提供相应的措施来对系统的全部或部分功能进行测试,例如软件测试报告和各模块的测试用例。作为源代码阅读的一部分,我们应该能够识别并且推演测试代码和测试用例,然后利用这些测试产物帮助理解其余的代码。例如,应该记得测试用例可以用来部分代替函数的规格说明。此外,利用测试用例的输入数据,还可以对源代码序列进行预演。

最简单的测试用例就是那些产生日志或者调试输出的语句,这些语句常用来验证程序的运行是否与预期相同(Java程序则通常使用免费的log4j类库来有效组织和管理日志信息的高效生成)。该编译系统中,显示用于词法、语法分析的中间数据和表格就类似日志功能。

右面板表格是对左面板语句词法分析的结果   图 8-3 右面板表格是对左面板语句词法分析的结果

对于Java程序,有专业的测试工具JUnit(MyEclipse中集成了),它支持测试数据的初始化、测试用例的定义、测试套件的组织、以及测试结果的收集。而Java程序中最常用的方法是,在Java类中添加main方法来对该类进行单元测试。例如编译系统中,compilers.unit_test包就是用来做单元测试的,之所以独立出来是为了不干扰其他模块的代码,而且有一些不太明确的Java用法也先在这里测一下。

总之,测试可以帮助我们阅读代码,了解整个系统的功能。而且测试本身对系统极其重要。我们可能在学校编写的软件出错了,最多崩溃重新改改。而实际中,大公司有专门的测试人员(尤其是银行),小公司负责开发的人员也要参与测试。如果软件在使用中出错或崩溃,尤其是涉及到钱的操作,那带给使用者和自己公司的损失将是无法估计的,可能上万或上千万。所以,软件开发一定要注重软件质量,而测试是守护软件质量的最后一道门。