天天看點

軟工實踐寒假作業(2/2)

這個作業屬于哪個課程 2021春軟體工程實踐¦S班 (福州大學)
這個作業要求在哪裡 軟工實踐寒假作業(2/2)
這個作業的目标 閱讀《建構之法》并提出問題、使用GitHub送出項目、完成WordCount程式并測試
作業正文 正文
其他參考文獻 部落格園、CSDN、簡書、百度百科

GitHub連結

項目連結

目錄

  • part1:閱讀《建構之法》并提問
    • 問題1
    • 問題2
    • 問題3
    • 問題4
    • 問題5
    • 附加題
  • part2:WordCount程式設計
    • Github項目位址
    • PSP表格
    • 解題思路
    • 代碼規範制定連結
    • 設計與實作過程
      • Lib類中的函數
    • 性能改進
    • 單元測試
      • 測試統計字元數函數
      • 測試統計單詞與提取頻率最高的10個單詞函數
      • 測試提取頻率最高的10個單詞
      • 測試有效行函數
      • 覆寫率截圖
    • 異常處理說明
    • 心路曆程與收獲

在書中61頁,作者用魔方的例子講訴了“精通”二字的含義。

但我們真的有必要精通一門程式設計語言嗎,以下摘自知乎平台的一份回答。

精通一門語言的最直接方法是研究和解析目智語言的編譯原則。想精通太難,而且作為一個大學畢業的學生,執着于精通一門語言其實意義并不是很大。

在我過去三年的學習中,不要說精通,就連掌握一門語言的标準都沒有達到。拿java來說,作為這次部落格作業的程式設計語言,我花了不少時間去重新學習了map,hashmap,treemap三種資料結構對應的方法,以及他們之間的關系。而精通java不僅要了解底層實作,還要有廣闊的技術視野,想必要花很多時間精力去學習。

我認為不必追求精通一門程式設計語言,但要不斷擴充自己的“學習區”,強化基礎知識,打牢基礎。

在書中93頁,作者主張在最外層,即行為和後果層給與他人回報。作者的理由是前兩層回報難以改變,而最外層的行為和後果是可以被改正和彌補的。

此外,作者也提到三明治式的回報是容易被人接受的。

但我認為這種方式有時不足以引起他人的重視。雖然三明治式的回報确實讓人容易接受,但也容易讓别人不以為然,認為他犯的的錯誤隻是小問題,拿過去的一次合作經曆說,有些隊員喜歡“劃水”,隊長不斷地催促他們完成自己的任務,最後上交的成果并不好。如果給與他人的回報,大多都在評論别人三種層次中的行為後果層,他或許隻是這一次改正,但再次出現問題時,他還是會重蹈覆轍,是以我會考慮進一步在行為動機層上告訴他問題所在,這樣會不會取得更好的效果呢?

在看第八章需求分析的時候,我突然萌生了一個問題,這裡的需求與軟體的發展有沖突怎麼辦?

例如手機上有一個誇克浏覽器,過去我因為其簡潔的界面一直把它當作手機浏覽器的首選,但是在以後的更新中,誇克浏覽器不但内置了語音助手,雲存儲,甚至有了它自己的ui。當然,浏覽器多功能化是它的發展趨勢,但與此同時也會與使用者需求相沖突。在視訊網站bilibili上我也多次看到,許多up主吐槽誇克浏覽器“忘記初心”,而且解除安裝了該浏覽器。如果說軟體的發展會影響對該軟體的需求,那我們應該怎樣平衡這種沖突呢?

26頁講述了單元測試的一些标準,好的單元測試在覆寫率,或是一些邊緣,異常情況下,都要有好的表現。正如書中所說,代碼覆寫率100%也不等同于100%正确。

但單元測試可否幫助提高程式的性能呢?還是說單元測試僅僅是用于驗證正确性?

單元測試中有時間工具,可判斷程式運作時間,有時我也會借此來估計程式的運作速度是否達到預期,而且在單元測試中,一些問題可能是由于資料結構不合理引發的,這時可以通過優化資料結構,在提高正确性的同時也提高了程式性能。

在49頁,作者提到了軟體工程師成長的第一點,是積累軟體開發相關的知識,提升技術技能。其中包括一些程式設計語言,裝置驅動程式,核心調試器的掌握以及對某一開發平台的掌握。

但是經過了大學三年,我發現自己好像學的太少了。對裝置驅動程式和開發平台沒有過多的了解,太久沒用的程式設計語言也會忘記一些知識。是以我對于第一階段的學習還遠沒有結束,我也不清楚自己要掌握了哪些具體技術之後,才算完成了初級軟體工程師的第一階段,以及我需要花費大概多少時間來學習這些技能。畢竟大四面臨畢業,怎樣在有限的時間内讓自己在軟體工程的領域内掌握足夠的知識?

