天天看點

寒假作業2/2

這個作業屬于哪個課程 2021春軟體工程實踐|W班 (福州大學)
這個作業要求在哪裡 寒假作業2/2
這個作業的目标 任務一:閱讀《建構之法》并提問、任務二:完成詞頻統計個人作業、任務三:撰寫部落格
其他參考文獻 CSDN、部落格園、簡書等

目錄

  • part1:閱讀《建構之法》并提問
    • 1.1 問題一
    • 1.2 問題二
    • 1.3 問題三
    • 1.4 問題四
    • 1.5 問題五
    • 附加題
  • part2:WordCount程式設計
    • 2.1 Github項目位址
    • 2.2 PSP表格
    • 2.3 解題思路描述
    • 2.4 代碼規範制定連結
    • 2.5 設計與實作過程
    • 2.6 性能改進
    • 2.7 單元測試
    • 2.8 異常處理說明
    • 2.9 心路曆程與收獲

在書中2.3個人開發流程中,書裡寫到“PSP依賴于資料。如果資料不準确或有遺失,怎麼辦?讓工程師編造些?”,
  • 我有了這個問題:如何保證資料準确呢。我在這次作業編寫程式過程中明顯感受到計劃趕不上變化,并不能很好的預估時間。而我上網查了資料,感覺這方面的内容也比較少,看的也不是很明白,就感覺時間不知道怎麼預估,有些子產品在做的時候甚至不知道自己做了,導緻感覺自己統計的時間也不是很準确,總結之後我的了解是:感覺現在這份表格,感覺有的不适合現在的我們的普通作業,比如生成設計文檔等,可能在腦子裡想想就過去了,有些部分在這次作業中不會出現,這就導緻這部分的内容很難預估時間,我的困惑是如何能在各種項目中都寫好PSP。

在書中3.2軟體工程師的思維誤區中,書裡寫到“一個工程師在寫程式時,經常容易在某個問題上陷進去,花大量時間對其進行優化,無視這個子產品對全局的重要性,甚至還不知道這個“全局”是怎麼樣的。這個毛病早就被歸納為“過早的優化是一切罪惡的根源"
  • 我有了這個問題:難道在各種項目中過早優化都是不好的嗎,上網查閱資料後看到了一些觀點:1、過早優化出現在一些相對容易解決的問題。2、過早優化是一種“美好的願景”。3、過早優化是由于未能正确對任務進行優先級排序。目前根據我自己的一些經驗,似乎過早優化并沒有壞處,反而感覺如果有的項目,作業之類,開頭沒有優化,會導緻後期要優化時要改大量的代碼。是以我目前的觀點是一些小項目前期優化時可以的。也不知道自己的觀點對不對。

在書中第5章第7章都講了團隊合作,書裡寫到“各司其職,對項目共同負責;團隊的各個角色合起來,對整個項目最終的成功負責,為什麼?因為每個角色在其職責範圍内的失敗都會導緻整個項目的失敗。”
  • 平時,在進行團隊合作時或多或少會出現團隊各個組員實力不均,配置設定完任務,可能會因為某些問題有的組員抱着混一混的心思,并不能完成自己的任務。那這時候為了順利完成任務,其他人就不僅要完成自己份内的任務還要兼顧别人任務,這就可能導緻組員心态的失衡,網上很多辦法都是換組員什麼的,但我想知道有什麼更好的辦法可以避免這種現象呢,或者說如何調動組員的積極性呢?

在書中8.3.2提到了深度面談來擷取使用者需求,“通過詳細的面談,廣泛而深入地了解使用者的背景、心理、需求等。這通常是一對一的采訪。這種方法費時費力,效果往往取決于主持面談的團隊成員的能力。深入面談這一方法也可以用在某一特定領域,例如軟體的使用者可用性和使用者界面,這也可以稱為軟體可用性研究(Usability)”
  • 文章提到深度面談來擷取使用者需求,比如說進行使用者界面的修改,但我的觀點是:一千個觀衆眼中有一千個哈姆雷特,每個人的審美也是不同的,可能有的人認為這個界面好看,有的人認為那個界面好看,但深度面談這種方法費時費力顯然不可能大規模開展,取到的樣本數也應該不會太多,那怎麼能判斷出深度面談所獲得的需求一定會适用于大多數人呢?

