精华内容
下载资源
问答
  • 代码复审

    2017-10-03 11:19:00
    代码复审 概要部分 代码能符合需求和规格说明么? 符合 代码设计是否有周全的考虑? 不能说是周全,应该是经过一段时间的思考设计后再开始写的,不过有很多细节没有提前想到。 代码可读性如何? 有部分变量命名和...

    A.代码复审

    概要部分

    • 代码能符合需求和规格说明么?
      符合
    • 代码设计是否有周全的考虑?
      不能说是周全,应该是经过一段时间的思考设计后再开始写的,不过有很多细节没有提前想到。
    • 代码可读性如何?
      有部分变量命名和函数名不大易懂,经过交流得到了改善
      具体问题和交流过程见git https://github.com/ZiJiaW/Sudoku/issues/1
    • 代码容易维护么?
      功能做了一定的模块化,连接比较清晰,核心算法不是非常复杂,但是实现是全部在一个函数里,递归终止的条件和返回值的意义有些让人难懂
    • 代码的每一行都执行并检查过了吗?

    设计规范部分

    • 设计是否遵从已知的设计模式或项目中常用的模式?
      直观感觉上,设计较为合理,除了一些封装的问题和局部的代码存在冗余的问题。
    • 有没有硬编码或字符串/数字等存在?
      有,已解决
    • 代码有没有依赖于某一平台,是否会影响将来的移植(如Win32到Win64)?
      暂且没有
    • 开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现?在本项目中是否存在类似的功能可以调用而不用全部重新实现?
      存在,其SudokuMaker和SudokuSolver类采用了相同的数据结构和类似的算法,可以考虑合并其功能以重用代码。
    • 有没有无用的代码可以清除?(很多人想保留尽可能多的代码,因为以后可能会用上,这样导致程序文件中有很多注释掉的代码,这些代码都可以删除,因为源代码控制已经保存了原来的老代码。)
      存在代码的类似及重复,但这是设计上的原因,可以清除但不损失功能的代码不存在。

    代码规范部分

    • 修改的部分符合代码标准和风格么(详细条文略)?
      大部分已经符合,部分变量名没有完全更改

    具体代码部分

    • 有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常?
      有,部分的file指针没有检查,没有异常的抛出 姑且不必处理
    • 参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度,是以0开始计数还是以1开始计数?
      没有
    • 边界条件是如何处理的?Switch语句的Default是如何处理的?循环有没有可能出现死循环?
    • 有没有使用断言(Assert)来保证我们认为不变的条件真的满足?
      没有
    • 对资源的利用,是在哪里申请,在哪里释放的?有没有可能导致资源泄露(内存、文件、各种GUI资源、数据库访问的连接,等等)?有没有可能优化?
      有存在泄漏情况,详情见 https://github.com/ZiJiaW/Sudoku/issues/2 有可能优化
    • 数据结构中是否有无用的元素?
      存在一些微小的问题

    效能

    • 代码的效能(Performance)如何?最坏的情况是怎样的?
      使用了回溯法求解数独,性能一般
    • 代码中,特别是循环中是否有明显可优化的部分(C++中反复创建类,C#中string的操作是否能用StringBuilder 来优化)?
            for (auto p = ProblemSet.begin(); p != ProblemSet.end(); ++p, ++i)
            {
                SudokuSolver solution;
                solution.ProblemInit(*p);
                if (!solution.StartSolving())
                {
                    cout << "No Solution in problem NO." << i + 1 << endl;
                    return 0;
                }
                int(*r)[9] = solution.getSolution();
                for (int k = 0; k < 9; ++k)
                {
                    for (int l = 0; l < 9; ++l)
                    {
                        result[9 * k + l + 81 * i] = char(r[k][l] + '0');
                    }
                }
            }

    该循环中getSolution函数会申请新的内存来存放解,而这一步可以通过在循环外部建立静态缓存来优化

    • 对于系统和网络调用是否会超时?如何处理?
      不存在可能超时的调用

    可读性

    • 代码可读性如何?有没有足够的注释?
      一般,有些变量的意义没有给出注释,有些变量的定义和使用相隔较远

    可测试性

    • 代码是否需要更新或创建新的单元测试?
      需要

    B.设计一个代码规范

    1.工具提供的代码规范和你个人的代码风格有什么不同?

    • 库头文件写在工程头文件前面
    • 使用using-declarations 而不是 using-derectives
    • { 写在前一行结尾
    • 不使用tab 而使用空格
    • 不在代码块内留空行
    • , { , != , +之前留空格

    2.工具提供的代码规范里有哪些部分是你之前没有想到的?

    除了tab和大括号的用法之外都没有留意过

    3.为什么要这样规范?这样的规范有意义吗?

    使得不同的程序员写出来的代码不会差异太大,在进行团队合作时更有利。也使个人的代码风格更为规范,但是像“{”加在前一行尾还是新开一行的问题就显得没多大意义。

    4.代码规范

    • 缩进用空格
    • 单行长度不超过80
    • if分支全部使用大括号
    • 大括号独占一行
    • 多条语句分行
    • 类/函数名使用Pascal,变量名使用lowerCamel
    • 类成员的申明先public后private
    • 函数重载不能让人不清楚其语义

    转载于:https://www.cnblogs.com/felixcaae/p/7623418.html

    展开全文
  • 代码复审实战

    2019-09-25 00:53:56
    代码复审实战 0.前言 结对伙伴:顾舒婷 代码地址:Coding.net 1.部分代码示例 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> void A();//A->V:=E void ...

    代码复审实战

    0.前言


    结对伙伴:顾舒婷

    代码地址:Coding.net

    1.部分代码示例


    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<ctype.h>
    void A();//A->V:=E
    void E();//E->TE'
    void T();//T->FT'
    void E1();//E'->+TE'|-TE'|NULL
    void T1();//T'->*FT'|/FT'|NULL
    void F();//F->V|(E)
    void V();//V->a|b|c|d....|z
    char s[50],n='1';//s[50]”√”⁄¥Ê∑≈ ‰»Îµƒ∏≥÷µ±Ì¥Ô Ω
    char Table[50][3]; //≤˙…˙÷–º‰¥˙¬ÎÀ˘–˵ƒ∑˚∫≈±Ì
    int SIGN,sym;//symŒ™s[50]÷–µ±«∞∂¡»Î∑˚∫≈µƒœ¬±Í
    int list_n=0;//∑˚∫≈±Ìµƒœ¬±Í
    int main()
    {
       SIGN=0;
       sym=0;
       scanf("%s",&s);
       if(s[0]=='\0')
       return 0;
       A();
       if(s[sym]!='\0'&&SIGN==0)
       {
          printf("ERROR!\n");
          exit(0);
       }
       return 0;
    }

    2.代码审查


    功能模块名称 简单语法分析
    审查人 张晓涛
    审查日期 2018.4.5
    代码名称 简单的语法分析.cpp
    代码作者 顾舒婷
    文件结构
    重要性 审查项 结论
    头文件和定义文件的名称是否合理?
    头文件和定义文件的目录结构是否合理?
    版权和版本声明是否完整?
    重要 头文件是否使用了 ifndef/define/endif 预处理块?
    头文件中是否只存放“声明”而不存放“定义”
    程序的版式
    重要性 审查项 结论
    空行是否得体?
    代码行内的空格是否得体?
    长行拆分是否得体?
    “{” 和 “}” 是否各占一行并且对齐于同一列?
    重要 一行代码是否只做一件事?如只定义一个变量,只写一条语句。
    重要 If、for、while、do等语句自占一行,不论执行语句多少都要加 “{}”。
    重要 在定义变量(或参数)时,是否将修饰符 * 和 & 紧靠变量名?注释是否清晰并且必要?
    重要 注释是否有错误或者可能导致误解?
    命名规则
    重要性 审查项 结论
    重要 命名规则是否与所采用的操作系统或开发工具的风格保持一致?
    标识符是否直观且可以拼读?
    标识符的长度应当符合“min-length && max-information”原则?
    重要 程序中是否出现相同的局部变量和全部变量?
    类名、函数名、变量和参数、常量的书写格式是否遵循一定的规则?
    静态变量、全局变量、类的成员变量是否加前缀?
    表达式与基本语句
    重要性 审查项 结论
    重要 如果代码行中的运算符比较多,是否已经用括号清楚地确定表达式的操作顺序?
    是否编写太复杂或者多用途的复合表达式?
    重要 是否将复合表达式与“真正的数学表达式”混淆?
    重要 是否用隐含错误的方式写if语句? 例如
    (1)将布尔变量直接与TRUE、FALSE或者1、0进行比较。
    (2)将浮点变量用“==”或“!=”与任何数字比较。
    (3)将指针变量用“==”或“!=”与NULL比较。
      如果循环体内存在逻辑判断,并且循环次数很大,是否已经将逻辑判 
      断移到循环体的外面?
    重要 Case语句的结尾是否忘了加break?
    重要 是否忘记写switch的default分支?
    重要 使用goto 语句时是否留下隐患? 例如跳过了某些对象的构造、变量的初始化、重要的计算等。
    常量
    重要性 审查项 结论
      是否使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串?
    在C++ 程序中,是否用const常量取代宏常量? 
    重要 如果某一常量与其它常量密切相关,是否在定义中包含了这种关系?
      是否误解了类中的const数据成员?因为const数据成员只在某个对象
      生存期内是常量,而对于整个类而言却是可变的。
    函数设计
    重要性 审查项 结论
      参数的书写是否完整?不要贪图省事只写参数的类型而省略参数名字。
      参数命名、顺序是否合理?
      参数的个数是否太多?
      是否使用类型和数目不确定的参数?
      是否省略了函数返回值的类型?
      函数名字与返回值类型在语义上是否冲突?
    重要 是否将正常值和错误标志混在一起返回?正常值应当用输出参数获得,而错误标志用return语句返回。
    重要 在函数体的“入口处”,是否用assert对参数的有效性进行检查?
    重要 使用滥用了assert? 例如混淆非法情况与错误情况,后者是必然存在的并且是一定要作出处理的。
    重要 return语句是否返回指向“栈内存”的“指针”或者“引用”?
      是否使用const提高函数的健壮性?const可以强制保护函数的参数、返回值,甚至函数的定义体。“Use const whenever you need”
    内存管理
    重要性 审查项 结论
    重要 用malloc或new申请内存之后,是否立即检查指针值是否为NULL?(防止使用指针值为NULL的内存)
    重要 是否忘记为数组和动态内存赋初值?(防止将未被初始化的内存作为右值使用)
    重要 数组或指针的下标是否越界?
    重要 动态内存的申请与释放是否配对?(防止内存泄漏)
    重要 是否有效地处理了“内存耗尽”问题?
    重要 是否修改“指向常量的指针”的内容?
    重要 是否出现野指针?例如
    (1)指针变量没有被初始化;
    (2)用free或delete释放了内存之后,忘记将指针设置为NULL。
    重要 是否将malloc/free 和 new/delete 混淆使用?
    重要 malloc语句是否正确无误?例如字节数是否正确?类型转换是否正 确?
    重要 在创建与释放动态对象数组时,new/delete的语句是否正确无误?
    C++ 函数的高级特性
    重要性 审查项 结论
      重载函数是否有二义性?
    重要 是否混淆了成员函数的重载、覆盖与隐藏?
      运算符的重载是否符合制定的编程规范?
      是否滥用内联函数?例如函数体内的代码比较长,函数体内出现循环。

    3.评价


    我的结对伙伴代码的程序版式和常量的命名规则都很规范,表达式和基本语句也都做了处理,避免了太长,很便于理解。函数的设计也理解到位,递归思想也发挥的淋漓尽致,“码”如其人,也体现了这份代码的主人心思之细腻。注释也简洁精炼,虽然由于我们IDE不同导致了乱码,但也能看出能把常量和函数的功能总结成短短数字,反映了此程序员不拖沓,办事可靠的性格特点。更让我叹为观止的是该设计者定义了两个全局变量从而省去了函数的传参,整篇代码设计了7个函数,返回值全部为void,匠心独具,真可谓妙哉妙哉!

    void A();//A->V:=E
    void E();//E->TE'
    void T();//T->FT'
    void E1();//E'->+TE'|-TE'|NULL
    void T1();//T'->*FT'|/FT'|NULL
    void F();//F->V|(E)
    void V();//V->a|b|c|d....|z

    大到头文件引用,函数声明,小到变量赋值,数据的下角标,这已经不仅仅是能完成‘简单的语法分析’功能的一份代码了,更是一件艺术品,哦,不,艺术品只是有观赏性,应该用文物来形容,兼具观赏性的同时记录着匠人的心血,虽缺乏岁月的锤炼,但值得后世的程序猿敬仰并一代一代的传承。能有幸观摩并复审这份代码,实属在下的荣幸,如果非要鸡蛋里挑骨头的话,我也只能说函数名的定义可能过于简练,单用一个字母不便于理解函数的功能,但也可能是我无法理解大师的用心,如有冒犯,还望不吝赐教。
    对了,忘了介绍我的结对伙伴,顾舒婷同学,为人随和,美丽大方,温柔善良,实乃居家旅行之……咳咳,有点跑题了,但总之不会有差。希望在今后的结对编程中也能得到顾同学的点拨,现在此谢过(手动膜拜表情)。

    4.小结


    这次结对编程,让我体会到了之前自己代码版式,命名的不规范,想要有一份别人能读懂的代码,就应该从细节入手,一步步完善,这样才能打好基础,有所进步。

    转载于:https://www.cnblogs.com/stupiderman/p/8727073.html

    展开全文
  • 代码规范与代码复审

    2015-03-25 22:40:51
    阿超建议大家互相看看别人的代码,在TFS中每个人都把各自项目的权限放宽,允许别人访问,交流一下意见。 两个小时后,小飞来抱怨说,九条的代码都是一行到底,随意缩进,跟他提了意见,他还说“编译通过就行了”。 ...

    在第9章中,同学们完成了WC程序,经过评比,九条的程序获得了第一名。这时,阿超说,现代软件产业经过几十年的发展,已经不可能出现一个人单枪匹马完成一个软件的事情了,软件都是在相互合作中完成的。阿超建议大家互相看看别人的代码,在TFS中每个人都把各自项目的权限放宽,允许别人访问,交流一下意见。

    两个小时后,小飞来抱怨说,九条的代码都是一行到底,随意缩进,跟他提了意见,他还说“编译通过就行了”。

    他们找到了九条。

    九条:我打麻将的时候牌都是乱摆,赢的也不少呀。

    阿超:为什么要乱摆?

    九条:因为怕围观的人看清我的牌路,我自己清楚就行了。

    上课了。阿超问大家,我们写的代码是给人看的,还是给机器看的?

    杂曰:人也看,机器也看。

    阿超:对,但是最终是人在看。而且和打麻将不同,我们的代码要让“旁观者”看得清清楚楚。请看下一段代码,如代码清单10-1所示,如果你接手这样的代码,有什么感想?

    代码清单10-1  badly formatted code – big C[1]

                    #include "stdafx.h"

                #include             "stdio.h"

              void test();

                       int _tmain

                       (int argc,

                          _TCHAR*           argv[])

                              { test(); return

                                        0; }

     

                                     char C[25][40];void d(int x,int y)

                   {C[x][y]=C[x][y+1]=32;}int f(int x){return (int)x*x*.08;}

                   void test(){int i,j;                                    char s[5]="TEST";

              for(i=0;i<25;i++)

             for(j=0;j<40;j++)

            C[i][j]=s[(i+j)%4];

           for(i=1;i<=7;i++)

           {d(18-i,12);

           C[20-f(i)][i+19]=

            C[20-f(i)][20-i]=32;

             }d(10,13);d(9,13);

              d(8,14);d(7,15);

                  d(6,16);d(5,18);d(5,20);                         d(5,22);d(5,26);

                      d(6,23);d(6,25);d(7,25);for(i=0;i<25;i++,printf("\n"))

                                      for(j=0;j<40;printf("%c",C[i][j++]));}


    同学们纷纷发言,基本上有如下的反应:

    1Faint!!

    2)重写程序!!

    3)找到原作者,暴打一顿!!!

    4)让此人从公司辞职!!!!

    计算机只关心编译后的机器码,你的程序是什么样的缩进风格,以及变量名是否有统一的规范等和机器码的执行无关。但是,做一个有商业价值的项目,或者在团队里工作,代码规范相当重要。

    我们讲的“代码规范”可以分成两个部分。

    1)代码风格规范。主要是文字上的规定,看似表面文章,实际上非常重要。

    2)代码设计规范。牵涉到程序设计、模块之间的关系、设计模式等方方面面,这里有不少与具体程序设计语言息息相关的内容(如C/C++/Java/C#),但是也有通用的原则,这里主要讨论通用的原则。

    代码风格的原则是:简明,易读,无二义性。

    提示:这是移山公司的一家之言,如果碰到争执,关键是要本着“保持简明,让代码更容易读”的原则,看看如下争执中的代码规范是否能够让程序员们更好地理解和维护程序。

    10.1.1  缩进

    是用Tab键好,还是248个空格?

    结论:4个空格,在VS2005和其他的一些编辑工具中都可以定义Tab键扩展成为几个空格键。不用 Tab键的理由是Tab键在不同的情况下会显示不同的长度。4个空格的距离从可读性来说正好。

    10.1.2  行宽

    行宽必须限制,但是以前有些文档规定的80字符行宽太小了(以前的计算机/打字机显示行宽为80字符),现在时代不同了,可为100字符。

    10.1.3  括号

    在复杂的条件表达式中,用括号清楚地表示逻辑优先级。

    10.1.4  断行与空白的{ }行

    程序的结构是什么风格?下面有几种格式,我们一一讨论。

    最精简的格式A

    if (condition)    DoSomething();

    else     DoSomethingElse();

    有人喜欢这样,因为可以节省几行,但是不同的语句(Statement)放在一行中,会使程序调试(DeBug)非常不方便,如果要一步一步观察conditioncondition有可能是包含函数调用的复杂表达式)中各个变量的变化情况,单步执行就很难了。

    因此,我们还是要有断行,这样可以得到如下的结构——格式B

    if (condition)

        DoSomething();

    else

        DoSomethingElse();

    这样的结构,由于没有明确的“{”和“}”来判断程序的结构,在有多层控制嵌套的时候,就不容易看清结构和对应关系。下面的改进(格式C)虽好,但是阿超认为还是不够清晰:

    if ( condition) {

        DoSomething();

    } else {

        DoSomethingElse();

    }

    于是我们最后做了这个选择,每个“{”和“}”都独占一行。就是格式D

    if ( condition)

    {

        DoSomething();

    }

    else

    {

        DoSomethingElse();

    }

    10.1.5  分行

    不要把多行语句放在一行上。

    a = 1; b = 2;           // bogus

    if (fFoo) Bar();        // bogus

    更严格地说,不要把不同的变量定义在一行上。

    Foo foo1, foo2;         // bogus

    10.1.6  命名

    阿超:我在某个同学的程序中看到有些变量叫“lili”,“yunyun”,不知道这些变量在现实生活中有没有什么意义。

    下面哄笑起来。

    果冻:(红着脸问)那有些变量的确想不出名字,简单的变量像ijk都用完了,怎么办?

    阿超:当我们的程序比“Hello World”复杂10倍以上的时候,像给变量命名这样简单的事看起来也不那么简单了。我们就来谈谈如何起名字这个问题。程序中的实体、变量是程序员昼思夜想的对象,要起一个好的名字才行。大家都知道用单个字母给有复杂语义的实体命名是不好的,目前最通用的,也是经过了实践检验的方法叫“匈牙利命名法”。例如:

    fFileExist,表明是一个bool值,表示文件是否存在;

    szPath,表明是一个以0结束的字符串,表示一个路径。

    如此命名的目的是让程序员一眼就能看出变量的类型,避免在使用中出错。早期的计算机语言(如BCPL)不作类型检查,在C语言中,intbytecharbool大概都是一回事。下面这一句话:

    if (i)

    从语义来说,i可以是表示真/假的一个值,也可以表示长度是否为零,

    还可以表示是否到了字符串的结束位置,或者可以表示两个字符串比较的结果不相等(strcmp()返回-101)。从程序的文字上,很难看出确切的语义。

    同样是字符串类型,char *BSTR的有些行为是很不一样的。

    HRESULT的值也可以用来表示真假,但是HR_TRUE == 0HR_FALSE ==1,这和通常的true/false刚好相反。

    大部分的程序,错就错在这些地方!在变量面前加上有意义的前缀,就可以让程序员一眼看出变量的类型及相应的语义。这就是“匈牙利命名法”的用处。

    匈牙利命名法的一些通用规定,见本书附录B(第337页)。

    还有一些地方不适合用“匈牙利命名法”,比如,在一些强类型的语言(如C#)中,不同类型的值是不能做运算的,对类型有严格的要求,例如C# 中,if()语句只能接受bool值的表达式,这样就大大地防止了以上问题的发生。在这样的语言中,前缀就不是很必要的,匈牙利命名法则不适用了。Microsoft .Net Framework就不主张用这样的法则。

    10.1.7  下划线问题

    下划线用来分隔变量名字中的作用域标注和变量的语义,如:一个类型的成员变量通常用m_来表示。移山公司规定下划线一般不用在其他方面。

    10.1.8  大小写问题

    由多个单词组成的变量名,如果全部都是小写,很不易读,一个简单的解决方案就是用大小写区分它们。

    Pascal——所有单词的第一个字母都大写;

    Camel——第一个单词全部小写,随后单词随Pascal格式,这种方式也叫lowerCamel

    一个通用的做法是:所有的类型//函数名都用Pascal形式,所有的变量都用Camel形式。

    /类型/变量:名词或组合名词,如MemberProductInfo等。

    函数则用动词或动宾组合词来表示,如get/set; RenderPage()

    10.1.9  注释

    谁不会写注释?但是,需要注释什么?

    不要注释程序是怎么工作的(How),你的程序本身就应该能说明这一问题。

    //this loop starts the i from 0 to len, in each step, it

    // does SomeThing

    for (i = 0; i<len; i++)

    {
             DoSomeThing();

    }

    以上的注释是多余的。

    注释是用来解释程序做什么(What),为什么这样做(Why),以及要特别注意的地方的,如下:

    //go thru the array, note the last element is at [len-1]

    for (i = 0; i<len; i++)

    {

        DoSomeThing();

    }

    复杂的注释应该放在函数头,很多函数头的注释都是解释参数的类型等的,如果程序正文已经能够说明参数的类型in/out等,就不要重复!

    注释也要随着程序的修改而不断更新,一个误导的(Misleading)注释往往比没有注释更糟糕。

    另外,注释(包括所有源代码)应只用ASCII字符,不要用中文或其他特殊字符,它们会极大地影响程序的可移植性。

    在现代编程环境中,程序编辑器可以设置各种好看的字体,我们可以使用不同的显示风格来表示程序的不同部分。

    注意: 有些程序设计语言的教科书对于基本的语法有详细的注释, 那是为了教学的目的, 不宜在正式项目中也这么做。

     

    下面的例子中,有很多注释, 但是那些注释是非常必要的呢? 哪些错误处理是多余的?

    Protected Sub txtSSN_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtSSN.TextChanged

    Try  ‘Provides error trapping

      '_______________________________________________________________________ '

      ' txtSSN_TextChanged

      ‘     Activated by entering SSN.

      '     Transfer form value to local class variable. '_______________________________________________________________________ '

      ' anOrder.SSN

      '     Holds the SSN for processing in all forms ' txtSSN ' Form object that holds user entered SSN ' '_______________________________________________________________________ '

      anOrder.SSN = txtSSN.Text

    Catch ex As Exception ' Error trapping. '_______________________________________________________________________ ‘

         ' Output system error message to user on form under form title and

         ' send details to database

      '_______________________________________________________________________ ' subErrorReporting("txtSSN_TextChanged",ex.Message)

     End Try

    End Sub

     

    10.2  代码设计规范

    代码设计规范不光是程序书写的格式问题,而且牵涉到程序设计、模块之间的关系、设计模式等方方面面,这里有不少与具体程序设计语言息息相关的内容(如CC++JavaC#),但是也有通用的原则,这里主要讨论通用的原则。如果你只想为了“爽”而写程序,那么可以忽略下面的原则;如果你写的程序会被很多人使用,并且你自己会加班Debug 你的程序,那最好还是遵守下面的规定。

    10.2.1  函数

    现代程序设计语言中的绝大部分功能,都在程序的函数(Function, Method)中实现,关于函数最重要的原则是:只做一件事,但是要做好。

    10.2.2  goto

    问:我们能不能用goto

    答:函数最好有单一的出口,为了达到这一目的,可以使用goto。只要有助于程序逻辑的清晰体现,什么方法都可以使用,包括goto

    如代码清单10-2

    代码清单10-2

    HRESULT  HrDoSomething(int parameter)

    {

    //parameter check and initialization

    //processing part 1

    If (SomeCode() != ok)
        {

        //set HR value
            Goto Error;

    }

    //processing part 1

    If (SomeCode() != ok)
        {

        //set HR value
            Goto Error;

    }

    Error:

        //clean up
        return hr;

    }

    10.2.3  错误处理

    80%的程序代码,都是对各种已经发生和可能发生的错误的处理。

    ——阿超

    如果错误会发生,那就让程序死的地方离错误产生的地方越近越好。

    ——阿超

    1.参数处理

    DeBug版本中,所有的参数都要验证其正确性。在正式版本中,从外部(用户或别的模块)传递过来的参数要验证其正确性。

    2.断言

    如何验证正确性?那就要用Assert(断言)。断言和错误处理是什么关系?

    当你觉得某事肯定如何,你可以用断言。

    Assert (p != NULL);

    然后可以直接使用变量p;

    如果你认为某事可能会发生,这时就要用错误处理。

    如:

    ……

    p = AllocateNewSpace(); // could fail

    if (p == NULL)

    { // error handling.

    }

    else

    { // use p to do something

    }

    10.2.4  如何处理C++中的类

    注意,除了关于异常(Exception)的部分,大部分其他原则对C#也适用。

    1.类

    1)使用类来封装面向对象的概念和多态(Polymorphism)。

    2)避免传递类型实体的值,应该用指针传递。换句话说,对于简单的数据类型,没有必要用类来实现。

    3)对于有显式的构造和析构函数,不要建立全局的实体,因为你不知道它们在何时创建和消除。

    4)只有在必要的时候,才使用“类”。


    2Class vs. Struct

    如果只是数据的封装,用Struct即可。

    3.公共/保护/私有成员PublicPrivateProtected

    按照这样的次序来说明类中的成员:publicprotectedprivate

    4.数据成员

    1)数据类型的成员用m_name说明

    2)不要使用公共的数据成员,要用inline访问函数,这样可同时兼顾封装和效率。

    5.虚函数Virtual Functions

    1)使用虚函数来实现多态(Polymorphism)。

    2)只有在非常必要的时候,才使用虚函数。

    3)如果一个类型要实现多态,在基类(Base Class)中的析构函数应该是虚函数。

    6.构造函数Constructors

    1)不要在构造函数中做复杂的操作,简单初始化所有数据成员即可。

    2)构造函数不应该返回错误(事实上也无法返回)。把可能出错的操作放到HrInit()FInit()中。

    下面是一个例子(见代码清单10-3):

    代码清单10-3

    class Foo

    {

    public:

    Foo(int cLines) { m_hwnd = NULL; m_cLines = cLines}

    virtual ~Foo();

    HRESULT HrInit();

    void DoSomething();

    private:

    HWND m_hwnd;

    int m_cLines;

    };

    7.析构函数

    1)把所有的清理工作都放在析构函数中。如果有些资源在析构函数之前就释放了,记住要重置这些成员为0NULL

    2)析构函数也不应该出错。

    8NewDelete

    1)如果可能,实现自己的New/Delete,这样可以方便地加上自己的跟踪和管理机制。自己的New/Delete可以包装系统提供的New/Delete

    2)检查New的返回值。New不一定都成功。

    3)释放指针时不用检查NULL

    9.运算符(Operators

    1)在理想状态下,我们定义的类不需要自定义操作符。只有当操作符的确需要时。

    2)运算符不要做标准语义之外的任何动作。例如,“==”的判断不能改变被比较实体的状态。

    3)运算符的实现必须非常有效率,如果有复杂的操作,应定义一个单独的函数。

    4)当你拿不定主意的时候,用成员函数,不要用运算符。

    10.异常(Exceptions

    1)异常是在“异乎寻常”的情况下出现的,它的设置和处理都要花费“异乎寻常”的开销,所以不要用异常作为逻辑控制来处理程序的主要流程。

    2)了解异常及处理异常的花销,在C++语言中,这是不可忽视的开销。

    3)当使用异常时,要注意在什么地方清理数据。

    4)异常不能跨过DLL或进程的边界来传递信息,所以异常不是万能的。

    11.类型继承(Class Inheritance

    1)当有必要的时候,才使用类型继承。

    2)用Const标注只读的参数(参数指向的数据是只读的,而不是参数本身)。

    3)用Const标注不改变数据的函数。

    阿超:代码复审看什么?是不是把你的代码拿给别人看就行了?

    杂曰:1)别人根本就不懂,给他们讲也是白讲。

    2)我是菜鸟,别的大牛能看得上我的代码么?

    3)也就是形式而已,说归说,怎么做,还是我说了算。

    代码复审的正确定义:看代码是否在“代码规范”的框架内正确地解决了问题(见表10-1)。

    10-1  复审的形式

      

      

      

    自我复审

    自己 vs. 自己

    用同伴复审的标准来要求自己。不一定最有效,因为开发者对自己总是过于自信。如果能持之以恒,则对个人有很大好处

    同伴复审

    复审者 vs. 开发者

    简便易行

    团队复审

    团队 vs. 开发者

    有比较严格的规定和流程,用于关键的代码,以及复审后不再更新的代码。

    覆盖率高——有很多双眼睛盯着程序。但是有可能效率不高(全体人员都要到会)

    软件工程中最基本的复审手段,就是同伴复审。

    谁来做代码复审?即最有经验,熟悉这一部分代码的人。对于至关重要的代码,我们要请不止一个人来做代码复审。

    复审的目的在于:

    1)找出代码的错误。如:

    a. 编码错误,比如一些能碰巧骗过编译器的错误。

    b. 不符合项目组的代码规范的地方。

    2)发现逻辑错误,程序可以编译通过,但是代码的逻辑是错的。

    3)发现算法错误,比如使用的算法不够优化。

    4)发现潜在的错误和回归性错误——当前的修改导致以前修复的缺陷又重新出现。

    5)发现可能改进的地方。

    6)教育(互相教育)开发人员,传授经验,让更多的成员熟悉项目各部分的代码,同时熟悉和应用领域相关的实际知识。

    10.3.1  为啥要做代码复审

    问:为什么非得做代码复审不可?难道开发人员没有能力写出合格的代码?既然你招我进了移山公司,就是相信我有这个能力,对不对?

    答:首先,在代码复审中发现的问题,绝大多数都可以由开发者独立发现。从这一意义上说,复审者是在替开发者干开发者本应干的事情。

    问:这么说如果开发者做到完美,复审者的时间和精力是一种浪费了?

    答:不对,即使是完美,代码复审还有“教育”和“传播知识”的作用。更重要的是,不管多么厉害的开发者都会或多或少地犯一些错误,有欠考虑的地方,如果有问题的代码已签入到产品代码中,再要把所有的问题找出来就更困难了。大家学习软件工程都知道越是项目后期发现的问题,修复的代价越大。代码复审正是要在早期发现,并修复这些问题。

    另外,在代码复审中的提问与回应能帮助团队成员互相了解,就像练武之人互相观摩点评一样。在一个新成员加入一个团队的时候,代码复审能非常有效地帮助新成员了解团队的开发策略、编程风格及工作流程。

    问:新成员是否应该在完全掌握了这些方面之后再写代码?

    答:理论上是如此。但是如果我们要“完全掌握”,可能需要比较长的时间,另外,如果不开发实际的软件,这样的“完全掌握”有意义么?还是在实际中学习吧。这也是“做中学”(Learning by Doing)思想的体现。

    10.3.2  代码复审的步骤

    在复审前——

    1)代码必须成功地编译,在所有要求的平台上,同时要编译DeBug| Retail版本。编译要用团队规定的最严格的编译警告等级(例如C/C++中的W4)。

    2)程序员必须测试过代码。什么叫测试过?最好的方法是在DeBugger中单步执行。

    问:有些错误处理的分支我不能执行到怎么办?

    答:如果你作为作者都不能让程序执行到那里,那谁能保证这些错误处理的正确性呢?

    同时也可以加上OutputDeBugString等输出来监视程序的控制流。

    3)程序员必须提供新的代码,以及文件差异分析工具。WindiffVSTS自带的工具都可以。VSTS中可以通过Shelveset来支持远程代码复审。

    4)复审者可以选择面对面的复审、独立复审或其他方式。

    5)在面对面的复审中,一般是开发者控制流程,讲述修改的前因后果。但是复审者有权在任何时候打断叙述,提出自己的意见。

    6)复审者必须把反馈意见逐一提出。注意,复审者有权提出很多看似吹毛求疵的问题,复审者不必每一件事都要亲自调查,开发者有义务给出详尽的回答。例如:

    复审者:你在这里申请了这个资源,你是如何保证它在所有路径下都能正确释放的?

    开发者:这个……我要再检查检查。

    或者——

    开发者:这个是这样保证的,我用了SmartPointer,然后这里有try/catch/ finally……

    要记住复审者是通过问这些问题来确保软件质量的,而不是有意找碴。

    7)开发者必须负责让所有的问题都得到满意的解释或解答,或者在TFS中创建新的工作项以确保这些问题将来会得到处理。例如:

    复审者:这一段代码可能会被多个线程调用,代码是thread-safe么,我怎么没有看到对共享资源的保护?

    开发者:我一时得不出结论,让我在TFS中开一个“任务”来跟踪此事。

    8)对于复审的结果,双方必须达成一致的意见。

    a. 打回去——复审发现致命问题,这些问题解决之前不能签入代码;

    b. 有条件地同意——发现了一些小问题,在这些问题得到解决或记录之后,代码可以签入,不需要再次复审;

    c. 放行——代码可以不加新的改动,签入源码控制服务器。

    避免不必要的繁文缛节,我们做代码复审的目的是为了减少错误的发生,而不是找一个人来对着你的代码点头。一些简单的修改不是非得要一个复审者来走一遍形式。在项目开发的早期斤斤计较于一些细枝末节(例如:帮助文件里的拼写错误,数据文件格式不够最优化等)也是于大局无补的,但是,这些问题并不是不用处理了,我们必须建立一些优先级较低的工作项来跟踪这些事情。

    10.3.3  在代码复审中还要做什么

    好的复审者不光是要注意到程序员修改了什么,还要把眼光放远,问一些这样的问题:

    “你这样修改了之后,有没有别的功能会受影响?”

    “项目中还有别的地方需要类似的修改么?”

    “有没有留下足够的说明,让将来维护代码时不会出现问题?”

    “对于这样的修改,有没有别的成员需要告知?”

    “导致问题的根本原因是什么?我们以后如何能自动避免这样的情况再次出现?”

    有些修改看似聪明有效率,但是这样的修改有可能会让以后的开发和维护更困难。


    10.3.4  在代码复审后要做什么

    人不能两次踏入同一条河流,程序员不能两次犯同样的错误。在代码复审后,开发者应该把复审过程中的记录整理出来:

    1)更正明显的错误。

    2)对于无法很快更正的错误,要在TFS中创建Bug把它们记录下来。

    3)把所有的错误记在自己的一个“我常犯的错误”表中,作为以后自我复审的第一步。

    有些人喜欢在程序中加一些特定的标记,来跟踪各种“要做的事情”,例如:

    //$todo:  make this function thread-safe

    //$review: is this function thread-safe?  Need to look at it again

    //$bug: when input array is very large, this function might lead //to crash

    这些标记最好是加上人名,以示负责。如:

    //$bug (AChao): when input array is very large, this function will //   become very slow due to O(N*N) algorithm

    在代码复审过程中,$review标记的问题要一一讨论,在代码复审过后,所有的$review标记要清除。在一个里程碑或正式版本发布之前,所有的$todo:$bug:标记都要清除。

    做标记是不错的办法,但是如果开发者光记得做标记,最后却没有真正去研究和改正这些潜在的问题,这些todoreviewBug就会被遗弃在代码中。过了一段时间后,后来的程序员也不敢碰它们——因为没有人能真正了解上一个版本的$todo是真的要马上做,还是已经做过了(done)了,只是没有更新$todo的注释,或者问题早已通过别的方式解决了。其根本原因在于团队没有用TFS(或者其他的管理软件)进行记录,没有人会跟踪这些事情。

    10.3.5  代码复审的核查表

    下面是移山公司总结的代码复审核查表,我们可以在复审中使用,也可以加上自己认为重要的注意事项。

     

    1.概要部分

    1)代码能符合需求和规格说明么?

    2)代码设计是否有周全的考虑?

    3)代码可读性如何?

    4)代码容易维护么?

    5)代码的每一行都执行并检查过了吗?

    2.设计规范部分

    1)设计是否遵从已知的设计模式或项目中常用的模式?

    2)有没有硬编码或字符串/数字等存在?

    3)代码有没有依赖于某一平台,是否会影响将来的移植(如Win32Win64)?

    4)开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现?在本项目中是否存在类似的功能可以调用而不用全部重新实现?

    5)有没有无用的代码可以清除?(很多人想保留尽可能多的代码,因为以后可能会用上,这样导致程序文件中有很多注释掉的代码,这些代码都可以删除,因为源代码控制已经保存了原来的老代码。)

    3.代码规范部分

    1)修改的部分符合代码标准和风格么(详细条文略)?

    4.具体代码部分

    1)有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常?

    2)参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度,是以0开始计数还是以1开始计数?

    3)边界条件是如何处理的?Switch语句的Default是如何处理的?循环有没有可能出现死循环?

    4)有没有使用断言(Assert)来保证我们认为不变的条件真的满足?

    5)对资源的利用,是在哪里申请,在哪里释放的?有没有可能导致资源泄露(内存、文件、各种GUI资源、数据库访问的连接,等等)?有没有可能优化?

    6)数据结构中是否有无用的元素?

    5.效能

    1)代码的效能(Performance)如何?最坏的情况是怎样的?

    2)代码中,特别是循环中是否有明显可优化的部分(C++中反复创建类,C# string 的操作是否能用StringBuilder 来优化)?

    3)对于系统和网络调用是否会超时?如何处理?

    6.可读性

    代码可读性如何?有没有足够的注释?

    7.可测试性

    代码是否需要更新或创建新的单元测试?

    还可以有针对特定领域开发(如数据库、网页、多线程等)的核查表。

    九条:哇,这么多酷的C++ 功能都不能用,那我们还学什么C++,为了迎接考试,我都把Operator OverloadPolymorphism背得滚瓜烂熟了,为什么不让我用?

    阿超:我们写程序是为了解决问题,不是“为赋新词强说愁”,这些高级的语言特性,不是不让用,而是要用得慎重,不要动不动就写三五个类,一个套一个,要把注意力集中在能否用简洁的方法解决问题上来。

    荔荔:这么多规范,我不知道怎么写第一行程序了。

    二柱:自我复审也很重要——把代码摆在面前,当作是别的菜鸟写的。把你通常问别人的,以及别人会问你的问题都自己问一遍。这样就能发现不少问题。

    九条:如果开发者很厉害,那么复审者就没有什么作用,也许这些复审都是走过场?

    小飞:笑话,同理可以推论,如果开发者很厉害,那么测试人员也没什么作用,也是走过场,干脆把他们送回家得了。

    荔荔给阿毛讲自己刚写好的代码,阿毛对于这个代码要解决的问题还没有什么了解,荔荔说什么,阿毛就“啊,啊”地附和。最后,阿毛也提不出什么意见,荔荔倒是在自己描述的过程中发现了一些问题。

    阿毛:这样的代码复审有用么?

    阿超:还是有一点用处,至少确保代码的作者把代码的逻辑和思想系统地表达了一遍,这样做本身就能发现不少问题。

     

    问:数据库的代码有没有规范?

    答:有,见本书附录B(第337页)。

     

    问: 这些规范啊, 建议啊, 都是细枝末节的东西, 我们要做世界级的软件,搞这些东西是不是太小家子气了?

    答: 首先世界级的软件也会因为小小的纰漏而导致世界级的问题 - 例如一段时间以来我们常常听到的安全漏洞和紧急补丁。 其次,软件的开发是一个社会性的活动, 有它的规律。 其中一个规律就是 “破窗效应” (参见 broken windows theory),如果团队成员看到同伴们连一些细小的规范都不遵守, 那自己还要严格执行单元测试么?  另一个成员看到这个模块连单元测试都没有, 那他自己也随意修改算了。。。  这样下去, 整个软件的质量可想而知。

     

    展开全文
  • 词法分析器代码复审

    2019-04-25 18:33:00
      ·对同伴的作品进行代码复审,将对伙伴审查的结果以表格的形式博客中。 2.代码简介   ·此次博客审查的代码由李云辉同学编写,项目名称为:词法分析器,代码目的是将计算机科学中字符序列转换为单词序列,供...

    1.任务要求

      ·对同伴的作品进行代码复审,将对伙伴审查的结果以表格的形式博客中。

    2.代码简介

      ·此次博客审查的代码由李云辉同学编写,项目名称为:词法分析器,代码目的是将计算机科学中字符序列转换为单词序列,供语法分析器调用。

    3.代码地址

    coding.net可运行完整代码地址

    4.项目代码

    
    //
    //  main.cpp
    //  cffxq
    //
    //  Created by LiYuNhUi on 2018/4/8.
    //  Copyright © 2018年 LiYuNhUi. All rights reserved.
    //
    #include "stdlib.h"
    #include "stdio.h"
    #include "string.h"
    #include <iostream>
    
    using namespace std;
    
    static char reserveWord[32][20] = {
        "auto", "break", "case", "char","const","continue","default","do",
        "double", "else", "enum", "extern", "float", "for", "goto", "if",
        "int", "long", "register", "return", "short", "signed", "sizeof", "static",
        "struct", "switch", "typedef", "union", "unsigned", "void","volatile", "while"
    };
    
    static char operatorOrDelimiter[37][10] = {
        "+", "-", "*", "/", "<", "<=", ">", ">=", "=", "==",
        "!=", ";", "(", ")", "^", ",", "\"", "\'", "#", "&",
        "&&", "|", "||", "%", "~", "<<", ">>", "[", "]", "{",
        "}", "\\", ".", "\?", ":", "!","\n"
    };
    
    int searchReserveword(char reserveWord[][20], char s[]){
        int i=0;
        for (i=0; i<32; i++){
            if (strcmp(reserveWord[i], s)==0) {
                return i+1;
            }//if
        }//for
        return -1;
    }// 识别保留字
    
    bool Letters (char letter){
        if((letter>='a'&&letter<='z')||(letter>='A'&&letter<='Z')||letter=='-')return true;
        else return false;
    }//识别字母
    
    bool Digit (char digit){
        if(digit>='0'&&digit<='9') return true;
        else return false;
    }//识别数字
    
    int cleanproject(char r[],int pnum){
        int i=0, count=0;
        char clean[1000];
        while (i<pnum) {
            if(r[i]=='/'&&r[i+1]=='/'){
                while (r[i]!='\n') {
                       i++;
                }//while
            }//跳过单行注释
            if (r[i]=='/'&&r[i+1]=='*') {
                while (!(r[i]=='*'&&r[i+1]=='/')) {
                    i++;
                }//while
                i+=2;
            }//跳过多行注释
            if (r[i]!='\r'&&r[i]!='\t') {
                clean[count++]=r[i];
            }
            i++;
        }//while
        clean[count]='\0';
        strcpy(r, clean);
        return count;
    }
    
    void analysis(char project[], char take[], int &flag, int &pnum ){
        int i=0;
        int count=0;
        for (i=0; i<20; i++) {
            take[i]='\0';
        }
        while(project[pnum]==' ') {
            pnum++;
        }
        if (Letters(project[pnum])) {
            take[count++]=project[pnum];
            pnum++;
            while(Letters(project[pnum])||Digit(project[pnum])){
                take[count++]=project[pnum];
                pnum++;
            }
            take[count]='\0';
            flag=searchReserveword(reserveWord, take);
            if (flag==-1) {
                flag=100;
            }
            return;
        }
        else if (Digit(project[pnum])) {
            take[count++]=project[pnum];
            pnum++;
            while (Digit(project[pnum])) {
                take[count++]=project[pnum];
                pnum++;
            }
            take[count]='\0';
            flag=99;
            return;
        }
        else if(project[pnum]=='+'||project[pnum]=='-'||project[pnum]=='*'||project[pnum]=='/'||project[pnum]=='('||project[pnum]==')'||project[pnum]=='['||project[pnum]==']'||project[pnum]=='{'||project[pnum]=='}'||project[pnum]==';'||project[pnum]==','||project[pnum]=='\n'){
            take[count++]=project[pnum];
            pnum++;
            take[count]='\0';
            i=0;
            while (i<36) {
                if(strcmp(take, operatorOrDelimiter[i])==0){
                    flag=i+33;
                    return ;
                }
                i++;
            }
        }
        else if(project[pnum]=='='){
            take[count++]=project[pnum];
            if (project[++pnum]=='=') {
                take[count++]=project[pnum];
                take[count]='\0';
                flag=41;
                return;
            }
            else {
                flag=42;
            }
        }
        else if(project[pnum]=='<'){
            take[count++]=project[pnum];
            if (project[++pnum]=='=') {
                take[count++]=project[pnum];
                take[count]='\0';
                flag=38;
                return;
            }
            else {
                flag=37;
            }
        }
        else if(project[pnum]=='>'){
            take[count++]=project[pnum];
            if (project[++pnum]=='=') {
                take[count++]=project[pnum];
                take[count]='\0';
                flag=40;
                return;
            }
            else {
                flag=39;
            }
        }
    }
    
    int main(int argc, const char * argv[]) {
        char project[1000];
        char take[20];
        int pnum=0;
        int length=0;
        int flag=0;
        project[length]=getchar();
        while (project[length]!='#') {
            project[++length]=getchar();
        }
        pnum=0;
        length=cleanproject(project, length);
        int i=0;
        while (i<length){
            cout<<project[i];
            i++;
        }
        cout<<'\n';
        while(pnum<length){
            analysis(project, take, flag, pnum);
            if (flag<=32&&flag>=1) {
                printf("<%d, ->",flag);
            }
            else if(flag==99){
                printf("<%d, %s>",flag, take);
            }
            else if(flag==100){
                printf("<%d, %s>",flag, take);
            }
            else{
                if (flag==69) {
                    printf("\n");
                }
                else printf("<%d, %s>",flag, take);
            }
        }
        return 0;
    }
    

    5.代码审查表


    功能模块名称 词法分析器         
    审查人 李朝帅 审查日期 2019/4/23
    代码名称 词法分析 代码作者 李云辉 
    文件结构
    重要性 审查项 结论
      头文件和定义文件的名称是否合理? 合理
      头文件和定义文件的目录结构是否合理? 合理
      版权和版本声明是否完整? 不完整
    重要 头文件是否使用了 ifndef/define/endif 预处理块?    未使用 
      头文件中是否只存放“声明”而不存放“定义” 否 
         
    程序的版式
    重要性 审查项 结论
      空行是否得体?
      代码行内的空格是否得体? 是 
      长行拆分是否得体? 是 
      “{” 和 “}” 是否各占一行并且对齐于同一列? 否 
    重要 一行代码是否只做一件事?如只定义一个变量,只写一条语句。 是 
    重要 If、for、while、do等语句自占一行,不论执行语句多少都要加 “{}”。 否 
    重要 在定义变量(或参数)时,是否将修饰符 * 和 & 紧靠变量名?
      注释是否清晰并且必要?
    重要 注释是否有错误或者可能导致误解? 否 
    重要 类结构的public, protected, private顺序是否在所有的程序中保持一致? 无类结构 
         
    命名规则
    重要性 审查项 结论
    重要 命名规则是否与所采用的操作系统或开发工具的风格保持一致? 一致
      标识符是否直观且可以拼读? 是 
      标识符的长度应当符合“min-length && max-information”原则?

    未应用类似标识符

    重要 程序中是否出现相同的局部变量和全部变量? 是 
      类名、函数名、变量和参数、常量的书写格式是否遵循一定的规则? 是 
      静态变量、全局变量、类的成员变量是否加前缀? 否 
         
    表达式与基本语句
    重要性 审查项 结论
    重要 如果代码行中的运算符比较多,是否已经用括号清楚地确定表达式的操作顺序? 是 
      是否编写太复杂或者多用途的复合表达式? 是 
    重要 是否将复合表达式与“真正的数学表达式”混淆? 否 
    重要 是否用隐含错误的方式写if语句? 例如  
      (1)将布尔变量直接与TRUE、FALSE或者1、0进行比较。 否 
      (2)将浮点变量用“==”或“!=”与任何数字比较。 否 
      (3)将指针变量用“==”或“!=”与NULL比较。 否 
      如果循环体内存在逻辑判断,并且循环次数很大,是否已经将逻辑判断移到循环体的外面? 是 
         
         
    常量
    重要性 审查项 结论
      是否使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串? 是 
      在C++ 程序中,是否用const常量取代宏常量? 否 
    重要 如果某一常量与其它常量密切相关,是否在定义中包含了这种关系? 是 
      是否误解了类中的const数据成员?因为const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。 否 
         
    函数设计
    重要性 审查项 结论
      参数的书写是否完整?不要贪图省事只写参数的类型而省略参数名字。 是 
      参数命名、顺序是否合理? 是 
      参数的个数是否太多? 是 
      是否使用类型和数目不确定的参数? 否 
      是否省略了函数返回值的类型? 否 
      函数名字与返回值类型在语义上是否冲突? 否 
    重要 是否将正常值和错误标志混在一起返回?正常值应当用输出参数获得,而错误标志用return语句返回。 否 
    重要 在函数体的“入口处”,是否用assert对参数的有效性进行检查? 否 
    重要 return语句是否返回指向“栈内存”的“指针”或者“引用”? 否 
      是否使用const提高函数的健壮性?const可以强制保护函数的参数、返回值,甚至函数的定义体。“Use const whenever you need” 否 
         
    内存管理
    重要性 审查项 结论
    重要 用malloc或new申请内存之后,是否立即检查指针值是否为NULL?(防止使用指针值为NULL的内存)
    重要 是否忘记为数组和动态内存赋初值?(防止将未被初始化的内存作为右值使用) 是 
    重要 数组或指针的下标是否越界? 否 
    重要 动态内存的申请与释放是否配对?(防止内存泄漏) 否 
    重要 是否有效地处理了“内存耗尽”问题?
    重要 是否修改“指向常量的指针”的内容? 否 
    重要 是否出现野指针?例如(1)指针变量没有被初始化;(2)用free或delete释放了内存之后,忘记将指针设置为NULL。 否 
         
    C++ 函数的高级特性
    重要性 审查项 结论
      重载函数是否有二义性?
    重要 是否混淆了成员函数的重载、覆盖与隐藏? 否 
      运算符的重载是否符合制定的编程规范? 否 
      是否滥用内联函数?例如函数体内的代码比较长,函数体内出现循环。 否 
    重要 是否用内联函数取代了宏代码? 无 
         
    类的构造函数、析构函数和赋值函数
    重要性 审查项 结论
    重要 是否违背编程规范而让C++ 编译器自动为类产生四个缺省的函数:  
      (1)缺省的无参数构造函数; 无 
      (2)缺省的拷贝构造函数; 无 
      (3)缺省的析构函数; 无 
      (4)缺省的赋值函数。 否 
    重要 构造函数中是否遗漏了某些初始化工作? 否 
    重要 是否正确地使用构造函数的初始化表? 是 
    重要 析构函数中是否遗漏了某些清除工作? 否 
      是否错写、错用了拷贝构造函数和赋值函数? 否 
    重要 赋值函数一般分四个步骤:  
      (1)检查自赋值; 无 
      (2)释放原有内存资源; 无 
      (3)分配新的内存资源,并复制内容; 无 
      (4)返回 *this。是否遗漏了重要步骤? 无 
    重要 是否正确地编写了派生类的构造函数、析构函数、赋值函数? 无派生 
      注意事项:  
      (1)派生类不可能继承基类的构造函数、析构函数、赋值函数。 无 
      (2)派生类的构造函数应在其初始化表里调用基类的构造函数。 无 
      (3)基类与派生类的析构函数应该为虚(即加virtual关键字)。 无 
      (4)在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值 无 
         
    类的高级特性
    重要性 审查项 结论
    重要 是否违背了继承和组合的规则? 否 
      (1)若在逻辑上B是A的“一种”,并且A的所有功能和属性对B而言都有意义,则允许B继承A的功能和属性。 否 
      (2)若在逻辑上A是B的“一部分”(a part of),则不允许B从A派生,而是要用A和其它东西组合出B。 否 
         
    其它常见问题
    重要性 审查项 结论
    重要 数据类型问题:  
      (1)变量的数据类型有错误吗? 否 
      (2)存在不同数据类型的赋值吗? 否 
      (3)存在不同数据类型的比较吗? 否 
    重要 变量值问题:  
      (1)变量的初始化或缺省值有错误吗? 否 
      (2)变量发生上溢或下溢吗? 未发生 
      (3)变量的精度够吗? 足够 
    重要 逻辑判断问题:  
      (1)由于精度原因导致比较无效吗? 否 
      (2)表达式中的优先级有误吗? 否 
      (3)逻辑判断结果颠倒吗? 否 
    重要 循环问题:  
      (1)循环终止条件不正确吗?
      (2)无法正常终止(死循环)吗? 否 
      (3)错误地修改循环变量吗?
      (4)存在误差累积吗? 不存在 
    重要 错误处理问题:  
      (1)忘记进行错误处理吗? 否 
      (2)错误处理程序块一直没有机会被运行? 否 
      (3)错误处理程序块本身就有毛病吗?如报告的错误与实际错误不一致,处理方式不正确等等。 否 
      (4)错误处理程序块是“马后炮”吗?如在被它被调用之前软件已经出错。 否 
    重要 文件I/O问题: 未应用 

    6.代码分析

    ·代码评价

      词法分析是计算机科学中将字符序列转换为单词序列的过程,也叫扫描器,是进行下一步计算机编译的基础。在词法分析器的编写过程中虽然并没有过多过难的设计和算法,但对程序设计者的整体程序设计能力、各功能函数的设计和应用、模块功能的实现层次及编程时的耐心和细心都有着很大的考验。李云辉同学在这方面做的很好,在自动单元测试的环节中通过所有预先设定好的测试样例,在程序的细节把控上较为完美。从此词法分析器整体来看,数据结构设置合理且全面,应用充分,函数层次分明,各功能模块可以实现相互配合实现的代码的更优化。
      ·代码亮点
        ·函数层次分明,易于审查及维护;
        ·代码工整,各变量设置妥当;
        ·充分考虑词法分析时的各种情况,做到no warning,no error。

      ·代码缺陷
        ·函数名定义不规范,有些函数名定义过于随意;
        ·版权和版本声明不完整;
        ·少数函数编写的啰嗦,对代码执行速率的考虑不够周全;
        ·存在一些部分,代码书写格式不规范。

       ·改进意见
        ·希望代码作者可以在代码具体书写的能力上多做一些加强;
        ·在函数命名和函数结构设计上,我们都应该多看一些他人的技术博客,多多学习;
        ·代码作者可以进一步优化代码,省去一些不必要的判断,加快代码执行速率。

    7.心得体会

      ·对其他同学的代码审查也是对自己的考验,在细心发现他人代码缺陷的同时也是对自己编程能力的更严苛要求,第一次结伴作业受益良多。

    转载于:https://www.cnblogs.com/lichaoshuai/p/10759161.html

    展开全文
  • 无论如何,代码在最终封箱打包前,一定要复审一下下才放心。 自己复审自己的代码,基本上是个笑话;交给同事复审自己的代码,太多沟通代价;交给上司复审自己的代码,比高考还焦虑。 交给工具软件才是正途,Code...
  • 代码复审结果

    2015-09-30 19:41:00
    说明:因为本次一些特殊的原因,我与一位韩国留学生同学、以及六班的一位同学一起结对编程。 ...两位结对的伙伴的代码都只能实现需求功能中的一部分: Z同学目前实现的功能有:只能生成1000...
  • 代码复审和两人合作

    2016-04-22 17:40:24
    代码复审的步骤: 代码必须成功地编译,在所有要求的平台上,同时要编译DeBug| Retail版本。编译要用团队规定的最严格的编译警告等级(例如C/C++中的W4)。程序员必须测试过代码。什么叫测试过?最好的方法是...
  • C/C++代码复审

    2019-04-23 18:15:00
    同伴的题目要求 设计一个利用哈夫曼算法的编码和译码系统,通过键盘输入权值数据,并以文件形式存储,从屏幕输出相应的哈夫曼树。利用建好的哈夫曼树生成哈夫曼编码,并通过屏幕...代码复审 功能模块名称 ...
  • 第10章 代码规范与代码复审 在第9章中,同学们完成了WC程序,经过评比,九条的程序获得了第一名。这时,阿超说,现代软件产业经过几十年的发展,已经不可能出现一个人单枪匹马完成一个软件的事情了,软件都是在相互...
  • 软工4 代码复审

    2018-04-06 12:31:00
    软工4:结对编程之代码复审 1.题目要求 (1). 首先在同学中找一个同伴,范围不限,可以在1~5班中随意组合,建议尽量不要找同组的成员,女同学尽量找男同学结对,但是不做强制要求; (2). 从以往个人完成的项目中选择...
  • 一、代码复审部分 1. 概要部分 代码能符合需求和规格说明么? 代码基本符合需求和规格说明,但是在程序中有多余的Debug信息(sudoku.cpp中的176行) 代码设计是否有周全的考虑? 有必要的错误处理,但是还不太...
  • 代码复审与团队沟通代码复审复审的形式复审的目的复审的步骤复审前复审中复审后复审的核心内容代码规范设计规范数据与效能情况测试情况团队沟通正确的反馈反馈的层次易于接受的反馈:三明治法则 代码复审 代码复审的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,825
精华内容 2,730
关键字:

代码复审工具