軟件工程—WC功能實現 (JAVA)
軟件工程—WC功能實現(JAVA)
Github項目地址:https://github.com/Ousyoung/wc
項目要求
? wc.exe 是一個常見的工具,它能統計文本文件的字符數、單詞數和行數。這個項目要求寫一個命令行程序,模仿已有wc.exe 的功能,並加以擴充,給出某程序設計語言源文件的字符數、單詞數和行數。實現一個統計程序,它能正確統計程序文件中的字符數、單詞數、行數,以及還具備其他擴展功能,並能夠快速地處理多個文件。
基本要求
? -c [filename]統計文件字符數
-w [filename]統計文件詞數
-l [filename]統計文件行數
擴展功能
-s [filename]遞歸處理目錄下符合條件得文件
-a [filename]返回文件代碼行 / 空行 / 註釋行
? 支持各種文件的通配符(*,?)
高級功能
-x 圖形化界面(未實現
PSP
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 40 | 60 |
· Estimate | · 估計這個任務需要多少時間 | 40 | 60 |
Development | 開發 | 850 | 1400 |
· Analysis | · 需求分析 (包括學習新技術) | 60 | 120 |
· Design Spec | · 生成設計文檔 | 40 | 120 |
· Design Review | · 設計復審 (和同事審核設計文檔) | 40 | 60 |
· Coding Standard | · 代碼規範 (為目前的開發制定合適的規範) | 20 | 60 |
· Design | · 具體設計 | 80 | 120 |
· Coding | · 具體編碼 | 500 | 800 |
· Code Review | · 代碼復審 | 40 | 30 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 70 | 90 |
Reporting | 報告 | 80 | 90 |
· Test Report | · 測試報告 | 40 | 50 |
· Size Measurement | · 計算工作量 | 10 | 20 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 30 | 20 |
合計 | 970 | 1550 |
解題思路
? 此次項目是模擬WC統計文件字,詞,行信息,很容易想到用JAVA中的I/O流來讀取文件信息,對於讀取文件字符數以及行數,這些都比較簡單,字符數只需讀出字符匹配不是換行符即可,行數只需沒讀到換行符就加一,基本功能當中就屬詞的統計較為不易,詞的界定相對來說要模糊的多,所以想到需要用正則表達式來匹配字符串,替換掉一些分隔符號,繼而使詞的匹配簡單化。
? 擴展功能方面,無非是在以上基礎上再多加細分和應用。代碼行/空行/註釋行同樣需要使用正則表達式來進行匹配,其界定一開始自己也很苦惱,但上網查閱資料,弄明白後也就迎刃而解了;至於文件的遞歸查找,只需要設計一個遞歸方法,遞歸查找指定目錄下所有文件名,再通過用戶鍵入的關鍵字作為方法參數,用contains(string)方法檢索出符合條件的文件即可。
設計實現
此次項目中只寫了兩個類,一個是存放各種統計方法的WC類,另一個是運行程序的主類Main類,用於啟動程序,裏面還包括了命令獲取,命令匹配的代碼。
運行程序,main方法啟動,系統顯使用說明,再通過用戶鍵入得到命令,考慮到項目要求的命令顯示,需要把命令拆分成兩部分:功能選擇function以及文件路徑path,繼而需要調用split(" ")方法,用空格分割命令得到子命令,再用多個if語句的嵌套,匹配調用WC類中不同的靜態方法,將path作為參數輸入,找到指定文件。
設計主類Main類中的靜態方法:詞統計wordsCount,代碼行,空行統計complexCount,文件遞歸處理fileHandle 等(只列舉了部分,詳見代碼說明)
類,方法調用關系圖如下
代碼說明
main方法
數據的輸入輸出處理,命令的提取,匹配都在這一部分,下面主要展示命令的提取,匹配部分。這裏使用nextLIne方法獲取命令,再用split分割命令,得到子命令,再進行匹配;外層使用whlie循環訪問,直至用戶鍵入退出命令 -esc 退出
while (true) {
Scanner getIn = new Scanner(System.in);
if (getIn.hasNext())
command = getIn.nextLine(); // 獲取指令
if (command.equals("-esc")) {
System.out.println("***WC.exe程序已關閉");
System.exit(0); // 關閉程序
} else {
String[] commands = new String[2]; // 字符串數組用於存放指令
commands = command.split(" ", 2); // 拆分用戶輸入的指令
function = commands[0]; // command前半部分為功能選擇
path = commands[1]; // 後半部分為文件路徑
// Mark:修改代碼時出現了數組下標越界的問題*****************
if (function.equals("-c"))
WC.charsCount(path);
else if (function.equals("-w"))
WC.wordsCount(path);
else if (function.equals("-l"))
WC.rowsCount(path);
else if (function.equals("-a"))
WC.complexCount(path);
else if (function.equals("-all"))
WC.allCount(path);
else if (function.equals("-s")) {
System.out.print("請輸入文件叠代查找的關鍵字:\n");
Scanner getIn2 = new Scanner(System.in);
// 需要拿到指定字符串,這裏的方法需要兩個參數
WC.fileHandle(path, getIn2.nextLine());
} else
System.out.println("用戶輸入不合法,請重新輸入!");
}
}
WC類
統計字符 charsCount
比較簡單,所以只展示關鍵代碼
try {
int tempchar;
readFile = new InputStreamReader(new FileInputStream(file));
while ((tempchar = readFile.read()) != -1) {
if ((char) tempchar != ‘\r‘ && (char) tempchar != ‘\n‘) {
// 只要匹配的不是換行符,字符數就加一
charsNum++;
}
}
readFile.close();
System.out.println("文件路徑:" + fileName + " 文件的字符數為: " + charsNum);
}
統計行 rowsCount
同樣比較簡單,所以只展示關鍵代碼
try {
readFile = new InputStreamReader(new FileInputStream(file));
int tempchar;
while ((tempchar = readFile.read()) != -1) {
if ((char) tempchar == ‘\n‘) {
// 匹配到換行符,行數就加一
lineNum++;
}
}
System.out.println("文件路徑:" + fileName + " 文件的行數為: " + lineNum);
readFile.close();
}
統計詞 wordsCount
使用正則表達式,匹配字符,將文件字符串中的 . * " " / 等符號用reaplaceAll方法替換成空格,再用split方法用空格分割字符串,得到詞
static void wordsCount(String fileName) throws IOException {
// 返回該文件的詞的數目
File file = new File(fileName);
BufferedReader bur = null;
String string = "";
String line;
int wordNum = 0;
String words[] = null;
try {
bur = new BufferedReader(new FileReader(file));
while ((line = bur.readLine()) != null) {
String s = line.replaceAll("[\\p{Punct}\\s\\p{Nd}\\uffe5\\u4e00-]", " ");
// 得到除字符外全是空格的文本
string = string + s + " ";
// 這裏要加空格,否則string每次增長,行末的詞會和下一行首詞相連
}
words = string.split(" ");
// 去除空格,得到詞的字符串數組
wordNum = words.length;
System.out.println("文件路徑:" + fileName + " 文件的詞數為: " + wordNum);
bur.close();
} catch (FileNotFoundException e) {
System.out.println("***系統提示:找不到指定的文件!請重新輸入:");
}
}
統計代碼行,註釋行等 complexCount
使用正則表達式匹配相應行:“((//)|(/+)|((^\s))|((^\s)+/))+” 註釋行,"^\s$"空白行,"(?!import|package).+;\s(((//)|(/+)).)" 代碼行
static void complexCount(String fileName) throws IOException {
// 返回該文件的代碼行 / 空行/註釋行
File file = new File(fileName);
BufferedReader bufr = null;
String line = null;
int codeLine = 0;
int blankLine = 0;
int noteLine = 0;
Pattern codePattern = Pattern.compile("(?!import|package).+;\\s*(((//)|(/\\*+)).*)*",
Pattern.MULTILINE + Pattern.DOTALL);
// 匹配代碼行 // Mark:匹配代碼曾行時出現了錯誤,還有優化空間*********
Pattern blankPattern = Pattern.compile("^\\s*$");
// 匹配空白行
Pattern notePattern = Pattern.compile("((//)|(/\\*+)|((^\\s)*\\*)|((^\\s)*\\*+/))+",
Pattern.MULTILINE + Pattern.DOTALL);
// 匹配註釋行
try {
bufr = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
while ((line = bufr.readLine()) != null) {
if (codePattern.matcher(line).matches())
codeLine++;
if (blankPattern.matcher(line).find())
blankLine++;
if (notePattern.matcher(line).find())
noteLine++;
}
System.out.println(
"文件路徑:" + fileName + " 文件的代碼行 / 空行 / 註釋行數為: " + codeLine + "/" + blankLine + "/" + noteLine);
bufr.close();
} catch (FileNotFoundException e) {
System.out.println("***系統提示:找不到指定的文件!請重新輸入:");
}
}
}
文件遞歸處理 fileHandle 及 allCount
遞歸返回目錄下所有文件名,再調用String類的contains()方法模糊匹配所有文件名含指定字符串str的文件,再調用allCount方法返回各個文件的詞,字符,行信息
static void fileHandle(String fileName, String str) throws IOException {
// 文件叠代處理
List<File> fileList = new ArrayList<File>();
File file = new File(fileName);
File[] files = file.listFiles();
// 獲取目錄下的所有文件或文件夾
if (files == null) {
// 如果目錄為空,直接退出
System.out.println("***系統提示:找不到指定路徑!請重新輸入:");
}
// 遍歷,目錄下的所有文件
for (File f : files) {
if (f.isFile()) {
fileList.add(f);
} else if (f.isDirectory()) {
System.out.println(f.getAbsolutePath());
fileHandle(f.getAbsolutePath(), str);
}
}
for (File f1 : fileList) {
if (f1.getName().contains(str) == true) {
// 文件名包含指定字符串,contains()方法值為true
WC.allCount(fileName + "/" + f1.getName());
// f1.getName()只是文件名,這裏要在f1.getName()前加入作為參數傳入的fileName路徑,否則會找不到文件
}
}
// System.out.println("測試代碼"); 左側為測試用代碼
}
static void allCount(String fileName) throws IOException {
// 返回詳細信息
WC.charsCount(fileName);
WC.wordsCount(fileName);
WC.rowsCount(fileName);
WC.complexCount(fileName);
System.out.println("\n");
}
運行測試
-c -w -l -a 測試 readme.txt文件
由於我寫了一個allCount方法(命令 -all ),相當於一次執行-w -c -l -a ,所以對於以上命令只做部分測試
-all 測試 readme.txt文件
-s 測試 檢索test目錄下文件名含one的文件(onechar, oneword ,oneline,one)
遞歸處理 .java源代碼測試
註:(上面截圖第二行文字中“叠代查找”應為“遞歸查找”,截圖的時候沒註意,寫錯了......代碼中已更改)
代碼覆蓋率
項目小結
? 此次項目是軟件工程的第一次項目,總的來說感覺自己做的有些倉促,許多細節的地方還有優化的空間,比如正則表達式匹配詞那裏,對於一些中文詞沒有考慮在內;還有main方法裏完全可以寫一個匹配方法來處理用戶命令,是代碼結構看起來更加合理;部分代碼(I/O流的創建)有些繁瑣,可以再簡潔化一點,達到更低的時間復雜度。不過,這次項目的收獲也不少,首先是對正則表達式的掌握有了提升,以前自己都沒怎麽仔細了解正則表達式,做項目的時候,邊做邊學,現在自己也能寫一些正則表達式了。再者,通過軟件工程理論的學習,開始了解編程的設計步驟,從無到有,一步一步來,效率有所提高,正如老師所說的,前面的準備工作做足,考慮更全面,後面的代碼編寫就會輕松高效很多。
軟件工程—WC功能實現 (JAVA)