藍牙的名字來源于10世紀丹麥國王Harald Blatand——英譯為Harold Bluetooth(因為他十分喜歡吃藍梅,是以牙齒每天都帶着藍色)。在行業協會籌備階段,需要一個極具有表現力的名字來命名這項高新技術。行業組織人員,在經過一夜關于歐洲曆史和未來無限技術發展的讨論後,有些人認為用Blatand國王的名字命名再合适不過了。Blatand國王将現在的挪威,瑞典和丹麥統一起來;他的口齒伶俐,善于交際,就如同這項即将面世的技術,技術将被定義為允許不同工業領域之間的協調工作,保持着個各系統領域之間的良好交流,例如計算,手機和汽車行業之間的工作。名字于是就這麼定下來了。

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

  • 輸入方式:指令行
  • 輸入内容:兩個文本的路徑(相對路徑或絕對路徑),其中,前一個文本為待讀取文本,後一個文本為待輸出文本
  • 輸出内容:待輸出文本中的内容,包括:
  1. 統計字元數
  2. 統計單詞數
  3. 統計有效行數
  4. 統計最多的10個單詞及其詞頻

首先我先不考慮用指令行的輸入方式,而是手動輸入兩個檔案的路徑。輸入檔案路徑後,下一步要讀取文本并對文本進行分析統計,讀取文本不難,可建立一個用于打開檔案并分析檔案的類,在該類中寫一些靜态的方法(實踐中發現建立靜态方法并不是最優選擇)。下面對4種統計要求進行分析:

  1. 統計字元數:可在打開檔案讀取内容時順帶執行
  2. 統計單詞數:使用正規表達式比對(實踐中發現效率不高。已舍棄)
  3. 統計有效行數:使用正規表達式比對(實踐中發現效率不高。已舍棄)
  4. 統計最多的10個單詞及其詞頻:利用統計單詞數時的資料進行排序

最後将統計結果按照規定格式寫進輸出文本即可。

代碼規範

  • Wordcount

    用于擷取指令行的文本路徑,主函數的運作以及調用Lib類。

  • Lib

    用于讀寫文本并對文本内容進行分析統計。

  • readFileByChars() —— 打開檔案并統計字元數,單詞數,有效行數,并儲存單詞及其頻率

利用FileReader逐個讀入字元并統計字元數