在書中12.1.6使用者體驗和品質中,書裡寫到“好的使用者體驗當然是所有人都想要的,如果它和産品的品質有沖突,怎麼辦?犧牲品質去追求使用者體驗麼,使用者能接受麼?”
  • 看了後面傑克·韋爾奇的故事,我有了這樣問題到底是體驗>品質,還是品質>體驗呢,如何能找到他們之前的平衡點呢?網上有些的說法是這樣的:“使用者需求是根本,在使用者體驗這個問題上,還要特别考慮到短期刺激和長期影響,在必要的時候我覺得可以犧牲軟體品質去追求使用者體驗。”因為項目做得不多在這方面也沒有什麼直覺的感受,看完了網上的回答的困惑是如何能更好平衡産品品質和使用者體驗呢?

故事:瑪麗·肯尼思·凱勒(Mary Kenneth Keller)在計算機科學曆史上絕對占有一席之地:她還是研究所學生時,幫助開發了BASIC語言;1965年從威斯康星大學計算機學系拿到了博士學位,這讓她成為第一批計算機學博士。同一個月,歐文·C·唐(Irving C. Tang)拿到了他在華盛頓大學聖路易斯分校的理學博士;這意味着,擁有計算機學博士學位的所有人當中有一半一度是女性。讓這個故事更具戲劇性的是,凱勒還是個修女,來自聖母瑪利亞修女會,受命緻力于教育事業。後來,凱勒在克拉克學院擔任了20年的計算機學系系主任。

見解:并不能說做程式員男生一定比女生有優勢,從事任何一份職業,努力必然會有結果,是以無論是男性還是女性,用能力和代碼來說話,每一個熱愛程式設計、肯于努力的人都會有回報。

PersonalProject-Java

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 20 30
• Estimate • 估計這個任務需要多少時間
Development 開發 415 740
• Analysis • 需求分析 (包括學習新技術) 70 40
• Design Spec • 生成設計文檔
• Design Review • 設計複審
• Coding Standard • 代碼規範 (為目前的開發制定合适的規範) 5
• Design • 具體設計
• Coding • 具體編碼 120 200
• Code Review • 代碼複審 80
• Test • 測試(自我測試,修改代碼,送出修改) 300
Reporting 報告 55 65
• Test Repor • 測試報告
• Size Measurement • 計算工作量 15
• Postmortem & Process Improvement Plan • 事後總結, 并提出過程改進計劃
合計 490 835

  1. 看到輸入檔案和輸出檔案以指令行參數傳入,想到涉及檔案讀寫就想着有沒有高效的檔案讀寫方式
  2. 統計檔案的字元,因為隻需要統計Ascii碼,漢字不需考慮,感覺一個個字元讀出來取長度就行了
  3. 統計單詞當時一開始想的是循環暴力判斷,後面做的時候發現好像可以通過正規表達式取出單詞,就直接去其他博文上查找正則的知識
  4. 統計有效空行,想的是取出行後trim完和空字元串對比即可判斷
  5. 統計單詞Top10看到要用鍵值儲存就想到hashmap,要排序什麼的感覺hashmap應該也有方法,就要用hashmap存放單詞
  6. 查詢資料主要是CSDN、簡書、部落格園等

codestyle.md

代碼分為Lib類(7個函數)和WordCount類(2個函數),WordCount類通過調用Lib類中的方法來完成要求:

Lib類

public class Lib {
    //統計字元數
    public static int countCharacters(String str)
    //統計有效行數
    public static int countLines(String str)
    //統計單詞數
    public static int countWords(String str, HashMap<String, Integer> wordMap)
    //按照單詞出現個數排序
    public static List<HashMap.Entry<String, Integer>> getSortedList(HashMap<String, Integer> wordMap)
    //讀取輸入檔案
    public static String readFile(String filePath) throws IOException
    //将結果拼接成字元串
    public static String getResStr(int charsNum, int wordsNum, int linesNum
            , HashMap<String, Integer> wordMap)
    //将拼接的字元串寫入檔案
    public static void writeFile(String filePath, int charsNum, int wordsNum, int linesNum
            , HashMap<String, Integer> wordMap) throws IOException
}
           

