進行了個人項目的實踐之後,我與晏旭瑞同學開展了結對程式設計項目,現已基本完成。根據老師的要求,現由結對程式設計的兩名人員互相複審對方的個人項目的代碼。
本次我複審晏旭瑞第一次個人項目的代碼。
個人項目要求: http://www.cnblogs.com/jiel/p/3978727.html
晏旭瑞的代碼整體來說看起來非常簡潔,沒有備援的部分,多處利用庫函數提高了程式的效率,并實作了一定程度上的代碼重用;
函數和類的命名有含義而且簡明,讓人看到名字就知道子產品的基本功能。整個程式整體采用過程式設計方法,而過程中調用的方法都是類之中的方法,
将打開檔案(周遊檔案夾)、疊代讀入單詞、分析單詞并進行計數以及最後的結果排序都配置設定給不同的方法來完成,而這些不同的方法都是不同的類的成員方法,也就是說總體過程化的程式設計,以及任務的分割以及類與對象實體的建構,也包含了面向對象的設計思想。類的定義都在頭檔案中,在cpp檔案中才對具體的方法進行書寫,看起來結構層次分明,類似于接口的實作,一定程度上展現了面向對象程式設計的封裝性,資訊隐藏以及抽象原則。整個程式邏輯清楚,分支不多但是卻覆寫了應有的情況。
1 #include"findFile.h"
2 int findFile::dfsFolder(string folderPath,vector<string> &fileArray)
3 {
4 _finddata_t FileInfo;
5 string strfind = folderPath + "\\*";
6 //搜尋與指定的檔案名稱比對的第一個執行個體,若成功則傳回第一個執行個體的句柄,否則傳回-1L
7 long Handle = _findfirst(strfind.c_str(), &FileInfo);
8
9 if (Handle == -1L)
10 {
11 return -1;// "wrong folder path"
12 }
13 do{
14 //子目錄
15 if (FileInfo.attrib&_A_SUBDIR)
16 {
17 //這個語句很重要
18 if( (strcmp(FileInfo.name,".") != 0 ) &&(strcmp(FileInfo.name,"..") != 0))
19 {
20 string newPath = folderPath+"\\" + FileInfo.name;
21 dfsFolder(newPath,fileArray);
22 }
23 }
24 //普通檔案
25 else
26 {
27 string fileName(FileInfo.name);
28 auto pos=fileName.rfind('.');
29 if(pos==string::npos)//無字尾的檔案,調試了好久,坑啊!
30 continue;
31 string fileSuffix=fileName.substr(pos);//擷取檔案字尾
32
33 if(regex_match(fileSuffix,suf))//是否比對給定的檔案類型
34 {
35 fileArray.push_back(folderPath+"\\"+FileInfo.name);
36 }
37 }
38 }while (_findnext(Handle, &FileInfo) == 0);
39 _findclose(Handle);
40 return 0;
41 }
42 int findFile::getAllFiles(vector<string> &fileArray)//通過應用傳回函數值
43 {
44 return dfsFolder(dir,fileArray);
45 }
同時,程式中添加了許多注釋,使程式的可讀性提高。疊代器的使用使得單詞的讀取更加規範化模式化,并且省去了很多備援的代碼。
1 #include"word_iterator.h"
2
3 bool isAlpha(char c)
4 {
5 if(c>='A'&&c<='Z'||c>='a'&&c<='z')
6 return true;
7 else return false;
8 }
9 bool isAlnum(char c)
10 {
11 if(isAlpha(c)||c>='0'&&c<='9')
12 return true;
13 return false;
14 }
15
16 void word_iterator::getWord()
17 {
18 int l=line.size();//無符号轉成int
19 while(1)
20 {
21 while(s<(l-2))//找到第一個字母
22 {
23 if(isAlpha(line[s]))
24 if((s>0&&!isAlnum(line[s-1]))||s==0)
25 break;
26 s++;
27 }
28
29
30 if(s>=(l-2))
31 {
32 position=-1;
33 break;
34 }
35 position=s;
36 t=s+1;
37 if(isAlpha(line[t])&&isAlpha(line[++t]))//前三個是字母,找到一個單詞
38 {
39 while(++t<line.size()&&isAlnum(line[t]));
40 length=t-s;
41 str=line.substr(s,length);
42 s=t;
43 break;
44 }
45 else s++;//下次從s開始查找
46
47 }
48 }
自定義疊代器也使得程式多了很多自由性,便于滿足具體需求。疊代器暴露給其他類的僅僅是一些接口,比如讀取下一個單詞,判斷有沒有下一個單詞。至于具體如何讀取下一個單詞,下一個單詞的選取有什麼要求,這些都是其他類不需要考慮的。也就是說,疊代器内擷取下一個單詞的邏輯可以自定義,可以根據實際需求而改變,同時由于接口不變,其他類的代碼不需改變,隻需繼續通過疊代器暴露的接口來進行操作即可,實作了封裝性原則以及子產品之間一定程度上的獨立性,降低耦合度。
盡管晏旭瑞的代碼簡明扼要,有很多優點,設計模式也很好,但是仍然有一些細微的問題,下面主要讨論代碼中的三個小問題:
1.在程式中,部分變量的命名可以改善,使其更有意義,可讀性更好,就像類和函數的命名一樣,讓閱讀者一看就知道是做什麼用的。
1 for(word_iterator it(s);it.position!=-1;++it)
2 {
3 //如果上次處理了一個單詞(或詞組),這次減掉一個單詞
4 if(i==m)
5 {
6 i--;
7 if(m==1)
8 target="";
9 else
10 target=target.substr(target.find(' ')+1);
11 }
12
13 //第一遍循環從這裡開始,分三種情況
14 if(m==1||i==0)
15 {
16 target=it.str;
17 i++;
18 }
19 else
20 {
21 if(s[p]==' '&&it.position-p==1)
22 {
23 target+=" "+it.str;
24 i++;
25 }
26 else
27 {
28 target=it.str;
29 i=1;
30 }
31 }
32 p=it.position+it.length;//三種情況都需要執行這句話
33
34 //判斷是否進行處理
35 if(i!=m)
36 continue;
37
38
39 add(target);
40 }
在該語句塊中,變量i和m的可讀性不是太好,讓人看了不明白是表示的什麼量,是以對于有i和m參與的語句,語句的含義最初看來也不是太明了。
1 #ifndef _wordCount_H
2 #define _wordCount_H
3 #include<map>
4 #include<string>
5 #include<regex>
6 #include"struct.h"
7 using namespace std;
8
9
10 class wordCount
11 {
12 private:
13 int m;
14 regex word;//定義單詞的正規表達式
15 map<string,int,cmp> wordList;
16 map<string,string> map2;
17 void add(string target);
18 public:
19 wordCount(string wordDef,int a):m(a){regex r(wordDef);word=r;}
20 void statistic(string s);
21 int query(string s);//供wordGroupCount查詢使用
22 void outPut(string fileName);
23 };
24 #endif
在類的定義中找到了m,但是仍然不太明白m的作用,這個變量名稱的可讀性依然不是太好。對于這些計數變量,或者是表示統計資料等的變量,雖然看起來有些麻煩,但是這些變量的命名依然建議具有含義,這樣編寫代碼者不會混淆其含義,閱讀代碼者也能夠很快明白其含義。
2.程式中某些部分産生局部變量過多,産生了一些程式垃圾,而這些其實是有可能避免的。
1 int findFile::dfsFolder(string folderPath,vector<string> &fileArray)
2 {
3 _finddata_t FileInfo;
4 string strfind = folderPath + "\\*";
5 //搜尋與指定的檔案名稱比對的第一個執行個體,若成功則傳回第一個執行個體的句柄,否則傳回-1L
6 long Handle = _findfirst(strfind.c_str(), &FileInfo);
7
8 if (Handle == -1L)
9 {
10 return -1;// "wrong folder path"
11 }
12 do{
13 //子目錄
14 if (FileInfo.attrib&_A_SUBDIR)
15 {
16 //這個語句很重要
17 if( (strcmp(FileInfo.name,".") != 0 ) &&(strcmp(FileInfo.name,"..") != 0))
18 {
19 string newPath = folderPath+"\\" + FileInfo.name;
20 dfsFolder(newPath,fileArray);
21 }
22 }
23 //普通檔案
24 else
25 {
26 string fileName(FileInfo.name);
27 auto pos=fileName.rfind('.');
28 if(pos==string::npos)//無字尾的檔案,調試了好久,坑啊!
29 continue;
30 string fileSuffix=fileName.substr(pos);//擷取檔案字尾
31
32 if(regex_match(fileSuffix,suf))//是否比對給定的檔案類型
33 {
34 fileArray.push_back(folderPath+"\\"+FileInfo.name);
35 }
36 }
37 }while (_findnext(Handle, &FileInfo) == 0);
38 _findclose(Handle);
39 return 0;
40 }
在該函數中,有一個do-while循環體,在每一次循環中,
string newPath = folderPath+"\\" + FileInfo.name;
和string fileName(FileInfo.name);
都會建立兩個新的引用,來指向已有字元串或者是新産生的字元串。當然本來這些字元串是需要被提取并儲存以便後用的,但是大可不必在每次循環的時候都建立新的引用類型變量,而是可以在類中定義私有變量來表示相同涵義,在函數中利用這些私有變量來進行字元串位址的引用與儲存,這樣就不需要在每次循環趟中都建立新的引用變量了。
3.在最後的排序過程中,本程式現将哈希表中的元素導出至vector中,然後調用庫函數中的sort方法,并傳入自定義的比較器進行比較。比較的方面,調用固有的sort方法并自定義比較器是比較明智并且快速簡潔的做法,但是并不一定需要将哈希表中的元素導出至vector中然後再排序。也就是說,可以直接對哈希表進行排序。這樣可以省去導出哈希表項所耗費的資源。
1 bool comp(const PAIR &x, const PAIR &y)
2 {
3 if(x.second>y.second)
4 return true;
5 if(x.second==y.second)
6 return x.first<y.first;
7 else return false;
8 }
9 void wordCount::outPut(string fileName)//按照出現次數降序排序
10 {
11 vector<PAIR> pair_vec;
12 for (auto map_iter = wordList.begin(); map_iter!= wordList.end(); ++map_iter)//将map換到vector中
13 {
14 //cout<<setw(20)<<left<<map_iter->first<<map_iter->second<<'\n'<<right;
15 pair_vec.push_back(make_pair(map_iter->first, map_iter->second));
16 }
17 sort(pair_vec.begin(), pair_vec.end(), comp); //按照comp的規則排序
18
19 int n=pair_vec.size();
20 if(m!=1&&M<n)
21 n=M;
22 ofstream out(fileName);//在目前目錄建立晏旭瑞.txt,輸出統計結果
23 for (int i=0;i<n;i++)
24 {
25 out<<setw(40)<<left
26 <<"<"+pair_vec[i].first+">:"<<pair_vec[i].second<<'\n'
27 <<right;
28 }
29 }
将鍵值對存儲在哈希表中,有利于單詞的查詢(鍵-值對随機查詢,時間複雜度為0(1))。不過哈希表的排序并不容易,因為哈希表不能夠進行下标索引,是以一般的排序方法對哈希表都不奏效。在我的程式中我也是使用了哈希表來進行單詞的存儲以及檢索,不過我用的不是map類,而是dictionary類。dictionary類本身實作了IEnumerable接口,是以可以利用System.Linq命名空間中的Enumerable類的方法進行排序,方法主要有OrderBy,OrderByDescending,ThenBy,ThenByDescending等。這些排序方法可以指定排序的對象以及排序的比較器,在哈希表的排序中非常好用,時間複雜度與sort相同,都與快速排序的複雜度一樣,為O(nlogn)。
以上是我對晏旭瑞的個人項目代碼的複審報告。