WordCount优化
基本任务
1.Github地址
https://github.com/chaosrings/wcPro
2.PSP表格
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
20 |
20 |
· Estimate |
· 估计这个任务需要多少时间 |
20 |
20 |
Development |
开发 |
380 |
470 |
· Analysis |
· 需求分析 (包括学习新技术) |
20 |
30 |
· Design Spec |
· 生成设计文档 |
0 |
0 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
30 |
· Design |
· 具体设计 |
30 |
60 |
· Coding |
· 具体编码 |
60 |
90 |
· Code Review |
· 代码复审 |
60 |
60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
180 |
200 |
Reporting |
报告 |
100 |
90 |
· Test Report |
· 测试报告 |
60 |
60 |
· Size Measurement |
· 计算工作量 |
10 |
10 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
20 |
|
合计 |
500 |
580 |
3.接口实现
我负责输入控制模块的实现。
设计了一个Scanner类,提供以下接口:
无参构造函数和析构函数:
Scanner() { } ~Scanner() { }
主要接口:getString()方法
输入参数是txt文件路径的字符串,返回该文件的内容的字符串。
需求要求只处理txt文件,而不处理其他文件,所以做一个判断即可。
如果是txt文件则返回该文件内容的字符串,否则输出提示语,然后返回空字符串。
string getString(const string &path) { if (path.substr(path.length() - 4, 4) == ".txt") { ifstream in(path); stringstream buffer; buffer << in.rdbuf(); string content(buffer.str()); return content; } else { cout << This is not a ".txg" file !"; } return ""; }
4.测试用例设计
只需要测试getString()方法
白盒测试:
程序结构简单,只有一个结点,只需设计两个测试用例即可全部覆盖。
测试用例 | 预期输出 | 实际输出 |
test.txt | 成功读取出文件内容的字符串 | 成功读取 |
test.c | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
黑盒测试:
需求中要求只处理txt文件,我们根据需求等方面来设计黑盒测试。
如果只分为txt文件和非txt文件,等价类太少实在设计不出20个测试用例。
我们稍微细分一下
工作路径 | 其他路径 | |
存在的txt文件 | 等价类1 | 等价类2 |
存在的非txt文件 | 等价类3 | 等价类4 |
不存在的txt文件 | 等价类5 | 等价类6 |
不存在的非txt文件 | 等价类7 | 等价类8 |
比如我们测试txt文件与数个非txt文件,它们只有文件格式不同,文件内容全部相同。
测试用例 | 预期输出 | 实际输出 |
blacktest.txt | 成功读取出文件内容的字符串 | 成功读取 |
blacktest.c | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
blacktest.doc | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
我们测试其他路径下的文件
测试用例 | 预期输出 | 实际输出 |
D:\blacktest.txt | 成功读取出文件内容的字符串 | 成功读取 |
D:\blacktest.c | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
D:\blacktest.doc | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
我们测试不存在的文件
测试用例 | 预期输出 | 实际输出 |
notexist.txt | 成功读取出文件内容的字符串 | 成功读取(虽然是空串) |
notexist.c | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
notexist.doc | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
我们测试在其他路径下不存在的文件
测试用例 | 预期输出 | 实际输出 |
D:\notexist.txt | 成功读取出文件内容的字符串 | 成功读取(虽然是空串) |
D:\notexist.c | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
D:\notexist.doc | 输出“This is not a ".txt" file !”且返回空字符串 | 输出“This is not a ".txt" file !”且返回空字符串 |
(就算写了很多感觉不必要的测试用例也不知道怎么凑起20个测试用例了)
5.单元测试脚本
白盒测试:
TEST_CLASS(ScannerWhiteBoxTest) { private: Scanner * scannerTest; public: TEST_METHOD_INITIALIZE(setUp) { scannerTest = new Scanner(); } TEST_METHOD_CLEANUP(tearDown) { delete scannerTest; } TEST_METHOD(TestGetString) { Assert::AreEqual<string>("For the witcher,Heartless cold.", scannerTest->getString("test.txt"), L".txt fail"); Assert::AreEqual<string>("", scannerTest->getString("test.c"), L".c fail"); } };
黑盒测试:
TEST_CLASS(ScannerBlackBoxTest) { private: Scanner * scannerTest; public: TEST_METHOD_INITIALIZE(setUp) { scannerTest = new Scanner(); } TEST_METHOD_CLEANUP(tearDown) { delete scannerTest; } TEST_METHOD(TestGetString) { Assert::AreEqual<string>("For the witcher,Heartless cold.", scannerTest->getString("blacktest.txt"), L".txt fail"); Assert::AreEqual<string>("", scannerTest->getString("blacktest.c"), L".c fail"); Assert::AreEqual<string>("", scannerTest->getString("blacktest.doc"), L".doc fail"); Assert::AreEqual<string>("For the witcher,Heartless cold.", scannerTest->getString("D:\\blacktest.txt"), L".txt fail"); Assert::AreEqual<string>("", scannerTest->getString("D:\\blacktest.c"), L".c fail"); Assert::AreEqual<string>("", scannerTest->getString("D:\\blacktest.doc"), L".doc fail"); Assert::AreEqual<string>("", scannerTest->getString("notexist.txt"), L".txt fail"); Assert::AreEqual<string>("", scannerTest->getString("notexist.c"), L".c fail"); Assert::AreEqual<string>("", scannerTest->getString("notexist.doc"), L".doc fail"); Assert::AreEqual<string>("", scannerTest->getString("D:\\notexist.txt"), L".txt fail"); Assert::AreEqual<string>("", scannerTest->getString("D:\\notexist.c"), L".c fail"); Assert::AreEqual<string>("", scannerTest->getString("D:\\notexist.doc"), L".doc fail"); } };
运行结果:
扩展任务
1.文档规范
邹欣老师讲义“现代软件工程讲义 3 代码规范与代码复审
如:代码风格规范中谈到,
缩进用4个空格比较好。其实这个有点不好接受,虽然我在python中一直习惯用4个空格缩进,这是因为python本身语法的因素。而VS里面会自动的给你用Tab缩进,所以我觉得Tab缩进也没问题吧...
行宽必须限制。这是必然的。一行如果太长,观察代码时还需要左右拖动。
在复杂的条件表达式中,用括号清楚地表示逻辑优先级。这样不仅能清楚地表示逻辑优先级,还无需记住逻辑运算符的优先顺序。
每个“{”和“}”都独占一行。虽然是空行,但独占一行能使花括号对齐,容易看。
2.评审代码
我对核心处理这一模块进行了评审,按照上述规范。(学号:16072)
token.h :
1 class Lex 2 { 3 private: 4 unsigned int currentPos; 5 string targetStr; 6 7 public: 8 Lex(const string & str) 9 { 10 setTargetStr(str); 11 } 12 Lex() 13 { 14 setTargetStr(""); 15 } 16 ~Lex() 17 { 18 targetStr.clear(); 19 } 20 void setTargetStr(const string& str) 21 { 22 currentPos = 0; 23 targetStr = ""; 24 for (char ch : str) 25 { 26 if (ch < -1 || ch>255) //特殊字符用`代替。不影响逻辑 27 targetStr += "`"; 28 else if (isalpha(ch)) 29 targetStr += tolower(ch); 30 else 31 targetStr += ch; 32 } 33 } 34 string getTargetStr() 35 { 36 return targetStr; 37 } 38 static bool cmpByFrequencyDec(const pair<string, int>& p1, const pair<string, int> &p2) 39 { 40 return p1.second > p2.second; 41 } 42 bool finished() 43 { 44 return currentPos == targetStr.size(); 45 } 46 string getNextToken() 47 { 48 string thisToken = ""; 49 while (currentPos<targetStr.size() && !isalpha(targetStr[currentPos]))//在currentPos小于目标字符长度时跳过非字母开头 50 ++currentPos; 51 while (currentPos < targetStr.size()) 52 { 53 char curChar = targetStr[currentPos]; 54 if (isalpha(curChar)) //如果是字母便保存 55 thisToken += curChar; 56 else if (curChar == '-'&¤tPos < targetStr.size() - 1 && isalpha(targetStr[currentPos + 1]))//有连字符号时,只有连字符号后是字母才算单词 57 { 58 thisToken += '-'; 59 } 60 else 61 break; //其他情况,已经从curentPos开始获取到了一个单词,返回 62 currentPos++; 63 } 64 return thisToken; 65 } 66 vector<pair<string, int> > getWordFreqVec() 67 { 68 unordered_map<string, int> countMap; 69 while (!finished()) 70 { 71 string curToken =getNextToken(); 72 if (curToken.size() == 0) 73 continue; 74 countMap[curToken]++; 75 } 76 vector<pair<string, int> > wordFreqVec; 77 wordFreqVec.assign(countMap.begin(), countMap.end()); 78 sort(wordFreqVec.begin(), wordFreqVec.end(), cmpByFrequencyDec); 79 return wordFreqVec; 80 } 81 unordered_map<string, int> getWordFreqMap() 82 { 83 unordered_map<string, int> ans; 84 currentPos = 0; 85 vector<pair<string, int> > wordFreqVec = getWordFreqVec(); 86 for (auto wfPair : wordFreqVec) 87 { 88 ans[wfPair.first] = wfPair.second; 89 } 90 return ans; 91 } 92 };
在这个模块中,缩进、{}、逻辑表达式、命名都做得很好。
我觉得有问题的地方有以下:
1,第49、56行过长,本来就是很长的代码,再加上注释,都在一行。可以考虑把注释写在上面一行。
2,有些循环或if语句内只有一行语句时,不用{}括起来,由于没有明确的“{”和“}”来判断程序的结构,在有多层控制嵌套的时候,就不容易看清结构和对应关系。如第26,50,54行。
其他的都达到了规范要求,如命名:类Pascal格式(所有单词的第一个字母都大写);函数和变量是lowerCamel格式(第一个单词全部小写,随后单词随Pascal格式)。
3.静态检查工具
我采用了cppcheck工具来对代码进行静态检查。
下载地址:http://cppcheck.net/
4.扫描结果
由于好像不能选择.h文件,于是我把Scanner.h扩展名先改成了.cpp,检查之后再改回去。
未发现错误。大概是代码简单,易写,容易遵循规范。
我自己的代码里在缩进、空行、{}、逻辑表达式、行宽、命名等方面,因为比较简单的原因,都做到了遵循规范。
重新运行单元测试的结果:
小组贡献分:0.26