fr = new FileReader(inputFilePath);
            int ch;
            while ((ch = fr.read()) != -1) {
            charsNumber++;
           

由于單詞至少有4個字母開頭,且之後可跟數字,是以可了解為單詞的開頭是4個字母,單詞是由于非字母數字結束的(單詞不會因為數字而結束,因為小于4個字母開頭構不成單詞),基于此設計算法。

單詞的判斷算法:

  1. 設定一個暫存字元串,規定暫存字元串隻有三種類型:為空,由1~3個字母組成,為一個單詞。(判斷暫存字元串是否為單詞的方法:長度是否大于三)
  2. 不斷讀入字元,判斷該字元的類型。
  3. 字元類型為字母,加入暫存字元串。
  4. 字元類型為非字母非數字,清空暫存字元串,判斷暫存字元串是否為單詞,若是則記錄。
  5. 字元類型為數字,判斷暫存字元串是否為單詞,若不是則清空暫存字元串,若是則加入暫存字元串。
  6. 讀入字元結束,判斷暫存字元串是否為單詞,若是則記錄。
軟工實踐寒假作業(2/2)

添加單詞進hashmap(需判斷其是否存在)

if (tempWord.length() > 3) {
              //若沒有則添加,若有則加一
              word = tempWord.toString().toLowerCase();
              wordsCount.merge(word, 1, Integer::sum);
           

由于換行一定有一個Enter鍵,隻需判斷在兩個Enter鍵之間是否出現過非空字元,基于此設計算法。

有效行的判斷算法:

  1. 設定一個布爾數a為假,代表該行沒有非空字元。
  2. 若字元不為回車,也不為空白字元,且該行沒有非空字元,則将a置為真。
  3. 若字元為回車,判斷該行有無非空字元,若有則有效行加一,不管結果如何a都置為假。
  4. 讀入字元結束,判斷該行有無非空字元,若有則有效行加一。
if (ch == 10) {
              if (isValued) {
                  lineNumber++;
              }
              isValued = false;
          } else {
              if (!isValued && !isBlankCharFuc(ch)) {
                  isValued = true;
              }
          }
           
  • countWords() —— 對單詞按照頻率進行排序

    利用stream可以同時實作:

  1. 按頻率排序
  2. 再按key值大小排序
  3. 截取前十個元素
wordsCount = getWordsCount()
                .entrySet()
                .stream()
                .sorted(Map.Entry
                        .<String, Integer>comparingByValue()
                        .reversed()
                        .thenComparing(Map.Entry.comparingByKey()))
                .limit(10)
                .collect(Collectors
                        .toMap(Map.Entry::getKey
                                , Map.Entry::getValue
                                , (e1, e2) -> e2
                                , LinkedHashMap::new));
           
  • writeFile() —— 寫入檔案

    周遊hashmap

for (Map.Entry<String, Integer> entry : wordsCount.entrySet()) {
            outputString.append(entry.getKey())
                    .append(": ")
                    .append(entry.getValue())
                    .append('\n');
        }
           

利用BufferedOutputStream寫入檔案

fos = new FileOutputStream(outputFilePath);
            bos = new BufferedOutputStream(fos);
            //設定編碼為utf-8
            byte[] bytes = outputString.toString().getBytes(StandardCharsets.UTF_8);
            bos.write(bytes, 0, bytes.length);
           
  • getCharsNumber() —— 擷取字元數
  • getWordNumber() —— 擷取單詞數
  • getLineNumber() —— 擷取有效行數

  • 一開始我想用靜态方法實作這些函數,但是每個方法都要傳遞參數,有的甚至要四五個參數,例如寫檔案時要給函數檔案路徑,單詞數,有效行數等等,而且靜态函數有時需要傳回多個值,綜上考慮後舍棄靜态方法,而是在類中添加了若幹成員變量。
  • 将打開讀取檔案與統計字元,統計單詞,統計有效行一起執行,做到隻讀取一次檔案内容。之前我是把檔案内容存進String中,後來想到,萬一這個String很大很大,在統計單詞與有效行的時候需要一遍又一遍地周遊,十分花時間,幹脆隻讀取一次。
  • 舍棄正則比對有效行數與單詞數。利用算法可以在讀取字元時順帶統計。
  • hashmap排序的stream用法。

由于不考慮中文,僅用0~127号ascii碼測試,結果為128,符合預期

StringBuilder text= new StringBuilder();
        for(int i=0; i<128; i++) {
            text.append((char) i);
        }
           

測試統計單詞:設計了一個字元串,其中包含5個合法單詞,并讓字元串重複添加五遍,總單詞數為25,符合預期

StringBuilder text= new StringBuilder();
        String words="123a;a15;A81qqs;axSx1651;1888asadw;QDWQ156;1535;awwwwc;ACWa;acwa\n";

        for(int i =0; i < 5; i++) {
            text.append(words);
        }
           

測試了單詞全小寫,相同頻率單詞的排序,結果符合預期

String[] answerKey={"acwa", "awwwwc", "axsx1651", "qdwq156"};
        Integer[] answerValue = {10, 5, 5, 5};
        int i=0;
        for (Map.Entry<String, Integer> entry : testLib.wordsCount.entrySet()){
            Assert.assertEquals(entry.getKey(), answerKey[i]);
            Assert.assertEquals(entry.getValue(), answerValue[i]);
            i++;
        }
           

測試包括多行空行,單行空行,單行中僅包含空白字元,結果為4,符合預期

String text="\ncs\n61561\n\n\n156\n\t\n615\n\n";
           

提高覆寫率方法:可通過對各個函數都進行測試,有分支的情況如if需要将每種情況測試一遍。

軟工實踐寒假作業(2/2)

包含兩個部分:指令行傳遞參數不為2,檔案讀寫錯誤

  • 若參數不為2則退出程式:
if (args.length != 2) {
            System.out.print("該程式隻接受兩個參數,待讀取檔案與待寫入檔案,程式關閉。。。");
            System.exit(0);
        }
           
  • 待讀寫檔案不存在則顯示錯誤資訊
catch (IOException e) {
                e.printStackTrace();
                System.exit(0);
            }
           

  • 這次作業相較于上次作業來說,工作量明顯增加了,作業的要求寫得十分詳細,但即便如此我做起來仍然有些費勁,感覺把java重新學了一遍。
  • 之前我沒有這麼系統的用github送出一個項目,這次作業加深了我對項目管理的認識。
  • 重新讀了一遍《建構之法》,對軟體工程的了解更加深刻了,但還沒有用于實踐,希望能夠盡快學以緻用。
  • 我之前用的ide是eclipse,現在換成了idea,對很多功能不是很了解,例如JUnit4的使用,通過這次作業讓我學習很多。