這個作業屬於哪個課程 | 2021春軟工實踐|W班 (福州大學) |
---|---|
這個作業要求在哪里 | 寒假作業2/2 |
這個作業的目標 | 1.閱讀《構建之法》並提問 2.完成詞頻統計個人作業 |
其他參考文獻 | CSDN、博客園、Git |
GitHub:項目地址
part1:閱讀《構建之法》並提問
1.閱讀《構建之法》並提問
- Q1:我看了這一段文字(軟件系統是給用戶使用的,用戶的需求並不是要看這個機構的內部組織架構圖,而是要解決用戶的問題。 一個合適的團隊結構,能更大地改進交流的效率,讓團隊更能把注意力集中在最主要的目標——解決用戶需求上面。),有這個問題針對每個團隊各自隊員不同的脾性、能力,怎么在一開始選擇一個“合適” 的團隊模式。我查了資料,有這些說法(1.團員組成之際大家都應當有各自擅長的方面 2.多進行一些拓展活動讓大家迅速的熟悉起來 3.根據大家的特點去挑選適合這個項目組的團隊模式),根據我的實踐,我得到這些經驗(就以團隊成員的身份而言,一個人要學會融入,不管一個人技術怎么過硬,都是需要團隊的幫助的,但是還是很難避免團隊之中有個別人員不太配合)。 但是我還是不太懂,我的困惑是(如若,既定了一個盡可能優的團隊模式下,還是有個別團隊成員不願意配合,又該如何)。
- Q2:我看了這一段文字(如果使用QWERTY鍵盤,那么只有10%的英語單詞能在手指不離開鍵盤中列(Home Row,即ASDFG那一排)的情況下敲出來。但是如果使用Dvorak鍵盤布局,你可以在鍵盤中列打出60%的常用單詞。但是,長期以來,人們已經習慣了QWERTY鍵盤,所謂先入為主),有這個問題如果我有一個更好的創新性想法,改變某一原有事物,使得它的效率大提高,但由於人們總是習慣於先入為主,我的創新性行為失去了意義,我還要繼續嗎?我查了資料,有這些說法(讓別人清楚的看到創新想法與之前之間的區別,展示創新想法的優勢,並勇敢嘗試),根據我的實踐,我得到這些經驗(就我自己而言,在我用慣了一個軟件之后,別人告訴我另一個軟件其實更好用,我也依舊懶於嘗試。)。 但是我還是不太懂,我的困惑是(如果我花費了大量心思完成了我的創新想法,但依舊沒什么接受度,怎么辦)。
- Q3:我看了這一段文字(軟件工程專家Paul Rook說,“我們其實並不是不會估計,我們真正不會的,是把估計后面藏着的種種假設全部列舉出來”。),有這個問題我們在平時編程作業布置下來時,也常常錯誤估計了自己的預期完成時間,導致嘗嘗在ddl前瘋狂掙扎,所以到底如何正確預估項目所需時間?。我查了資料,有這些說法(1.降低任務的分解粒度 2.使用任務列表,列表包括任何測試和集成工作),根據我的實踐,我得到這些經驗(就我目前依照PSP表格估計作業時間,但依舊和實踐花費時間存在不小的差距)。
- Q4:我看了這一段文字(每個人每天的高效率工作時段不超過3—4個小時。結對編程中駕駛員和領航員的角色要經常互換,避免長時間緊張工作而導致觀察力和判斷力下降。),有這個問題在結對編程中,兩個經常交換角色不會導致打斷思路,降低效率嗎。我查了資料,有這些說法(一定時間周期地打亂配對,讓參與項目的人員相互轉換位置,容易讓所有人都熟悉每個模塊,這樣對於公司也很有好處),根據我的實踐,我得到這些經驗(經常的角色交換雖然如何資料而言可以使大家對各模塊更熟悉,但對我自己而言,角色的交換很多時候會把我既定的思路打亂,再次回到原來的位置時還要重新進入狀態,效率降低了些許)。
- Q5:我看了這一段文字(我們都用過各種電視/DVD播放器的遙控器,功能很強,按鈕很多吧?你有沒有注意到老人家使用遙控器時的困難?我們常說做產品要從用戶的角度考慮問題,這需要有“同理心”。),有這個問題一個產品它的受眾是許多的,每個人的適應度不同,雖有“同理心”,但大家感受不一,最后的設計到底該如何抉擇。我查了資料,有這些說法(針對主要受眾人群來抉擇),根據我的實踐,我得到這些經驗(就如遙控器而言,功能展示在按鈕上進行,於我來說感覺還是很方便,但我的家長就覺得如果簡單些更好)。 但是我還是不太懂,我的困惑是(如果兩個適應方向達到近1:1,如何抉擇)。
2.附加題
也許最令人難以置信的是,歷史上第一位程序員是位女性。她的名字是Ada Lovelace。在1843年,這位英國數學家Ada Lovelace,翻譯了意大利工程師Luigi Menabreaw撰寫的分析引擎文章。在翻譯過程中,她把自己的理解都批注到每篇文章下,而這舉動加快了計算機編程技術的發展。在這之后,她又設計出了第一種能夠利用分析引擎計算伯努利數的算法,這也是第一個用電腦編寫的算法。
參考鏈接
見解: 無論何人,其能力與創造力都是不容小覷的。
part2:WordCount編程
1.Github項目地址
2.PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 15 | 20 |
• Estimate | • 估計這個任務需要多少時間 | 15 | 20 |
Development | 開發 | 400 | 460 |
• Analysis | • 需求分析 (包括學習新技術) | 40 | 60 |
• Design Spec | • 生成設計文檔 | 20 | 20 |
• Design Review | • 設計復審 | 10 | 15 |
• Coding Standard | • 代碼規范 (為目前的開發制定合適的規范) | 20 | 25 |
• Design | • 具體設計 | 20 | 20 |
• Coding | • 具體編碼 | 200 | 220 |
• Code Review | • 代碼復審 | 30 | 30 |
• Test | • 測試(自我測試,修改代碼,提交修改) | 60 | 70 |
Reporting | 報告 | 70 | 90 |
• Test Repor | • 測試報告 | 45 | 60 |
• Size Measurement | • 計算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后總結, 並提出過程改進計划 | 15 | 20 |
合計 | 485 | 570 |
3.解題思路描述
仔細閱讀完作業題目要求后,發現題目要實現6個功能,具體需求為:
- 1.讀取txt文件中的內容
- 2.統計文件的字符數
- 3.統計文件的單詞總數
- 4.統計統計文件的有效行數
- 5.統計文件中各單詞的出現次數,並輸出頻率最高的10個
- 6.將輸出結果寫入txt文件
分析完需求后,問題首先要解決的就是如何進行文件的讀寫
針對第二個需求統計文件的字符數,我首先想到的就是采用一個字符一個字符的讀取文件內容,這樣可以更好的對每一個字符進行判斷。而后的幾個需求,我認為采用行方式讀取文件內容,對於功能的實現會簡單些。
對於第三個需求,我的想法是將文件每行的內容采用分割法,以非字母、數字的分割符將其分割保存在數組中,在對每個划分后的詞進行判斷是否為有效單詞,但這個分割的方法一開始讓我無從下手,后面在CSDN上查找資料發現了正則表達式,這樣單詞的分割就簡單了許多。
第四個需求也是對文件的每行單獨判斷,想着嘗試將字符串中的空字符以空字符串代替,后面查找資料發現了replaceAll("\s*","")方法。
針對第五個需求,采用同需求三一樣的方法先划分單詞,然后再將有效單詞轉換為小寫保存在Map中,每次有效單詞出現就先判斷是否存在,存在則將value+1,否則就存入新的有效單詞。詞頻的統計我是在CSDN上查找了Map<String,Integer>的排序方法,並且修改排序規則從而實現的。
4.代碼規范制定鏈接
5.設計與實現過程
相關類的設計
根據程序功能要求,划分為一個主函數,兩個類
- WordCount 主函數
- FileIO 實現文件的讀取,以及將結果寫入文件
- DoWordCount 實現字符、單詞、函數、單詞詞頻的計算
相關函數的設計
1. FileIO:
getReader(String filePath) //返回文件讀取的Reader
readFile(String filePath) //以ArrayList<String>形式返回文件讀取內容
writeToFile(String filePath, String str) //將結果寫入指定的文件中
2. DoWordCount:
countChars(String filePath) //統計字符數
countWords(ArrayList<String> lines) //統計有效單詞數
countLines(ArrayList<String> lines) //統計有效行數
sortWords(ArrayList<String> lines) //單詞詞頻統計並排序后輸出Top10
printTop10(List<Map.Entry<String, Integer>> maplist) //將詞頻Top10以字符串形式返回
isValidWord(char[] word) //判斷是否為有效單詞
isAlpha(int temp) //判斷是否為字母
函數關系
- countChars調用getReader,得到其返回的Reader進行以單個字符形式的文件讀取
- countWords、countLines、sortWords的參數皆為readFile返回的ArrayList
- countWords中在判斷是否為有效單詞時,調用了isValidWord函數,isValidWord函數中判斷單詞前四位是否為字母時調用了isAlpha函數
- printTop10的參數為sortWords函數Map排序后返回的List<Map.Entry<String, Integer>>
運行結果
流程圖
統計文件中各單詞的出現次數
關鍵代碼
//統計字符數,空格,水平制表符,換行符,均算字符
while ((tempChar = reader.read()) != -1) {
sum++;
}
在單詞分割上,我使用了正則表達式(這應該就是我這代碼里面唯一的獨到之處了) ,使用非字母和數字來分割文件內容的每一行,在對分割后的單詞逐一進行判斷,調用isValidWord(word)方法判斷其長度是否滿足要求,isValidWord(word)方法里面又調用了isAlpha()方法判斷前四位是否為字母。
//統計文件的單詞總數 單詞:至少以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫。
for (int i = 0; i < lines.size(); i++) {
line = lines.get(i);
words = line.split("[^a-zA-Z0-9]+");
for (int j = 0; j < words.length; j++) {
char[] word = words[j].toCharArray();
if (isValidWord(word)) {
sum++;
}
}
}
將文件的每一行中的空白符以空字符串代替,在將其與空字符串比較,若非空,則為有效行。
//統計文件的有效行數:任何包含非空白字符的行,都需要統計。
for (int i = 0; i < lines.size(); i++) {
line = lines.get(i);
line = line.replaceAll("\\s*", "");
if (!(line.equals(""))) {
sum++;
}
}
同前面一樣,先判斷是否為有效單詞,若有效,將其轉換為小寫,然后使用map進行儲存,因為map的key不可重復,所以每次寫入前判斷map之前是否存在過該數據,若沒有存在過,則該value值為1,否則,在其value值上再加1.最后將map改為list存儲,再用Collections.sort進行排序,通過重寫comparator來實現要求的排序,當單詞的數量一致時,則比較單詞在字典序的先后。
//統計文件中各單詞的出現次數並排序
if (isValidWord(word)) {
tempWord = words[j].toLowerCase();
if (!map.containsKey(tempWord)) {
map.put(tempWord, Integer.valueOf(1));
} else {
map.put(tempWord, Integer.valueOf(map.get(tempWord).intValue() + 1));
}
}
//通過ArrayList構造函數把map.entrySet()轉換成list
List<Map.Entry<String, Integer>> mappingList = new ArrayList<>(map.entrySet());
//通過比較器實現比較排序
Collections.sort(mappingList, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
if (o1.getValue().equals(o2.getValue())) {
return o1.getKey().compareTo(o2.getKey());
} else {
return o2.getValue().compareTo(o1.getValue());
}
}
});
6.性能改進
- 程序中,我在判斷是否為有效單詞時使用了較多的char[],並在循環中進行判斷,是致成性能瓶頸的一部分。就目前沒有想到別的更優解決方法。
- 用BufferedWriter代替FileWriter:FileWriter繼承了OutputStreamWriter,這意味着它的寫入是由FileOutputStream實現的;而BufferedWriter直接繼承於java.io.Writer。FileWriter每次調用write()方法,就會調用一次OutputStreamWriter中的write()方法;而BufferedWriter只有在緩沖區滿了才會調用OutputStreamWriter中的write()方法。但由於我們此次的輸出到文件的數據量並不多,所以在時間上產生的差異並不大。
7.單元測試
1.單元測試數據:
- 構造思路:盡量涵蓋所有可能出錯的范圍
- 單元測試數據:空文本、文本只包含空格,換行符與制表符、大小寫單詞、非法單詞、空格分割單詞、其他符號分割單詞、同頻單詞以字典序排序、小於10個單詞、多於10個單詞、只含空白符行、大量數據 (上述測試全部通過)
2.測試覆蓋率截圖:
3.如何優化覆蓋率:
- 文件數據等測試,加快速度可以一次性加入內存跑
- 有些測試比較重要,但又影響速度(高並發,多線程,高計算,高頻訪問redis,memcached等等),可以考慮自己跑完后,直接@Ignore或者拆分小方法,慢的部分Ignore。
- 方法體盡量小,一入一出,不要使用變量傳遞。
4.單元測試代碼: 將測試函數的結果與預期結果使用assertEquals(xxx,xxx)進行比較
@org.junit.Test
public void countChars() {
String inFile = "C:\\Users\\yy\\IdeaProjects\\WordCount\\src\\input1.txt";
int characters=0;
try {
characters=DoWordCount.countChars(inFile);
assertEquals(44, characters);
} catch (IOException e) {
e.printStackTrace();
}
}
8.異常處理說明
- 異常處理命令行參數無輸入/輸出文件的情況:
if(args.length!=2){
System.out.println("參數輸入個數有誤,請重新輸入");
return ;
}
- 輸出文件不存在時,系統會自動創建輸出文件。
- 剩余的輸入流異常,拋出到最外面同一處理。
9.心路歷程與收獲
在這次的作業中,我學到了很多編寫代碼以外其他的很多內容,也更加體會到了軟件工程這門課的重要性。接觸了Git和GitHub,並了解了如何使用github發布項目和控制版本,不在只是停留在紙面上的認識,一旦項目有進展便簽入GitHub,也使我更加深刻感到它帶來的好處。在此次的實踐中,我也學會了按照PSP表格對項目進行一步一步的構建完成,不像之前一股腦就開始進入編程階段,按照這樣的方式寫出來的程序可靠性也要來的強得多。並且在此次的作業中,也嚴格規范了自己的代碼風格,對我來說有又是一次成長。雖然一開始感覺好多東西都在我的知識盲區中,但是每次作業的完成,對未知知識的探索,都是一次極好的體驗。