关于代码阅读的编程实现技巧及自己工作学习的一些经验(推荐看后面的后记)

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

第9章  编程实现技巧及注意事项

这一章主要写一些关于实际编程的内容,主要为编写易阅读易维护代码。像编译原理这门比较抽象的课程,LL(1)语法分析的原理你可能懂了,但如何用程序实现却是个难题。所以你需要多编程多实践多交流。下面分享一下这方面的经验心得,有的会涉及到很小的细节。

9.1基本编程元素

多数情况下,代码阅读是个自底向上的活动。这一章将讲述一些需要注意的基本编程元素,并概述如何对其进行阅读与思考。

(1)对于一些已经提供的系统函数,其执行效率可能达不到我们的要求,不过可以对其重新封装。例如,标准C函数strcmp(字符串比较函数),可以定义一个宏STREQ(a, b),#define STREQ(a, b) (*(a) == *(b)&& strcmp((a),(b)) == 0),这样就可以减少字符比较次数,另外,宏中的参数用小括号括起来是个良好的保护性编程实践。

(2)使用未初始化的变量可能导致问题,所以要在定义变量的同时就赋初值。你可能会说不是有默认值吗,干嘛还这么麻烦。即使这样,为了方便别人阅读代码,不至于导致混乱,而且不同语言对未赋值的变量处理不同。例如C语言中 int n; 则n为随机值,若程序中出现这样的代码,极易导致程序崩溃。

关于代码阅读的编程实现技巧及自己工作学习的一些经验(推荐看后面的后记)_第1张图片

图 9-1 Java类中定义变量的同时就赋初值(现C++11也支持这种用法)

(3)在分析一个重要程序时,最好找出其中重要的部分,例如全局变量(作用域大)。

(4)在C语言中,只用于单个文件的全局变量最好声明为static(防止多个文件同命名)。C++中使用命名空间,如 using namespace std; Java中使用包,如package compilers.javabean;

(5)查看一个函数的方法:

l 根据函数名猜测。

l 阅读函数开头的注释。

l 分析该函数是如何使用的。

l 阅读函数体的代码。

l 查询外部程序文档。

l 与函数的编写者或使用者交流。

 关于代码阅读的编程实现技巧及自己工作学习的一些经验(推荐看后面的后记)_第2张图片

图 9-2 如何查看函数示例

(6)使用渐进式的阅读方法,即先理解较容易的部分,然后可能使其他部分变得容易理解。如果代码的作者,例如工作中的同事在身边的活,去询问他理解困难的地方,可以获得极大的帮助(不要不好意思问,因为这将节约整个项目组的时间)。

(7)开发者要养成碰到库函数就阅读文档的习惯,这有助于增强自身的代码阅读和编写能力。例如微软的MDDN,Unix/Linux的man,Java的API文档(笔者编写该系统时,就经常查阅Java API文档)。

(8)switch的case会处理到break,如果需要共享,最好添加注释/* FALLTHROUGH */。另外,使用default处理忽然遇到的意外值,请记住,不管需不需要default,最好都添加上,这是一个良好的保护性编程实践。

   关于代码阅读的编程实现技巧及自己工作学习的一些经验(推荐看后面的后记)_第3张图片

图 9-3 switch case 注意事项(代码来自 NetBSD)

(9)for( ; ; )这种无限循环来表达循环开始或结束时退出条件无法确定的循环。不过也有真正的无限循环,如果你学过操作系统或看过Linux内核,就会知道进程的实质就是无限循环(死循环)。

(10)使用德·摩根法则简化比较逻辑:!(a || b) <=> !a&& !b; !(a && b) <=> !a || !b。由于比较采用的是短路求值,应该把概率大的放在最左边。


图 9-4 简化比较操作

(11)有时,创造性的代码布局可以提高代码的可读性。反面的例子很多,可以在国际混换C代码竞赛网站上(www.ioccc.org)找到很多,而且它们大多很有趣。

(12)添加空格,使用临时变量,使用小括号都可以提高表达式的可读性。

例如 if(! isTrue(a, b))。

(13)在阅读由自己控制的代码时,要养成添加注释的习惯。如下图是编译系统主函数入口注释,为标明该类含有main函数。

