对于是否需要有代码规范的几种观点
- 这些规范都是官僚制度下产生的浪费大家的编程时间、影响人们开发效率, 浪费时间的东西。
代码规范并不是官僚主义的产物,而是为了增加代码的可读性,使代码变得易读且易维护。书写符合代码规范的代码并不会降低开发效率,相反,这样做可以提高人们的开发、维护效率。
- 我是个艺术家,手艺人,我有自己的规范和原则。
编程的艺术不是从代码规范中体现的,代码的艺术更多的体现在算法设计、数据结构的选取等方面。符合一定的公认的代码规范只会为我们的代码添彩,不会降低代码的艺术性。
- 规范不能强求一律,应该允许很多例外。
每个团队都可以制定自己的代码规范,但是这也是要建立在一定的公认的基础上的。因为公认的代码规范之所以能流传下来,一定有他的道理,这样的代码规范符合人们对代码的认知,便于大家理解代码。
- 我擅长制定编码规范,你们听我的就好了。
一个团队的代码规范往往需要结合大家的意愿来制定,一些规范(比如大括号不换行和大括号换行的两种信仰……)不能仅听一个人的,而应符合大多数人的习惯。
结对伙伴第一次作业的代码复审
General
- Does the code work? Does it perform its intended function, the logic is correct etc.
首先,这位同学(鉴于以下言论中的负面影响较多,就不公布该同学的名字了……)的代码并不能正常工作。
打开源文件,找到main函数,看到第一句话我就惊呆了……
if (scanf_s("Myapp.exe -n %d -r %d", &n, &r) != 2 || n > 10000 || n < 0){ printf("input error"); return 0; }
这尼玛就是传说中的命令行参数!!??好吧,这些细节暂不考虑,暂且认为就是这样的输入吧,我随手输入了一组测试用例,然后我又一次惊呆了……
没用文件输出就算了,连答案都没生成,算式中还有一些奇怪的数字……什么1'1/1啊,0’6/6啊,一些稀奇古怪的数字全都冒出来了……
此外,这位同学也没有进行算式的重复性检查,从刚刚展示出来的那句话来看,明显也没有实现第二个功能(即读取题目和答案,给出评测结果的功能)
从代码逻辑性来看,(先抛开奇葩的输入格式),数字的输出问题是由于程序严重的逻辑缺陷导致的。
这位同学直接生成了一个分数的整数部分,分子部分和分母部分,然后没有任何判断的就直接输出了相应的部分,显然会由于随机函数的问题导致各种各样的无效分数。
此外,这位同学算式的生成也有很大问题,输出格式非常死,这样会导致很多有效的算式不能被生成。
- Is all the code easily understood?
嗯,这位同学的代码写的确实有点迷,但是至少我还是能看懂的……一些函数比如
Gcd()
啊、getNum()
(获得随机数)啊、gettwonum()
啊之类的也都进行了封装,虽然我对这样封装是否合理持怀疑态度,但是至少代码读起来不是毫无头绪。只不过算式的存储形式这位同学直接采用了好多个数组,虽然有注释但是我还是看了好久才明白这些数组都是干嘛用的……
- Does it conform to your agreed coding conventions? These will usually cover location of braces, variable and function names, line length, indentations, formatting, and comments.
由于之前是个人项目,所以我的代码规范并不适用于这位同学,不过这位同学的代码还是符合通用的规范的(虽然有很大一部分重复的代码让人读起来很不爽……)
- Is there any redundant or duplicate code?
之前已经提到了,有很多(大概100行)重复的代码,这是因为这位同学每次随机生成一个分数都写了大概十行代码,然后又分了三种情况(三种运算符数量),这就导致重复使用了很多次生成一个分数的过程,建议这位同学将这段代码封装为一个方法。其他一些小的代码冗余这里就不提及了。(放这位同学一条生路吧……)
- Is the code as modular as possible?
显然没有……最大的问题就是我刚刚提到的生成分数的过程没有完全封装。不过,这位同学封装了getnum、gettwonum、getthreenum这样的方法,在我看来,这些方法仅需要一个getnum就够了,其他的方法其实没必要……
- Can any global variables be replaced?
这位同学的代码中虽然仅出现了temp1、temp2、temp3三个全局变量(大概就是用于getnum之类的函数与外部交互的),但是这也完全没必要……
- Is there any commented out code?
这位同学的代码其实挺干净的,没有被注释掉的代码。
- Do loops have a set length and correct termination conditions?
没有错误。这位同学整个代码仅使用了一个for循环,就是输出n个算式的for循环,这个想出错都难……
- Can any of the code be replaced with library functions?
这位同学的代码其实仅实现了一部分功能,就这部分功能来说,没有可以被替换为库函数的代码段。
- Can any logging or debugging code be removed?
这个代码中没有任何用于调试的代码段。
Security
- Are all data inputs checked (for the correct type, length, format, and range) and encoded?
就这位同学的程序来说,确实对说有读入的安全性均进行了检查(因为读的全是整数嘛……)但是由于一些功能没有实现,所以对于一些本应为正确的输入,这位同学有可能会返回input error的信息。
- Where third-party utilities are used, are returning errors being caught?
没有用到第三方代码……
- Are output values checked and encoded?
输出值没有经过检查,这也是导致输出1'1/1之类数字的一个原因吧。
- Are invalid parameter values handled?
无效的参数确实进行了处理,但是一些有效的参数也被认为是无效的了……(前提是认为他的输入方式是正确的)
Documentation
关于文档这方面,相信很多同学都没有做,因此这里就不再一一赘述了。这位同学至少是有一些注释的,虽然注释的格式有待规范,但是这个习惯其实挺好的。带一些注释的话会使CodeReview以及自己调试的时候变得非常方便。
Test
测试这方面也是我们第一次作业所欠缺的,据我了解,还是有一部分同学做了单元测试的(比如鸣神),但是我不清楚他的代码覆盖率如何……很多同学(包括我在内)也都没有做单元测试,和我结对的这位同学也没有做。不过就他写的这几个函数来看,应该是可以通过单元测试的……经过实际测试,也确实通过了单元测试(都是很简单的函数嘛……)
一些感想
-
在做CodeReveiw时候,我也在想我的代码如果进行这样的CodeReview的话,结果会如何。我第一次作业的代码在实现时其实并没有完全按照我设计的初衷来做,这也直接导致了我的代码在表达式的存储、表达式去重的方面的代码可读性较差。这是在以后的编码中需要特别注意的问题。
-
此外,我们在做项目的时候都很欠缺对文档的分析以及严谨的测试过程,这其实是一个非常不好的习惯。就我来说,代码中没有任何注释,也没有任何说明文档。虽然这次作业的代码其实并不长,但是按照
尹宝林
老师的观点,生存周期超过1天的代码就应该有相应的注释,这样才便于维护和继续开发。 -
关于测试,单元测试其实是一个很好的检查bug的方法,但是我们往往都会因为单元测试太过繁琐而不去做。这样做在一个人开发的时候,或许体现不出来,因为自己对自己的代码都比较了解。但是如果到了团队项目中,大家都不做单元测试,那么如果出现了bug,大家就很难发现是谁的代码出现了问题。而在做了单元测试之后,这样的情况就会减少很多。出现的问题更多的会是没有考虑全情况,而不是已有代码出了错误。