WordCount類

public class WordCount {
    //構造函數
    public WordCount(String filePath) throws IOException
    //主函數
    public static void main(String[] args) throws IOException
}
           

流程圖

寒假作業2/2

函數關系

  • WordCount()

    構造函數,調用

    readFile()

    函數将檔案讀入為字元串,并将得到的字元串作為參數傳遞給

    countCharacters

    countLines

    countWords

    三個函數擷取字元數、有效行數、單詞數。
  • getResStr()

    擷取結果字元串函數,調用

    getSortedList()

    函數,對單詞按出現次數進行排序。
  • writeFile()

    寫入檔案函數,調用

    getResStr()

    函數擷取最終結果字元串,并将字元串寫入檔案。

主要函數介紹

  • 讀取檔案:通過

    BufferWriter

    ,将檔案的每個字元讀出,并通過

    StringBuilder

    拼接成字元串傳回。這樣,所傳回的字元串可以用于多個方法,而不用多次讀入檔案。
public static String readFile(String filePath) throws IOException {
    ...
    while ((ch = reader.read()) != -1) {
      str.append((char)ch);
    }
    ...
}
           
  • 統計檔案字元數:因為隻需要統計Ascii碼,漢字不需考慮,直接擷取

    length()

    即可
public static int countCharacters(String str) {
    return str.length();
}
           
  • 統計檔案有效行數:先通過

    str.split("\r\n|\n")

    将檔案字元串按行分割,然後再将分割出來的每行去掉空白符與空字元串對比,如果不是空字元串則有效行數+1
public static int countLines(String str) {
    ...
    String[] strLine = str.split("\r\n|\n");

    for (String validLine : strLine) {
        if (!validLine.replaceAll("\r|\n", "").trim().equals("")) {
            cnt++;
        }
    }
    ...
}
           
  • 統計檔案單詞數:因為單詞是以分隔符分割,用正規表達式

    [^a-zA-Z0-9]

    對檔案字元串分割,分割出的字元串即可能會成為單詞,然後再用正規表達式

    ^[a-z]{4}[a-z0-9]*

    判斷分割出的字元串是否為單詞(因為

    word = word.toLowerCase()

    ,是以不用正規表達式中不需要

    A-Z

    ),若是單詞則存入

    wordMap

    中.
public static int countWords(String str, HashMap<String, Integer> wordMap) {
    ...
    Pattern pattern = Pattern.compile("^[a-z]{4}[a-z0-9]*");
    Matcher matcher = null;
    String[] wordStrTmp = str.split("[^a-zA-Z0-9]");

    for (String word : wordStrTmp) {
        word = word.toLowerCase();
        matcher = pattern.matcher(word);

        if (!word.equals("") && matcher.find()) {
            cnt++;
            if(wordMap.containsKey(word)) {
                wordMap.put(word, wordMap.get(word) + 1);
            }
            else {
                wordMap.put(word, 1);
            }
        }
    }
    ...
}
           
  • 單詞出現次數排序:

    HashMap

    改為

    List

    存儲,再用

    Collections.sort

    進行排序
public static List<HashMap.Entry<String, Integer>> getSortedList(HashMap<String, Integer> wordMap) {
    ...
    Collections.sort(list, new Comparator<Map.Entry<String,Integer>>() {
        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());
            }
            return o2.getValue().compareTo(o1.getValue());
        }
    });
    ...
}
           

  • 減少IO次數,一開始在統計字元、統計空行等函數裡都要進行讀入檔案操作,後來把讀取檔案單獨獨立為一個函數,統計字元函數隻需要接收字元串即可,減少了讀取檔案次數。
  • BufferWriter

    替代

    FileWriter

    ,前者有效的使用了緩存器将緩存寫滿以後(或者close以後)才輸出到檔案中,然而後者每寫一次資料,磁盤就會進行一次寫操作。
  • 一開始是使用

    String

    不停拼接字元來存取檔案中的資料
String str = "";
while ((ch = reader.read()) != -1) {
    str += (char)ch;
}
           
  • String

    每一次内容發生改變,都會生成一個新的對象,小檔案在讀取時影響不大,但在稍大檔案效率就很低
  • 于是改用

    StringBulider

    來存放資料