关于代码阅读的编程实现技巧及自己工作学习的一些经验(推荐看后面的后记)_第4张图片

图 9-5 main函数块注释

(14)一个项目应采用一种标准的风格(如GNU或BSD),并始终如一的使用,详见8.4节编码规范和约定。

(15)if(a = b)可能存在“=” “= =” 错误,避免这类错误的方法是,对于常量,可以放在左边,例如if(0 ==a)。不过对于java、C#不需要担心此类错误,因为这类语言的流程语句只接受布尔值。

(16)while(column& 7) [......],这样的代码并不好理解。若b= 2n – 1,则 a & b(逻辑与操作)可以理解为 a % (b + 1) ,这样做的目的是使无符号的计算更高效。实际上,现在的优化编译器可以识别出这种情况并自己做替换,而除法和按位与操作在现代处理器上差距没有那么大了。因为,应会读这些代码,但避免写出这样的代码(另一个移位常用的操作是 *2n或 /2n ,对应左移和右移)。

(17)阅读控制结构代码时,可以将注意力集中在如何在抽象层对他们推理,即保存主要的控制结构,if, else, for, while 等,屏蔽无关细节代码。

9.2常用数据结构

程序通过对算法的应用做用于数据之上,因而数据内部组织结构对算法的执行而言十分重要。

(1)理解动态内存管理的基本操作有助于我们更加清晰地阅读程序代码。例如为结构体分配内存空间,先定义宏#define new(type) (type *)calloc(sizeof(type), 1),再分配空间node = new (structcodeword_entry),并且一定要检查malloc函数的返回值。

(2)在函数参数中使用引用时,为明确参数的值是否会被函数影响,可以使用/* IN */ 或 /* OUT */注释。例如 void getNum(char a, int *n /* OUT */ )。

(3)使用全局变量或者静态局部变量的函数在大多数情况下是不可重入的,即不可并发访问,这点在编写分布式程序时尤其要注意。

(5)结构体中各个字段的存储顺序与编译器和机器架构相关,并且每个元素的表示方法取决于具体的体系架构和操作系统。因为用结构体来映射外部数据有着与生俱来的不可移植性。

(6)C程序的typedef声明用来增强语言的抽象能力、提高代码的可读性和可移植性。

(7)C中对数组的操作,初始化为0:static char buf[128]; memset(buf,0, sizeof(buf));复制:memcpy(dest,src, sizeof(desc)); 这里需要判断desc和src是否指向同一内存空间,否则使用memmove()。

(8)若被调用函数中含有数组参数,如果访问数组外界元素,会导致缓冲区溢出。开发者对于那些可能重写其缓冲区的函数,诸如strcat, strcpy, spintf, gets 和 scanf等,应当谨慎使用或不用,或用其安全替代,strncat, strncpy, snprintf, fgets等。据不完全统计,针对微软Windows的攻击,75%都是利用缓冲区溢出攻击,你也可以在计算机安全的课上学到如何利用缓冲区溢出。

9.3其他编程技巧

笔者在这篇文章中列举的编程技巧都是非常巧妙的或者之前没有注意到的,还有更多常见的技巧,可以查阅像林锐博士写的《高质量C++/C编程》之类的书籍。

(1)在有错误返回的函数中,尽量把错误判断放在最前,一是效率较高,二是控制流程嵌套较少,阅读体验更好,如下图:

关于代码阅读的编程实现技巧及自己工作学习的一些经验(推荐看后面的后记)_第5张图片 

图 9-6 函数内部先处理错误

9.4后记

下面我想讨论一下非技术因素。

(1)态度。

不管是编程还是做其他的事,首先,积极、认真的态度是最重要的,它会直接体现在你做的东西上面,进而改变别人对你的直接看法。而且,它会影响你做事情的专注程度,遇到困难如何面对,能否坚持等。在公司每周五都要提交周志,有的人就随便写几句,而有的人认真总结这一周做的事,从而造成上级主管不同的印象,进而也会影响你平日的工作。所以,树立良好的态度是做事情前最重要的。

(2)读经典书籍。

