标签:debug 行修改 get 数据 关注 可读性 数组 设计 ddb
这一次和室友结对编程,第一项任务就是互看代码。想到上一次这样认真看她的C++代码,是一年前学程序设计这门课时,帮她人工debug。虽然都是从头到尾认真读代码、通逻辑,感觉却是不同的,之前的关注点是bug在哪,而现在却是带着任务的,要关注代码核查表中的项目。结果看着看着,总感觉自己在找茬dbq
回归正题↓
编译环境:Dev-C++ 5.9.2
程序语言:C++
能发现的优点,大多都是自己的缺点
通过详细的注释,即便是我们两个的个人项目使用的不是同一种语言,我看懂她的代码并没有花费很多时间。在她的代码中,几乎每一句关键语句上都加了注释,说明其功能,通过看注释,其实就可以基本明白所有需求实现的逻辑了。也不出我所料,她在看我的代码的时候非常绝望。因为过去编程都是个人编程,还没有过组队做项目的经历,所以我习惯只在程序块和变量定义前面加注释,标记数据块的功能和实现逻辑。这样虽然自己复查或者修改代码时可以看懂,但却给其他看代码的人带来了极大的麻烦。
每一个功能都封装成了一个函数,只有在主函数中,按照流程依次调用了各个功能函数,其余函数之间几乎没有相互调用,基本做到了模块与模块之间,尽可能的独立存在。这一点也是我的代码做的不太好的一点,为了开发时省事,我将功能划分的比较大,每个实现子功能的函数都相对复杂,这样一来,一旦某一个小功能需求改变,就需要在一个大模块中调整,工作量会增大、出错率也会提高。
看到她的代码开始定义的几个容器,给了我很大启发。容器的简单性、轻量性、可扩展性、可移植性的优点突然出现在脑子里,感觉自己用的数组特别low。(不过仔细看过后,这里使用容器还是有问题的,这个在后面的缺点里说吧)不管怎么样,这个项目还是有很多地方可以用容器呀。
需求中说,“每个账号一个文件夹”,我当时是按照用户文件夹提前创建好,然后生成试卷后寻址,存入该文件夹来理解的。而她的代码思路我觉得更好,在准备生成第一套试卷时,若用户文件夹不存在,则创建,这样一来扩展性比较强,当用户增多,或者需求改成用户表不是预设好的,而是通过注册产生的时,就会容易实现得多。
括号的位置、函数和变量的命名,都较为规范。
由于代码复审的思想,发现的不足和bug会多一点,下面开始疯狂diss
注释多,是优点,也是缺点。例如函数的注释,放在函数名和前括号"{"之间,有些影响美观。
1 int GetLevel(string name) //用于判断当前用户姓名并且确定用户身份等级,选择出题对象 2 { 3 if ((name.compare("张三1") == 0) || (name.compare("张三2") == 0) || (name.compare("张三3") == 0)) 4 { 5 cout << "当前选择为小学出题" << endl; 6 return 1; //小学老师表示为1 7 } 8 else if ((name.compare("李四1") == 0) || (name.compare("李四2") == 0) || (name.compare("李四3") == 0)) 9 { 10 cout << "当前选择为初中出题" << endl; 11 return 2; //初中老师表示为2 12 } 13 else if ((name.compare("王五1") == 0) || (name.compare("王五2") == 0) || (name.compare("王五3") == 0)) 14 { 15 cout << "当前选择为高中出题" << endl; 16 return 3; //高中老师表示为3 17 } 18 }
仔细阅读代码后,发现她定义的几个容器的作用是存储当前用户在本次程序所生成的所有试题,用于查重。而需求中的查重指的是题目不能与该用户文件夹下所有文件中试题重复。因此需求没有真正实现。
int型转换成string型,完全可以调用库函数,或者用String str = i + "";这样一条语句完成,无需定义这么复杂的函数按位转换哟。
1 string to_string(int n) //定义将整型转换为字符串的函数n 2 { 3 int m = n; 4 int i = 0, j = 0; 5 char s[100]; 6 string astring; 7 while (m > 0) 8 { 9 s[i] = m % 10 + ‘0‘; 10 m /= 10; 11 i++; 12 } 13 s[i] = ‘\0‘; 14 15 i = i - 1; 16 while (i >= 0) 17 { 18 astring += s[i]; 19 i--; 20 } 21 astring += ‘\0‘; 22 23 return astring; 24 }
加括号策略不能实现某两个操作数之间有两个或两个以上括号的情况。
1 void AddBracker(int i, char m[], char n[]) //加括号 2 { 3 int a[i]; 4 memset(a, 0, sizeof(a)); 5 int bracker_num = random(1, i); 6 while (bracker_num > 0) 7 { 8 int t = random(1, i); 9 if (a[t - 1] == 0) 10 { 11 a[t - 1] = 1; 12 int tem = random(t, i); 13 if (a[tem - 1] == 0) 14 a[tem - 1] = 2; 15 else 16 a[t - 1] = 0; 17 18 } 19 bracker_num--; 20 } 21 22 for (int k = 0; k < i - 1; k++) 23 { 24 if (a[k] == 1 && a[k + 1] == 2) 25 { 26 a[k] = 0; 27 a[k + 1] = 0; 28 } 29 } 30 for (int k = 0; k < i; k++) 31 { 32 if (a[k] == 1) 33 m[k] = ‘(‘; 34 if (a[k] == 2) 35 n[k] = ‘)‘; 36 } 37 }
初中的平方和开根号、高中的sin、cos、tan运算符,在两个操作数之间都不能有多个同类运算符。比如“tan(cos15)”这样的式子就无法生成。
1 if (m[0] == ‘(‘ && n[i - 1] == ‘)‘ && (a[i - 1].compare("^2") != 0 || a[i - 1].compare("^1/2") != 0)) //如果首尾都是括号且没有整体平方或开根操作 2 { 3 m[0] = ‘\0‘; 4 n[i - 1] = ‘\0‘; 5 } 6 7 for (int j = 0; j < i; j++) 8 { 9 equation += m[j]; 10 equation += to_string(CreatOperator()); 11 int jud = random(0, 1);//判断平方或者开根操作是在括号前还是括号后的 12 if (jud == 0) //如果判断结果为0 则平方或者开根操作在括号内 13 { 14 equation += a[j]; 15 equation += n[j]; 16 } 17 if (jud == 1)//如果判断结果为1 则平方或者开根操作在括号外 18 { 19 equation += n[j]; 20 equation += a[j]; 21 } 22 if (j != i - 1) 23 equation += CreatSymbol(); 24 }
判断用户是否成功登录的逻辑,室友是采用if-else暴力判断的,这样一来,当用户多起来,或者需求变为用户表由注册产生时,就无法扩展实现了。这也让我发现,我的方式也有同样的问题,我是将账号密码分别存入两个数组。由此我想到了更好的解决方式,使用类来封装,将用户信息封装到用户类中,这样当注册信息增添了其他内容时,也可以轻松扩展。
但看判断用户类型这一循环,在很多函数中都用到了,if-else多重判断,好多子功能函数中都重复了这一段代码。就当前实现来看,我觉得可以把这一过程写成函数或者宏定义,避免多次重写,修改的时候也需要多处同时改。更好的解决方式同6中提到的,如果用户信息封装成类,用户类型即可作为类中一个成员变量。
这样一个功能,因为要面向用户,做UI是大势所趋,所以第一版如果直接写成界面,而不用命令框,结对项目就可以不用重写了,所以本次结对项目应该是要以我的JAVA项目为底层代码,进行修改了。
用户只有在刚登录时可以切换出题类型,之后就只能在当前类型下多次出题,要想换类型只能重新登录。这一点极大影响了用户体验。
1、开始结对编程前让我们互看代码,非常有意义。一方面对方可以帮忙找到自己的bug或者不足,通过看对方代码,无论是对方的优点和缺点,总能联系到自己,发现自己可以改进的地方。
2、代码复审的重要原则——沟通。在看代码的过程中,我和结对编程的队友因为是室友,所以有足够的时间互相提问,有时候研究好久的几行代码,通过开发人员的一句话,就都懂了。
3、写博客的好处,通过这篇博客,可以记录我们互看代码发现的问题,和要改进的点,不至于在过两天开工结对编程项目时,把这些都忘干净了。
标签:debug 行修改 get 数据 关注 可读性 数组 设计 ddb
原文地址:https://www.cnblogs.com/gifted35/p/9715514.html