CW程序編碼與測試總結
github地址:https://github.com/star-mick/wcproject
PSP
階段: |
估計耗時(min) |
實際耗時(min) |
計劃: |
5 |
3 |
計劃所需時間 |
5 |
3 |
開發: |
105 |
22.5*60 |
需求分析(包括學習新技術) |
20 |
25 |
生成設計文檔 |
5 |
2 |
設計復審 |
5 |
2 |
代碼規範 |
5 |
2 |
具體設計 |
10 |
30 |
具體編碼 |
30 |
17*60 |
代碼復審 |
10 |
2*60 |
測試 |
30 |
2.5*60 |
報告: |
35 |
2.25*60 |
測試報告 |
20 |
2*60 |
計算工作量 |
5 |
5 |
事後總結,並提出過程改進計劃 |
10 |
10 |
合計: |
140 |
24*60 |
對題目的思考:
讀完題目後,感覺需求非常的明確,只涉及對文件的讀寫操作,基本功能相對簡單,可以在了解需求的時候就想清楚代碼的設計,唯一的設計難點是-a的功能,因為狀態較多,所以 需要額外的分析設計。
統計字符數,單詞數和行數,是在對文件的的一次遍歷時對結果的簡單判別,並不值得討論。禁用單詞的功能使在單詞統計的基礎上讀出單詞並與禁用詞表進行比較,如果采用C++,或是java 有對字符串的一系列函數可以直接調用,我是用C操作的,C 有一個簡單的函數 char* strstr(char*,char*);可以查看第一個字符串是否包含第二個字符串,可以簡化-e的功能,但是由於將要讀取的字符串的長度未知,此時需要使用鏈表將所讀取到的字符拼接成字符串的形式,這涉及鏈表的插入刪除和創建。
還有兩個不太熟悉的功能,一是向main函數傳參並讀取,經過查閱百度很快就解決了,網上有很多解決方法。二是對文件夾的遍歷並返回文件的名稱,這裏需要用到<io.h>這個頭文件,其中的函數_finddata(),和函數findnext()可以解決對文件夾的相關操作,需要了解可以在網上直接百度得到。
-a的功能需要建立每一行的狀態圖,因為狀態相對較多,所以非常復雜,但卻沒有太多思考。
最後需要對代碼進行測試,進行單元測試,就是要對每一個代碼模塊進行正確性測試,因為該項目中的每個代碼模塊通常就是一個功能,所以測試和校驗相對明確,即對每個功能模塊進行測試。
程序實現過程:
本程序的功能相對獨立,所以在設計時所包含每個函數都獨立的完成項目的一項功能,main 函數對輸入參數進行處理,解析出需要調用的模塊和相關的文件路徑,包括對文件夾的分析,然後由5個獨立的函數分別實現字符統計、單詞統計、行數統計、禁用詞表後的單詞統計、單詞行註釋行空行的統計。main 函數根據需要調用相關的函數進行統計輸出。
但這些函數相對簡單,只有最後的-a功能需要設計復雜的流程處理,我在設計時考慮到了所有可能出現的情況,共設計了9中狀態,由於過於復雜,就不畫流程圖了,之後我參看了其他同學的狀態分析,他門的處理相對簡單,更便於分析和設計,因此在對實際情況進行分析設計時應該簡化狀態來高效的處理關鍵的狀態遷移,而後才是由簡入繁式的更新代碼,這是我所學到的。
代碼說明:
這是所涉及的函數,每個函數完成一項功能,其下是建立字符節點的結構體和單詞的的結構體,單詞的結構體包含字符鏈表,存儲一個字符串。
//分別計算字符數,單詞數,行數,(代碼行,空行,註釋行),帶禁用的單詞統計 int countcn(FILE*); int countwn(FILE*); int countln(FILE*); int* countal(FILE*); int countwn2(FILE* f, FILE* f2); struct charlink { char c; charlink* next; }; struct word { charlink* mychar; int len; };
下面是-a功能的實現代碼,基本每一句代碼都有註釋。
//統計單詞行,空行,註釋行 int* countal(FILE* f) { int x[3]={0}; int ln=0;//記錄行狀態 char ch,ch1=‘a‘; int lm = 0;//記錄註釋類 int keword = 0; while ((ch = fgetc(f)) != EOF) keword++; fseek(f,0L,SEEK_SET); while (keword--) { if (keword>0) ch = fgetc(f); else ch = ‘\n‘; //printf("%d ", ln);//測試狀態遷移 lm = 0; //進註釋 if (ch == ‘/‘){ ch1= fgetc(f); if (ch1 == ‘*‘)lm = 1;//出現/* else if (ch1 == ‘\n‘) ch=ch1;// /後回車(假定不會出現) else if (ch1 == ‘/‘)lm = 3;//出現// else if (ch1!=‘ ‘&&ch1!=‘\t‘) ch1 = ‘x‘;//雙字符 } //出註釋 else if (ch == ‘*‘){ ch1 = fgetc(f); if (ch1 == ‘\n‘);//*後回車 else if (ch1 == ‘/‘) lm = 2;//出現*/ else if (ch1!= ‘ ‘&&ch1!= ‘\t‘) ch1 = ‘x‘;//雙字符 } if ((ch == ‘\n‘&&ln == 1 && ch1 != ‘\n‘) || (ch == ‘\n‘&&ln == 0))//空狀態本行有一個或0個字符 x[1]++, ln = 0, ch1 = ‘a‘; else if (ch1 == ‘\n‘&&ln == 1)//空狀態本行有2個字符 x[0]++, ln = 0, ch1 = ‘a‘; else if (ch == ‘\n‘ && ln == 5)//空註釋 x[1]++; else if (ch == ‘\n‘&&(ln == 6||ln==8)) x[0]++, ln = 0;//表示本行有代碼x else if (ch == ‘\n‘&&ln == 2) x[0]++, ln = 5;//進入x/*後的回車 else if (ch == ‘\n‘&&ln == 7) x[2]++, ln = 5;//進入/*後的回車 else if (ch == ‘\n‘ && ln == 9) x[2]++,ln = 0;//非空註釋狀態//下換行 else if (ch == ‘\n‘ && ln == 3) x[2]++,ln = 0;//非空註釋狀態*/下換行 else if (lm == 1 &&ln == 6) ln = 2;//代碼後跟進註釋/* else if (lm == 1 && ln == 3) ln = 7;//出註釋後有/* else if (lm == 3 &&ln == 6) ln = 8;//代碼後跟全註釋x// else if (lm == 3 && ln == 3) ln = 9;//出註釋後有// else if (lm == 2 &&(ln == 5||ln==7)) ln = 3;//註釋/*,後出註釋 else if (lm == 2 &&ln == 2) ln = 6;//代碼後進註釋後出註釋 else if (lm == 1 && ln == 0) ln = 7;//直接進入註釋狀態/* else if (lm == 3 && ln == 0) ln = 9;//直接進入完全註釋狀態// else if (ch1 == ‘x‘&&ln == 0) ln = 6, ch1 = ‘a‘;//空狀態下兩個字符 else if (ch != ‘ ‘&&ch != ‘\t‘ &&ln == 3) ln = 6;//出註釋後有代碼 else if (ch != ‘ ‘&&ch != ‘\t‘&&ln == 5) ln = 7;//空註釋狀態下有字符 else if (ch != ‘ ‘&&ch != ‘\t‘&&ln == 1) ln = 6;//進入代碼狀態 else if (ch != ‘ ‘&&ch != ‘\t‘&&ln == 0) ln = 1; } return x; }
這段代碼就是對某行狀態的有限狀態遷移的分析,ln記錄本行狀態,共8種狀態,lm記錄所遇到的特殊字符,即出現的註釋字符,ln 0-9分別表示(本行為空且不再註釋之內,本行有一個代碼,本行前面是代碼後面是/*~,本行前面是註釋後面是*/,無意義,本行是空但在註釋之內,本行只有代碼,本行只有註釋且在/*~*/之內,無意義,本行開頭是//~)。代碼針對讀取到的不同的字符將狀態進行轉換。(4,8無意義)
以下是與禁用詞表相關的內容:讀取文件中的字符並建立單詞的鏈表:myword是單詞結構體,每讀取一個字符,就向單詞中的字符鏈表插入字符,需要在最後插入\0;
int i, j, kword; word* myword; myword = (word*)malloc(sizeof(word)); 。。。。 //以下是關鍵部分 if (kword!=0&&(ch >= 97 && ch <= 122) || (ch >= 65 && ch <= 90)) { if (state == 0)//開始記錄單詞 { ones = (charlink*)malloc(sizeof(charlink)); ones->c = ch; ones->next =NULL; myword->len = 1; myword->mychar = ones; p1 = ones; state = 1; } else //正在記錄單詞 { ones = (charlink*)malloc(sizeof(charlink)); ones->c = ch; ones->next = NULL; myword->len++; p1->next = ones; p1 = ones; } } else if (state == 1||kword==0) { state = 0; ones = (charlink*)malloc(sizeof(charlink)); ones->c = ‘\0‘; ones->next = NULL; myword->len++; p1->next = ones; oneword = 1; }
建立數組sign[]來記錄輸入的參數,以便於後續的判斷
for(i=1;i<argc;i++) { if( argv[i][0]==‘-‘)//判斷輸入參數 switch (argv[i][1]) { case ‘c‘:sign[0]=1;break; case ‘w‘:sign[1]=1;break; case ‘l‘:sign[2]=1;break; case ‘o‘:sign[3]=1;break; case ‘a‘:sign[4]=1;break; case ‘e‘:sign[5]=1;break; case ‘s‘:sign[6]=1;break; default: printf("輸入參數有誤"); break; } else if(argv[i-1][1]==‘e‘) stoplist=argv[i]; else if (argv[i - 1][1] == ‘o‘) result = argv[i]; else source=argv[i]; } dir = source;
測試設計過程:
由於該項目的功能相對獨立,采用單元測試,針對每個參數進行一次測試,就可以測試各個函數是否正確,針對含有判定的函數,可以設計測試文件的內容,使得每種情況都會被遇到,達到完全覆蓋。測試代碼采用的是bat的start函數多次調用wc.exe文件並采用不同的參數和輸出文件,編寫完成後,直接運行bat就可以在文件夾中查看輸出文件的內容並進行校驗。
CW程序編碼與測試總結