关于计算机的书已经铺天盖地了,但真正成为经典的不多。我们的时间是有限的,所以要把时间用在经典书籍上,它们都经历了长时间和无数人的考验,价值不菲。想成为优秀的程序员一定要多读书,读好书。关于应该读哪些经典的书,可以去伯乐在线网站。

我把计算机类的书分为两类,一种是原理性的,包括计算机科学、离散数学、语言类、算法类、数据结构类等,这个是我们首先学习的,而且是打基础的,在学校一定要学好;另一种是技能实战性的,能在实际工作中用到的。例如做C++编程要了解COM组件,熟悉STL库,做网络编程要读《Unix网络编程》、《Unix高级环境编程》等,做Windows开发要读《Windows程序设计》、《Windows核心编程》等。这种书和你想要从事的方向和行业有关,在走向社会工作之前一定要有准备。这也是我一次面试失败得到的经验,自己就是第一种书看的太多,忽略了另一种,这和自己一直方向不明确也有关。所以希望你能够尽早找到自己的兴趣和方向,成为这个领域的专家。

(3)编程语言之争。

现在,计算机的语言已经有上百种了,而且还在随着技术的发展而推陈出新。究竟要学习什么语言呢?像我学了C之后是C++,然后是Java,自学了C#,虽然这都是很流行的语言,但我认为是极不合理的,例如C++、Java、C#都是面向对象的,实质上差不多。Peter Norvig在《用十年来学编程中》是这样推荐的,“学会至少半打编程语言。包括一门支持类抽象(class abstraction)的语言(如Java或C++),一门支持函数抽象(functional abstraction)的语言(如Lisp或ML),一门支持句法抽象(syntactic abstraction)的语言(如Lisp),一门支持说明性规约(declarativespecification)的语言(如Prolog或C++模版),一门支持协程(coroutine)的语言(如Icon或Scheme),以及一门支持并行处理(parallelism)的语言(如Sisal)”,最后一门我想补充Erlang语言,是一种分布式并行开发语言,笔者公司的服务器端开发用的就是Erlang。

另一点我想说明的是,不要对语言抱有太大的偏见或宗教主义,除非你非常精通。如果被过多的语言弄花了眼,得不偿失。真正要学习的是一些共性的知识,如底层架构、操作系统、算法、数据结构等等,这些基础的理论和知识是你真正需要掌握的,有了它们你就可以融汇贯通,否则学到的永远是皮毛,在工作中也不容易确定自己的核心竞争力。

(4)编程环境。

一个好的舒服的编程环境肯定能提高编程效率和编程体验。但我发现即使是工作的同事,有的对于集成开发环境IDE(公司用的是VisualStudio 2010)用的也不熟练。对于开发环境,首先是熟悉它的界面、功能,然后是有哪些使用技巧,如使用快捷键、按照自己的需要重新布局。熟悉这些当然要先花费些时间,但是磨刀不误砍柴工。


另外,不要局限在一个小的认知圈中,有的同学学C、C++一开始用VC++6.0,之后就只会用它,殊不知好用的编程环境有很多,例如,Windows上有Visual Studio系列,Eclipse C++版,Linux上有Vim,Emacs,使用gcc编译器。以前自己就不会也不愿尝试新的开发环境,都是同学介绍的。现在慢慢知道了如何去找最适合自己的编程环境。例如,开发项目一般都用相关语言专业的集成开发环境IDE,而自己学习,练习或写些小项目开始用Sublime Text,这是一种轻量级的代码编辑器,如下图:

关于代码阅读的编程实现技巧及自己工作学习的一些经验(推荐看后面的后记)_第6张图片

图 9-7 Sublime Test 2代码编辑器

(5)写在最后。

以上两章中提到的有些词我也可能只知道概念,但希望能够开拓视野,如果你恰好对哪个感兴趣,可以深入的去学习研究。还有一些建议是当时或者现在都没有做到的,希望你可以意识到。我们经常会看到或听到前人的经验和教训,但很少有人能够真正去做,最聪明的学习者能用别人的经历帮助自己成长。我毕竟只是学习了四年,工作了几个月,有些地方难免有错,如果你有什么问题或有不同的看法,请联系我[email protected]

相信每个人都能通过每天持续的努力完成自己想要做的事!

你可能感兴趣的:(编程视界,代码阅读,源代码,编程,CC++,Java)