这几章从注释、程序格式、对象与数据结构的规范以及错误处理四个方面介绍了如何使代码变得简洁易懂。不同于上次摘抄的方法,这一次我会结合第一次个人作业的代码进行分析。
第四章 注释
这一章告诉我们,好的注释要满足以下三点要求:
- 尽量避免用注释解释程序意图
- 注释要精练简洁
- 注释要明确,不能给人误导
好的程序往往通过代码就能告诉程序员这段代码要做什么,注释只能出现在可能出现歧义的地方以及需要特别注意的地方。当你想通过添加注释来解释意图的时候,不妨先想一想将功能分块、修改函数名、封装类等方法能不能帮助你更好地表达。万不得已必须要加注释的话,那你一定要注意注释的简洁性与明确性。下面我分析一下第一次个人作业中Linux平台下遍历文件夹的程序注释部分。
1 void traverseFileandCount(char* path, struct alphaArray* dictionary) 2 { 3 DIR *pDir; //定义一个DIR类的指针 4 struct dirent *ent=NULL; //定义一个结构体 dirent的指针,dirent结构体见上 5 int i = 0; 6 char childpath[512]; //定义一个字符数组,用来存放读取的路径 7 pDir = opendir(path); // opendir方法打开path目录,并将地址付给pDir指针 8 memset(childpath, 0, sizeof(childpath)); //将字符数组childpath的数组元素全部置零 9 while ((ent = readdir(pDir)) != NULL) 10 //读取pDir打开的目录,并赋值给ent, 同时判断是否目录为空,不为空则执行循环体 11 { 12 if (ent->d_type&DT_DIR) 13 /*读取 打开目录的文件类型 并与 DT_DIR进行位与运算操作,即如果读取的d_type类型为DT_DIR 14 (=4 表示读取的为目录)*/ 15 { 16 if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) 17 //如果读取的d_name为 . 或者.. 表示读取的是当前目录符和上一目录符, 18 //则用contiue跳过,不进行下面的输出 19 continue; 20 sprintf(childpath, "%s/%s", path, ent->d_name); 21 //如果非. ..则将 路径 和 文件名d_name 付给childpath, 并在下一行prinf输出 22 //printf("path:%s ",childpath);原文链接这里是要打印出文件夹的地址 23 traverseFileandCount(childpath, dictionary); 24 //递归读取下层的字目录内容, 因为是递归,所以从外往里逐次输出所有目录(路径+目录名), 25 //然后才在else中由内往外逐次输出所有文件名 26 } 27 else 28 //如果读取的d_type类型不是 DT_DIR, 即读取的不是目录,而是文件, 29 //则直接输出 d_name, 即输出文件名 30 { 31 //cout<<ent->d_name<<endl; 输出文件名 32 //cout<<childpath<<"/"<<ent->d_name<<endl; 输出带有目录的文件名 33 sprintf(childpath, "%s/%s", path, ent->d_name); 34 //你可以唯一注意的地方是下一行 35 //目前childpath就是你要读入的文件的path了,可以作为你的读入文件的函数的参数 36 count(childpath, dictionary);//这里就是你的处理文件的接口!, 37 } 38 } 39 }
先声明一点:这位同学加这么多注释并不是他的编程习惯,只是为了其他使用这段程序的同学更好懂。
我们来看一看这些注释,很明显,介绍这段程序功能的是注释而非代码!而且,每一句都有注释,降低了程序的可读性和简洁性。如果这段程序出现在我的代码当中,估计助教会疯掉。下面我稍微修改一下这个程序。
#define childPathLength 512 //tranverse file recursively void traverseFileandCount(char* path, struct alphaArray* dictionary) { DIR *filePoint; struct dirent *fileInfo = BULL; char childPath[childPathLength]; filePoint = opendir(path); memset(childPath,0,childPathLength);//init the childPath array with 0 while((fileInfo=readdir(filePoint)) != NULL) { if(isDirectory(fileInfo)) { if(isPresentOrPriorDirectory(fileInfo)) continue; sprintf(childpath, "%s/%s", path, ent->d_name);//create new path traverseFileandCount(childpath, dictionary); } else { sprintf(childpath, "%s/%s", path, ent->d_name);//create new path count(childpath, dictionary); } } } bool isDirectory(struct dirent *fileInfo) { return fileInfo->d_type&DT_DIR; } bool isPresentOrPriorDirectory(struct dirent *fileInfo) { return strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0; }
首先,变量命名上我使用filePoint来表示指向文件的指针,使用fileInfo表示读取的文件信息。这样即使读者不知道DIR和dirent究竟是什么,也可以通过变量的名字猜出来变量的作用。如果此处使用注释,那么当读者看到下面的程序的时候(比如ent->d_type),可能会疑惑这个ent是什么,为了弄明白这个变量的意思,读者不得不返回定义的地方去看。相比之下,filePoint和fileInfo则清楚许多。
接着,我将ent->d_type&DT_DIR和strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0两句比较难懂的话包装成了两个函数,通过函数名来解释它们的作用。当读者读到isDirectory(fileInfo),更容易想到这个函数是用来判断这个文件路径是否是目录的,读到isPresentOrPriorDirectory(fileInfo)的时候,也会大致知道这个函数是判断这个文件路径是否是当前目录或者上一级目录。这样的做法虽然多出了两个小函数,但是省掉了文字注释,提高了程序的可读性,我觉得更好一点。
其他地方,我保留了初始化子路径数组的注释和创建新路径的注释,因为memset和sprintf这两个函数可能有些人不知道。我也试图将其包装成一个函数,但是由于要传输3个参数,这样很大的降低了可读性,所以没有这么做。
第五章 格式
这一章从垂直格式和水平格式两方面告诉读者如何构建一个良好的程序格式。
- 垂直方向,采取从上到下的编程模式。被调用函数紧跟在被调用函数后面。
- 水平方向,注意缩进要清晰,变量定义无需对齐,一行代码不要过长。
下面看一下第一次个人作业我的主函数。
int main() { char filePath[filePathLength]; struct alphaArray dictionary[alphabet]; struct wordStatisticsResult topFrequencyWord[topFrequencyWordNum]; struct phaseStatisticsResult topFrequencyPhase[topFrequencyPhaseNum]; dictionaryInit(dictionary); getFilePath(filePath); traverseFileandCount(filePath, dictionary); outputResult(dictionary); topFrequencyWordStatistics(dictionary, topFrequencyWord); topFrequencyPhaseStatistics(dictionary, topFrequencyPhase); outputToFile(filePath,topFrequencyWord, topFrequencyPhase); dictionaryDestroy(dictionary); return 0; }
我现在很喜欢这种编程方法:在主函数里面将不同的功能分块,然后逐个实现,实现过程中不断细分功能模块。这样从上到下的编程很高效,很清楚,而且程序阅读起来也有条理。垂直格式上我自认为做得不错,可是水平格式上就丑了许多。
(page->wordArray + hash)->wordStr = (char*)realloc((page->wordArray + hash)->wordStr, sizeof(char)*((page->wordArray + hash)->strlength) * 2);
很丑是吧。(page->wordArray + hash)->wordStr实在是太长了,为什么不用tempWordStr代替呢?第一次作业时间有点紧张所以没有优化,下次我会注意的。
良好的垂直和水平格式会使你的代码更清晰美观。试想一下有良好格式的代码是一位三围比例合适的美女,你忍心自己写出的代码是一位身体比例不协调的丑女吗?所以,一定要在这方面下功夫,你的程序会因此赏心悦目。
第六章 对象和数据结构
由于Java还不怎么会用,所以这一部分也是囫囵吞枣的读了一遍。大致意思就是,尽量抽象类的接口,将类内部的情况隐藏起来,问了同学,说这样做可以减少耦合。
第七章 错误处理
同样,在c里面没用过try/catch进行错误处理,所以这一部分也不是很明白。只记得里面说先写try/catch语句定义好范围,然后编写细节部分。
注:以上代码以及修改只是我现在能做到的水平,如有不足欢迎指点!