StringBuilder str = new StringBuilder();
while ((ch = reader.read()) != -1) {
    str.append((char)ch);
}
           
  • 下面是讀取50w字元檔案用

    String

    進行拼接所需耗時
寒假作業2/2
  • StringBuilder

寒假作業2/2

測試函數說明

  • 單個函數測試,均是通過

    BufferedWriter

    将資料寫入檔案,并在單元測試函數彙總調用

    Lib

    類中對應方法進行測試,并通過

    Assert.assertEquals()

    将測試函數的結果與預期結果進行比較。

下為部分單元測試函數(部分測試樣例):

  • 字元統計
@org.junit.jupiter.api.Test
void testCountCharacters() throws IOException {
    BufferedWriter bw = new BufferedWriter(new FileWriter("test1.txt"));
    String str = "aaa[ \t$bbbbb\nsdsd\r\n1";
    for (int i = 0; i < 10000; i++) {
        bw.write(str);
    }
    bw.close();
    String test = testReadFile("test1.txt");
    int cnt = Lib.countCharacters(test);
    Assert.assertEquals(cnt, 20 * 10000);
}
           
  • 單詞統計
@org.junit.jupiter.api.Test
void testCountWords() throws IOException {
    BufferedWriter bw = new BufferedWriter(new FileWriter("test3.txt"));
    String str = "tes@word\naaaa\tqifei123|]haoye(123test)123test123\t\r\nwuhu\n";

    HashMap<String, Integer> wordMap = new HashMap<>();
    for (int i = 0; i < 10000; i++) {
        bw.write(str);
    }
    bw.close();
    String test = testReadFile("test3.txt");
    int cnt = Lib.countWords(test, wordMap);
    Assert.assertEquals(cnt, 5 * 10000);
}
           
  • 整體運作測試
@org.junit.jupiter.api.Test
void testMain() throws IOException {
    String str = Lib.readFile("input5.txt");
    HashMap<String, Integer> wordMap = new HashMap<>();
    int charNum = Lib.countCharacters(str);
    int lineNum = Lib.countLines(str);
    int wordNum = Lib.countWords(str, wordMap);
    Lib.writeFile("output.txt", charNum, wordNum, lineNum, wordMap);
}
           

構造測試資料的思路

  • 1、盡量覆寫多的測試範圍
  • 2、測試資料量大
  • 3、包含大量空白字元,大量換行,開頭結尾換行
  • 4、包含大量非法單詞(如:98qwer123456)

測試覆寫率截圖

寒假作業2/2
寒假作業2/2

如何優化覆寫率

  • 1、代碼盡量簡潔,删除重複的代碼
  • 2、

    try/catch

    中有的異常可能會沒有覆寫到,可以選擇編寫一個異常類
  • 3、簡化邏輯,不要一直增加if

  • 本次作業異常較少,主要是檔案異常(

    IOException

    FileNotFoundException

    )和使用者輸入異常
  • 使用者輸入指令行參數不對時:
    寒假作業2/2
  • 使用者輸入的檔案不存在:
    寒假作業2/2
  • 使用者輸出檔案不存在則會自動建立檔案

  • 在這次作業中感覺收獲了很多,之前雖然用過git,github但也已經好久沒用了,這次作業終于撿起來了,更加深入了解了版本控制,也體會到了代碼應該一實作一個小子產品就該簽入,而不是等到寫了很多再進行簽入。
  • 通過這次作業也學習了單元測試,之前并沒有體會到它的優點,進行單元測試感覺能讓自己的代碼更為可靠,也能更好的減少bug吧,也感覺通過觀察覆寫率去讓自己的方法更為簡潔有效。
  • 通過編寫PSP表格,深深體會到把握開發流程的重要性,強迫自己去做好規劃,而不是直接上手去寫代碼,這樣編出來的程式也更有條理,但第一次寫感覺預估時間不是很準确,感覺還是要多寫多估。
  • 也好好鞏固了Java知識吧,畢竟好久沒用都有點忘了,之前隻記得有hashmap,api啥的都忘記了,感覺代碼其實還是要多打,打多了就記得牢了。
上一篇: 寒假作業1/2
下一篇: 寒假作業2/2