寒假第二次作業
這個作業屬於哪個課程 | 2021春軟件工程實踐 -W班 (福州大學) |
---|---|
這個作業要求在哪里 | 寒假作業(2/2) |
這個作業的目標 | 閱讀《構建之法》並提問 WordCount編程 |
其他參考文獻 | 簡書/博客園 |
目錄:
-1. Github項目地址
-2. PSP表格
-3. 解題思路描述
-4. 代碼規范制定鏈接
-5. 設計與實現過程
-6. 性能改進
-7. 單元測試
-8. 異常處理說明
-9. 心路歷程與收獲
一、 閱讀《構建之法》
我看了這一段文字(MVP具體的做法是:把產品最核心的功能用最小的成本實現出來,然后快速征求用戶的意見,(VIP服務的例子)……但它更強調更早獲得用戶反饋,為此可以在產品完成之前就發布。),有這個問題(為了征求用戶意見,所給出的核心功能是一種假設(假設用戶會使用)嗎?假設和實際用戶使用如果存在很多差別怎么辦?)。根據我的實踐,我得到這些經驗(以選擇不同顏色的衣服為例,“我”在有“被測試者”和真正的購買者兩種身份時可能會做出不同的選擇)。 但是我還是不太懂,我的困惑是(如何保證參與測試的用戶所做出的的選擇(程序員得到的假設)跟實際相差不大呢?)。
我看了這一段文字(4.5.3結對編程中,編碼不再是私人的工作,而是一種公開的“表演”。程序員的代碼、工作方式、技術水平都變得公開和透明。),有這個問題(雖然結對編程存在開發者之間可能就某一問題發生分歧,產生矛盾,造成不必要的內耗等缺點,但就效率來說依舊是一種高效的工作方式。那碰到溝通不合、不喜歡公開工作方式和技術水平的合作伙伴,選擇什么樣的方式會比較好呢?)。我查了資料,有這些說法(盡量在開始合作前先了解對方,並盡快確立項目的方向。),根據我的實踐,我得到這些經驗(如果遇到溝通不順,雙方可以先完成自己的部分,並嘗試換位思考,有時候是不是自己太過於把東西強加給對方?)。但是我還是不太懂,我的困惑是(在雙方的溝通中,如果是工作以外的內容的交流導致合作不順,該怎么辦呢?)。
我看了這一段文字( 敏捷對團隊的要求很簡單:自主管理、自我組織、多功能型,但這很難做的。),有這個問題(敏捷對團隊的要求前兩點皆為“自主”,若是有一個團隊同時具備老手和新手,每個人能力不盡相同,此時應該用木桶原理去評判這個團隊嗎?)。我查了資料,有這些說法(任何一個組織,可能面臨的一個共同問題,即構成組織的各個部分往往是優劣不齊的,而劣勢部分往往決定整個組織的水平。),根據我的實踐,我得到這些經驗(如果團隊中有一人水平與其他成員相差較大,整個團隊完成工作的進度會被迫延長,因為要花許多的時間去幫助/等ta 完成他的部分)。 但是我還是不太懂,我的困惑是(如果團隊中確實存在這樣的“短板”,該怎么辦呢?)。
我看了這一段文字( 軟件開發不可能一次滿足所有利益相關者的要求,但是我們一定要讓相關角色在這個階段有機會提出他們的需求和意見。),有這個問題(當項目的需求與軟件的利益相對立的時候,團隊該如何做選擇呢?)。我查了資料,有這些說法(關於廣告主的需求和利益:廣告主的需求,根源在於宣傳,無論是宣傳產品,亦或是企業形象、服務;而廣告主的利益,則在於其投入產出的比例及相應的渠道選擇。),根據我的實踐,我得到這些經驗(在我使用一些軟件的時候,並不想看跳出來的廣告,這些廣告會影響我對這個軟件的使用感受)。但是我還是不太懂,我的困惑是(廣告是一部分軟件的收入來源,遇到這樣廣告主的利益與用戶的需求產生矛盾的情況,軟件團隊該如何做選擇呢?)。
我看了這一段文字( 在“現代軟件工程”課程上,許多同學也提出了不少宏大的創新想法,但是到了課程結束時,什么也沒做成,只剩下一個空的構想。 ),有這個問題(如何判斷自己的想法是可行的呢?如何找到創新的方向呢? )。根據我的實踐,我得到這些經驗(在算法課程上,有時候會有和課本上不一樣的想法,但是實際操作的時候經常發現這些想法行不通/方法不正確/計算機實現很麻煩 )。
但是我還是不太懂,我的困惑是(怎么能及時判斷自己的想法是不是現實可行的呢? )。
二、WordCount編程
1、Github項目地址
2、PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 15 | 25 |
• Estimate | • 估計這個任務需要多少時間 | 15 | 25 |
Development | 開發 | 440 | 491 |
• Analysis | • 需求分析 (包括學習新技術) | 50 | 60 |
• Design Spec | • 生成設計文檔 | 30 | 33 |
• Design Review | • 設計復審 | 10 | 10 |
• Coding Standard | • 代碼規范 (為目前的開發制定合適的規范) | 20 | 18 |
• Design | • 具體設計 | 40 | 30 |
• Coding | • 具體編碼 | 180 | 180 |
• Code Review | • 代碼復審 | 20 | 60 |
• Test | • 測試(自我測試,修改代碼,提交修改) | 90 | 100 |
Reporting | 報告 | 67 | 70 |
• Test Repor | • 測試報告 | 45 | 40 |
• Size Measurement | • 計算工作量 | 12 | 10 |
• Postmortem & Process Improvement Plan | • 事后總結, 並提出過程改進計划 | 10 | 20 |
合計 | 522 | 586 |
3、解題思路描述
多次閱讀作業要求后,我分析出程序主要要完成的功能:
- 讀取輸入的文件
- 統計文件的行數
- 統計文件的字符總數
- 統計文件有效單詞數
- 統計文件中出現頻率最多的10個及以內的單詞
- 將以上結果寫入新的文件
- 從主函數的參數中獲取輸入輸出文件的路徑
於是我從1.6.內容開始,使用Fle、Filewriter完成對文件的讀寫。之后再次分析2、3、4、5,選擇用Bufferreader的read()和readLine()方法對文件將進行讀取。
為了存儲單詞以及單詞出現的頻率,我選用HashMap進行存儲,對於排序,我在網上找到了使用TreeMap進行排序和利用Stream Collectors API進行排序的方法。
4、代碼規范制定鏈接
5、設計與實現過程
1. 主要函數及類
WordCount:主函數
FileIO類:處理輸入的文件和將內容寫進輸出的文件
DoCount類:處理文件,統計文件行數、統計文件字符數等
2. 主要功能的實現
//處理輸入文件
public static String readFile(String file_path) throws IOException {……}
//將行數、字符數、單詞數內容寫進輸出的文件
public static boolean writeFile(String file_path,String out_word) throws IOException{……}
//將其他內容寫進輸出文件
public static boolean writeFile(String file_path, Map<String, Integer>word_freq)throws IOException {……}
//統計行數
public static int countLine(String path) throws IOException{……}
//統計文件的字符數
public static Map<String, Integer> countCharacters (String path) throws IOException {……}
//統計單詞
public static int wordNum(String path,String word_str,HashMap<String, Integer> word_freq)throws IOException{……}
//排序
public static Map<String, Integer> sortWords(HashMap<String, Integer>word_freq) {……}
3、函數關系
WordCount類對命令中輸入的參數進行判斷,若符合條件,將input文件交給FileIO處理,若符合要求則WordCount安排DoCount類的三個方法完成統計行數、字符數、單詞數等功能,最終將結果返回給WordCount,WordCount再將內容交給FileIO把結果寫入output文件。
4、主要代碼
計算行數
while((string = bReader.readLine())!=null)
{
line++;
}
統計文件的單詞總數:先將讀到的內容全部轉為小寫再按空格分割,然后判斷單詞是否符合規則
while((string = bReader.readLine())!=null)
{
String[] str1 = string.toLowerCase().split("\\s+");// 按空格分割
array.add(str1);
}
Pattern p = Pattern.compile(rule);
Iterator iterator = array.iterator();
while (iterator.hasNext()) {
String[] s = (String[])iterator.next();
for(int y = 0;y<=s.length-1;y++) {
Matcher m = p.matcher(s[y]);
if(m.matches()) {
total++;
}
}
將符合規則的單詞放入hashmap中比統計出現的頻數
if(word_freq.get(s[y]) == null) {
word_freq.put(s[y], 1);
}
else {
int old_value = word_freq.get(s[y]);
old_value++;
word_freq.replace(s[y], old_value);
}
利用Stream Collectors API進行排序
Map<String,Integer>sort_mapMap=word_freq.entrySet()
.stream()
.sorted(Collections
.reverseOrder(……))
.collect(Collectors
.toMap(Map.Entry::getKey,
Map.Entry::getValue,(e1,e2)->e1,LinkedHashMap::new));
自定義排序方式,及先對單詞出現的頻率排序,若頻率相同則根據字典排序。
new Comparator<Entry<String, Integer>>(){
@Override
public int compare(Map.Entry<String,Integer> o1, Map.Entry<String,Integer> o2) {
if (o1.getValue()<o2.getValue()) {
return -1;
}else if(o1.getValue() > o2.getValue()) {
return 1;
}else {
String a = o1.getKey();
String b = o2.getKey();
if(strcmp(a,b) > 0){
return -1;
}else if(strcmp(b, a) < 0) {
return 1;
}else {
return 0;
}
}
}
參照c當中的strcmp,在java中寫了strcmp方法,對將字符串轉為字符數組然后進行掃描,根據字典規則排序。
private int strcmp(String a, String b) {
char a_char[] = a.toCharArray();
char b_char[] = b.toCharArray();
int flag = 0;
int n = a_char.length;
if (a_char.length > b_char.length) {
n = b_char.length;
}
for (int i = 0; i < n; i++) {
if(a_char[i] == b_char[i]) {
continue;
}else if (a_char[i] < b_char[i]) {
flag = -1;
break;
}else {
flag = 1;
break;
}
}
return flag;
}
5、流程圖
在WordCount中使用try-catch檢測三個函數的執行,若發生錯誤,立即發送error內容並停止程序
6、性能改進
改進前:
HashMap取得了關鍵字(key)后再取得關鍵值(value)
Set keys = word_freq.keySet();
int count = 0;
for(Object key:keys) {
writer.write("\n");
writer.write(key+":"+word_freq.get(key));
if(++count == 10)break;
}
改進后:
因為寫入輸出文件的時候是需要Map中存儲的所有內容,所以使用Map.Entry可以直接得到所有內容,比上面的方法更加高效。
Set<Map.Entry<String, Integer>> mSet = word_freq.entrySet();
for(Map.Entry<String, Integer> entry :mSet) {
writer.write("\n");
writer.write(entry.getKey()+":"+entry.getValue());
if(++count == 10)break;
}
7、單元測試
1、覆蓋率

2、運行結果展示
結果展示:輸出的單詞統一為小寫格式。- 統計文件的字符數
- 統計文件的單詞總數
- 統計文件的有效行數
- 統計文件中各單詞的出現次數

行數統計:任何包含非空白字符的行,都需要統計。

單詞判斷:至少以4個英文字母開頭,跟上字母數字符號

統計單詞:不區分大小寫

內容為空時:
排序條件:頻率相同的單詞,優先輸出字典序靠前的單詞。
3、單元測試
測試統計行數 public static void countLineTest() throws IOException{
String path = "input1.txt";
int line = 0;
String file_path = FileIO.readFile(path);
line = DoCount.countLine(file_path);
assert line == 4;
System.out.print("\nline"+":"+line);
}
測試統計字符數
public static void countCharactersTest() throws IOException{
String path = "input2.txt";
……
FileIO.readFile(path);
word_map = DoCount.countCharacters(file_path);
Set<String> keys = word_map.keySet();
for(Object key:keys) {
result = word_map.get(key);
}
assert result == 10;
System.out.print("characters"+":"+result);
}
測試統計單詞數
public static void countWordsTest() throws IOException{
String path = "input1.txt";
FileIO.readFile(path);
int word = 0;
word_map = DoCount.countCharacters(file_path);
……
word = DoCount.wordNum(file_path,word_str,word_freq);
assert word == 4;
System.out.print("\nword"+":"+word);
}
測試結果

8、異常處理說明
在統計字符數、單詞數、行數,統計詞頻時,若出現錯誤,則終止程序
try { ……
} catch (Exception e) {
System.out.print("error1");
return;
// TODO: handle exception
}
若輸入的參數不是文件或者不存在
System.out.print("文件不存在或類型錯誤!");
return;
9、心路歷程與收獲
1、學習了Streams排序的方法。
2、復習了正則表達式
3、復習了Bufferreader的使用
4、學習了使用psp表格估計自己完成小任務的時間,雖然在這次完成作業的初期中沒有很好地跟着流程走,但是在中期通過再次閱讀《構建之法》更了解了PSP表格的作用,使得我有了明確的“將大任務細化,通過完成小任務,不斷積累最終完成大任務”的想法,我還帶着這樣的思想運用到以后的課程學習中。
5、在性能改進方面,當時並不是很有頭緒,在實際操作過程中,優化得並不是特別好,總是想着完成功能就可以了。因此,接下來要注重這方面的學習。