WordCount
github:https://github.com/Hoyifei/SQ-T-Homework2-WordCount
PSP:
Planning |
计划 |
||
· Estimate |
· 估计这个任务需要多少时间 |
10 minute | |
Development |
开发 |
||
· Analysis |
· 需求分析 (包括学习新技术) |
5hour | About 1.5 hours(边看边写) |
· Design Spec |
· 生成设计文档 |
0 | 0 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 | 0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
0 | 0(已有) |
· Design |
· 具体设计 |
2hour | about 4hour |
· Coding |
· 具体编码 |
5hour | about 9 hour |
· Code Review |
· 代码复审 |
0 | 0 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
5 hour | about 4 hour |
Reporting |
报告 |
30 minurte | 40 minute |
· Test Report |
· 测试报告 |
0 | 0 |
· Size Measurement |
· 计算工作量 |
10 minute | about 10 minute |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
10 minute | 10 mminute |
合计 |
因为是零碎做的所以实际用时偏差很大,全部是估计值。只有值为0的非常确定
编程思路:
这有什么思路...挺直接的
处理argv,然后按要求统计就是
递归搜索:把通配符表达式转换成正则表达式,使用boost库的filesystem遍历,正则匹配一下
排除表其实有更好的解决方案,为排除表建立字典树,我偷懒直接逐个匹配了
进阶统计有几个细节,还有一个(可能是)UB(undefined Behavior):
/*
blahblah
*/*::这里不在注释里
/*
blahblah
*//::这里也不在
///*blahblah
::这里也不在注释里
/*UB:块注释里可以有空行,我的处理方法是既算注释也算空行
*/
argv里也有几个UB:
出现多个sourcefile:我的处理是全部接受进行统计
未指定统计操作:全部进行
此外,考虑到跨平台问题,-x参数没写弹对话框,而是从stdin中读取输入
测试思路:
最复杂的部分就是对于代码行,空行和注释的判定。为此我专门设计了test4.txt,重点测试了各种注释
其实我还是用了googletest,不过只是让测试结果的输出更好看一点,测试用的代码全都是自己写的
其他:
由于没有Windows系统 ,不仅编译出来的不是exe而是elf,而且测试在Windows下也跑不过(由于windows的路径使用反斜杠,会导致找不到输入,需要改一下),但是本体大概能跑(也没试过)。希望以后出作业能考虑一下跨平台的问题。
Git并非设计用来管理二进制文件,因为commit后没有机会删除,如果用来管理二进制文件会导致git目录迅速增长,占用过多空间。因此BIN文件夹下我会放一个txt文件,里面包含下载可执行文件(64位elf)的地址。依赖库也只能打包64位elf格式而且也不一定全。
只有类似于C/C++/Java的语法才会以//和/**/标识注释,所以出题时说清楚注释的判定标准比较好。严格来说因为没描述清楚所以注释的表示方法是个UB,我甚至可以写成统计Python/Shell的以#开头的行注释
输出全是英文所以格式不大一样
测试数据换行格式是Unix格式(\n)
下次做成Docker镜像就好了吧......
程序设计实现过程:
总共一个类,test_basic只是把protected的成员暴露出来
主要流程:分析参数→如果存在Except文件则读取并登记词语→如果有搜索参数则进行搜索→依次处理所有统计并输出
统计字数:strlen
行数:至少一行,每一个\n多一行(这里没考虑mac的\r换行)
Vim等文本编辑器会在最后一行后面加一个\n,所以有看上去5行实际上有6行的问题。不过我认为这确实是多了一行所以没处理
单词:遇到非分隔符将其加到一个临时字符串的末尾,遇到分隔符时,如果临时字符串非空,则检查是不是出现在屏蔽列表里,如没有则单词数+1。不管是否被屏蔽清空临时字符串
递归搜索:具体流程已在解题思路中指出。
进阶统计:逐个读取。
如果为可见字符,本行可见字符数+1
如果为可见字符且并不位于注释中,本行代码字符+1
遇到/时判定上一个字符,如果不在注释里且上一个字符为/则为行注释开头(//),本行代码字符扣除//。如果位于块注释里且上一个字符为*则块注释结束且记当前字符为\0
遇到*时如果不在注释里则判定上一个字符,如果为/则为块注释开头,代码字符扣除/*
遇到\n时判定:代码字符大于一个为代码行,小于等于一个且包含注释则判定为注释行。如果可见字符数小于等于1则判定为空行。
代码解说:
进阶统计部分
1 CWordCount::TAdvancedResult CWordCount::count_advanced() { 2 TAdvancedResult ans; 3 ans.empty=0; 4 ans.code=0; 5 ans.note=0; 6 auto len=strlen(str); 7 char c; 8 int cur_line_code_chars=0,cur_line_visible_chars=0; 9 bool line_note=false,block_note=false,line_include_note=false; 10 char last_char=0; 11 for(auto i=0;i<len;++i){ 12 c=str[i]; 13 switch(c){ 14 case ' ':case '\t':case '\r':case '\0':{//空白符,忽略不计 15 last_char=c; 16 break; 17 } 18 case '\n':{//换行符,判定这一行的类型并清空 19 if(cur_line_visible_chars<=1) ++ans.empty; 20 if(cur_line_code_chars>1) ++ans.code; 21 if(cur_line_code_chars<=1&&line_include_note) ++ans.note; 22 cur_line_visible_chars=0; 23 cur_line_code_chars=0; 24 line_note=false; 25 line_include_note=false; 26 if(block_note) line_include_note=true; 27 last_char=c; 28 break; 29 } 30 case '/':{ 31 ++cur_line_visible_chars; 32 if(!line_note&&!block_note) {//并非位于注释中,可以作为注释的开头 33 if (last_char == '/') { 34 --cur_line_code_chars; 35 line_note = true; 36 line_include_note=true; 37 last_char=0;//如果作为块注释的结尾就不能再作为注释的开头了,“*//asdf”里asdf并不是注释 38 }else { 39 ++cur_line_code_chars; 40 last_char=c; 41 } 42 }else if(block_note){//位于块注释中,可以作为注释的结尾 43 if(last_char=='*'){ 44 block_note=false; 45 last_char=0; 46 }else{ 47 last_char=c; 48 } 49 } 50 break; 51 } 52 case '*':{ 53 ++cur_line_visible_chars; 54 if(!line_note&&!block_note) {//并非位于注释中,可以作为块注释的开头 55 if (last_char == '/') { 56 --cur_line_code_chars; 57 block_note = true; 58 line_include_note=true; 59 last_char=0;//如果作为块注释的开头就不能作为块注释的结尾了,/*/asdf里asdf是注释 60 }else { 61 ++cur_line_code_chars; 62 last_char=c; 63 } 64 }else{ 65 last_char=c; 66 } 67 break; 68 } 69 default: { 70 if(!line_note&&!block_note) ++cur_line_code_chars; 71 ++cur_line_visible_chars; 72 break; 73 } 74 } 75 } 76 if(last_char!='\n') {//最后一行结尾可能没有换行,因此没有结算 77 if (cur_line_visible_chars <= 1) ++ans.empty; 78 if (cur_line_code_chars > 1) ++ans.code; 79 if (cur_line_code_chars <= 1 && line_include_note) ++ans.note; 80 } 81 //cerr<<ans.note<<" "<<ans.empty<<" "<<ans.code<<endl; 82 return ans; 83 }
测试思路:
代码中分支最多,最复杂, 处理情况最多,最容易出错的部分是处理程序参数和进阶统计部分。
对于其他的统计量没有特别的设计测试数据,而是在进阶统计部分的测试数据里顺便加上了对于其他统计的测试
递归搜索部分由于采用了相当成熟的库,因此只进行了简单的测试,也没有自动化地验证答案,而是人工进行了验证
由于程序接收参数的格式更大程度上取决于程序本身的规定,因此采用的是白盒测试
处理程序参数部分的代码采用了一个DFA模型,因此设计测试时覆盖了DFA的所有可能的状态转移,特别是覆盖了DFA的所有跳转到拒绝状态的路径。
由于对于代码行/空白行/注释行的判定有明确的标准,因此测试的方法是黑盒测试,通过判定程序能否正确处理输入并给出正确输出来进行测试
References:
Regular Expression:https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference
C++ regex:http://www.cplusplus.com/reference/regex/
Boost Filesystem:http://www.boost.org/doc/libs/1_66_0/libs/filesystem/doc/reference.html