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


這個作業屬於哪個課程 2021春軟件工程實踐|W班
這個作業要求在哪里 寒假作業2/2
這個作業的目標 閱讀《構建之法》提出問題、根據需求編寫程序、使用PSP進行時間管理與總結
其他參考文獻 csdn、《構建之法》

part1:閱讀《構建之法》並提問

說明:因為本人不只閱讀第三版,所以引文文字的出處只提是哪個章節的。

問題1 軟件工程有沒有銀彈?原因是什么?

第十一章:谷歌研究院的院長Peter Norvig被問及同樣問題的時候說:“我從來不喜歡UML類型的工具,如果你不能通過計算機語言表達( UML要表達的東西),那就是這種語言的弱點。”像任何新技術一樣,以UML為代表的圖形化分析方法的確解決了不少實際問題,但是也引發了一些誤解、誤用、狂熱和“銀彈"的信仰。
第三章:軟件的模塊之間存在着各種復雜的依賴關系,軟件的不可見性和易變性。

對於銀彈這個詞我可能見過那么一兩次,但是沒有真正去了解這個詞到底什么意思。我查了資料說的是,由於銀彈這個詞是從英文silver bullet單純的翻譯而來,所以對於中國人很不好理解,看英文翻譯過來的文章,其實要表達的意思就是“萬金油”,感覺有效果,實際上可能只是安慰劑。還有出自《沒有銀彈》的定義,在軟件工程中,銀彈指能讓生產力在十年中提高十倍的方法。經驗而談,”萬金油“這東西應該不存在,因此很顯然我也認為軟件工程不存在銀彈。但這是因為書中所提到的軟件工程本身存在各種復雜的依賴關系、不可見性和易變性,以至於我們無法從根本解決這些問題嗎?

問題2 應該根據什么來選擇在哪個方面追求“專和精"?

第三章:沒有人能在學校里掌握所有“將來會用得到的知識"才離開學校.隨后馬上把技術運用在實踐中。工程師應該在實際工作中不斷學習和不斷成長,根據自己的情況選擇在哪個方面追求“專和精".在哪幾個方面達到"知道就好”的水平”。

這里所說的”自己的情況“是指自己的喜歡程度,自己的能力?還是什么?還有就是困惑如果自己專和精的技術不是現在的主流,我們還有必要堅持嗎?感覺根據自己有點不實際,好多都是根據形勢去選擇應該“專和精”的方面。現實中大多數可能都會選擇追隨主流,畢竟比較吃香吧。關於專和精,網上有個人給了個說法“做技術,一定要專和精,才會是生產力”,所以我還有一個困惑--“廣”的地位在哪?多種技能難道不好嗎?這是考慮到實際人的能力有限的情況,還是什么呢?

問題3 用戶體驗與產品質量為什么會沖突?

  • 第一章:專業人士都知道軟件有"Bug" .軟件團隊的很多人都整天和Bug打交道,Bug 的多少可以直接衡量一個軟件的開發效率、用戶滿意度、可靠性和可維護性。
  • 第一章:一個好的軟件,即使功能和同類軟件區別不大,但卻會讓人感覺到非常好用.這就是軟件的用戶體驗(用戶體驗)。用戶體驗和數據結構、算法沒有直接的關系,但是很多非常成功的軟件就贏在這個方面.
  • 第十二章:好的用戶體驗是所有人都想要的,如果它和產品質量有沖突,怎么辦

前兩點好像確實是這么回事,bug的多少可以直接衡量用戶滿意度(也就是第二點提到的用戶體驗感);用戶比較關心功能,而對怎么實現並不關心。但是我對第三點有點質疑,高質量的產品無疑具有較高的性能和很少的bug,而bug的多少可以直接衡量一個軟件的用戶滿意度,所以高質量產品應該是對用戶體驗的一個提升。因此用戶體驗與產品質量應該不會沖突吧。假如我所說的是錯誤的,那一個好的軟件是應該追求好的用戶體驗,還是應該所追求好的產品質量,或者是折中呢?其中的“好“到底是怎么定義的?在書上也有看到對應的答案”優秀的作品往往並不符合所有”好“的標准。沒有最好的,只有最合適的“,但是還是不懂怎樣才是”合適“與”足夠好“。

問題4 如何把控軟件的依賴關系?

第三章:軟件的模塊之間存在着各種復雜的依賴關系,軟件的不可見性和易變性,使得軟件的依賴關系很難定義清楚,導致軟件不易得到及時的維護和修復。對依賴關系的兩種極端態度都會引出可笑的行為,並且無一例外地會造成延遲交付。

概括出來的思想誤區有這幾種

  • 分析麻痹:想弄清楚所有細節、所有依賴關系后再動手,心理上過於悲觀,不想修復問題。

  • 不分主次地想解決所有依賴問題:過於積極,想馬上動手“完美地”達到目標,而不是根據現有條件找到一個“足夠好”的方案。

  • 過早優化:在某一個局部問題上陷進去,花大量時間對其優化,無視全局。

  • 過早擴大化/泛化:程序雖然可擴展,但是要了解必要性和難度。把小問題真正解決好,也不容易。

這些誤區其實我也都有過。第一種實際上是還沒有開始就已經結束。第二種就好像做作業一樣,沒有先更好的熟悉API就着手,導致后面直接重構,十分浪費時間。關於優化這個問題,Donald Knuth論文中說到”找到瓶頸,做全局的性能分析,而不是把時間浪費在對程序中非關鍵部分的速度的無限思索“,但是很多同學包括我自己目前的全局觀念不夠多,導致經常放第三種思想錯誤。所以我們應該是只做全局瓶頸的性能優化嘛?但是不是每個部分都優化全局性能更好嗎?同時也很困惑怎樣去把控軟件的依賴關系,怎樣去思考軟件的開發才能解決所有依賴問題存在這樣的問題。

問題5 團隊中角色和職責有必要自然轉換嗎 ?

第十七章:團隊成員相互支持,相互依賴,角色和職責能夠根據項目的要求自然轉換

這里所說的角色、職責能夠自然轉換是否要求我們要掌握多種技能,勝任前端與后端嗎?但如果對於專攻前端和專攻后端的兩人,那這個要求顯然不實際。而且感覺較多人都會選擇一個方向專攻。所以我認為這個要求太過嚴格吧(雖然如果能做到,那更有助於完成各種項目,這個是無疑的)。我還有一個疑惑是全棧工程師的定義到底是什么,為什么我有這個困惑呢,因為上述所說的要求,符合百度百科中全棧工程師的定義,但是我在網上有看到一篇文章講到,百度百科上的定義是錯誤的,原話是“真正的全棧工程師,是讓你職業向上成長的概念;不是讓你掌握更多開發語言,往旁邊成長,這樣只會成為一個大胖子,互聯網行業發展這么快,大胖子是跟不上節奏的,會帶來職業生涯的災難”。所以真正的全棧工程師是廣而不精嗎?

附加題

英國著名詩人拜倫的女兒Ada Lovelace曾設計了巴貝奇分析機上解伯努利方程的一個程序。她甚至還建立了循環和子程序的概念。由於她在程序設計上的開創性工作,Ada Lovelace被稱為世界上第一位程序員。 美國國防部開發的ADA語言就是為紀念這位世界上的第一位程序員而命名的。

源自:源鏈接

part2:WordCount編程

2.1 Github項目地址

PersonalProject-Java

2.2 PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划 30 25
• Estimate • 估計這個任務需要多少時間 30 25
Development 開發 590 730
• Analysis • 需求分析 (包括學習新技術) 90 100
• Design Spec • 生成設計文檔 30 20
• Design Review • 設計復審 20 30
• Coding Standard • 代碼規范 (為目前的開發制定合適的規范) 20 40
• Design • 具體設計 30 60
• Coding • 具體編碼 240 220
• Code Review • 代碼復審 40 60
• Test • 測試(自我測試,修改代碼,提交修改) 120 200
Reporting 報告 120 120
• Test Repor • 測試報告 40 50
• Size Measurement • 計算工作量 20 20
• Postmortem & Process Improvement Plan • 事后總結, 並提出過程改進計划 60 50
合計 740 875

2.3 解題思路

設計一個Lib類,在其中設計所有統計的方法,供WordCount類調用

具體

  1. 文件讀取:使用BufferedReader的read()方法
  2. 統計字符:由於不會出現合法ASCII以外的字符,所以打算直接獲取字符串長度
  3. 統計單詞數:感覺以前都是一個字符一個字符的判斷,所以剛開始也是這么做,但是之前看到了正則表達式感覺有助於這個統計,而且更加方便,由於之前只聞其名,這次就動手學了
  4. 統計有效行:剛開始想的是用'\n'結合無效行計算,但是最后上網看到了相關的正則表達,就用了后者
  5. 統計文件中各單詞的出現次數(對應輸出接下來10行):想的是用map存,排序的話之前沒怎么深入學習,就上網查了下相關用法
  6. 文件寫入:使用BufferedWriter的write()方法
  7. 資料來源:csdn、簡書、百度等

2.4 代碼規范鏈接

codestyle.md

2.5 設計與實現過程

類設計:Lib類(4個函數)、WordCount類(3個函數,內部有個IOUtils類(2個函數))

**
 * 統計類
 */
public class Lib {
    /**
     * @description 統計字符數
     */
    static int countCharNum(String str)
    
    /**
     * @description 統計空白行數
     */
    static int countValidLineNum(String str)
    
    /**
     * @description 統計單詞總數, 並統計單詞對應個數
     */
    static int countWordNum(String str)
    
    /**
     * @description 對wordMap中的單詞頻率排序
     */
    static List<HashMap.Entry<String, Integer>> sortWordMap()
}
public class WordCount {
    /**
     * @description 文件處理工具類
     */
    static class IOUtils {
        /**
         * @description 讀取指定文件,返回對應字符串形式
         */
        static String readFile(String infile)

        /**
         * @description 將統計數據字符串寫入文件
         */
        static void writeFile(String result, String outfile)
    }

    /**
     * @description 統計數據硬編碼
     */
    private String getResult(String content)

    /**
     * @description 執行統計
     */
    private void process(String infile, String outfile)

    public static void main(String[] args)
}

函數調用圖
圖片

功能實現

  1. 讀取文件:使用BufferedReader的read()方法讀取字符,通過StringBuilder拼接,最后返回文件字符串。
static String readFile(String infile) throws IOException {
    ...
    while ((ch = reader.read()) != -1) {
      builder.append((char)ch);
    }
    ...
}
  1. 統計文件字符數:直接獲取文件字符串的length。
static int countCharNum(String str) {
    return str.length();
}
  1. 統計文件有效行數:通過正則表達式匹配
static int countValidLineNum(String str) {
    ...
    Pattern linePattern = Pattern.compile("(^|\n)\\s*\\S+");
    Matcher matcher = linePattern.matcher(str);
    while (matcher.find())
        lineNum++;
    ...
}
  1. 統計文件單詞數:使用正則表達式匹配非空白符,進行分割,然后對每個分割完的串進行單詞判斷;同時在這一步進行單詞詞頻統計。
static int countWordNum(String str) {
    ...
    String[] words = str.split("[^a-z0-9]+");
    for (String word : words) {
        if (word.matches("[a-z]{4,}[a-z0-9]*")) {
            wordNum++;
            if (wordMap.containsKey(word)) {
                int n = wordMap.get(word);
                wordMap.put(word, n + 1);
            } else {
                wordMap.put(word, 1);
            }
        }
    }
    ...
}
  1. 單詞頻率排序:使用Collection.sort對Map進行排序。
static List<HashMap.Entry<String, Integer>> sortWordMap() {
    ...
    Collections.sort(wordMapList, new Comparator<HashMap.Entry<String, Integer>>() {
        @Override
        public int compare(HashMap.Entry<String, Integer> word1, HashMap.Entry<String, Integer> word2) {
            if (word1.getValue().equals(word2.getValue())) {
                return word1.getKey().compareTo(word2.getKey());
            } else {
                return word2.getValue() - word1.getValue();
            }
        }
    });
    ...
}
  1. 寫入文件:使用BufferedWriter的write()方法將結果寫入文件。
static void writeFile(String result, String outfile) throws IOException {
    ...
    BufferedWriter writer = null;
    writer = Files.newBufferedWriter(Paths.get(outfile), StandardCharsets.UTF_8);
    writer.write(result);
    ...
}

2.6、性能改進

  • 文件讀取的方式使用了帶緩沖的BufferedReader和BufferedWriter,提高了讀寫效率。

  • 用StringBuffer代替String進行字符串拼接

    1. 使用String對20w字符文件進行字符串拼接(這部分運行時間將近19s
    reader = new BufferedReader(new FileReader(infile));
    long startTime = System.currentTimeMillis();
    while ((ch = reader.read()) != -1) {
        str += (char)ch;
    }
    long endTime = System.currentTimeMillis();
    System.out.println("程序運行時間:" + (endTime - startTime) + "ms");
    

    img
    2. 使用StringBuilder對20w字符文件進行字符串拼接(這部分運行時間僅有15ms

    reader = new BufferedReader(new FileReader(infile));
    long startTime = System.currentTimeMillis();
    while ((ch = reader.read()) != -1) {
        builder.append((char)ch);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("程序運行時間:" + (endTime - startTime) + "ms");
    

    img

2.7、單元測試

說明:均是通過BufferedWriter將數據寫入文件,在單元測試函數中調用Lib類中對應方法進行測試,並通過Assert.assertEquals()將測試函數的結果與預期結果進行比較。

以下展示代碼均為關鍵代碼

  • 測試統計字符數
    需要考慮Ascii碼,空格、水平制表符、換行符等都需要考慮在內
String str = "aaa[ \t.bbgdb\nnwindows2000\\n123file\\rsdsd\r\n1";
...
Assert.assertEquals(Lib.countCharNum(testStr), testStr.length());
  • 測試統計單詞數
    需要考慮單詞至少以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫
String str = "tes@word\naaaa\tqifei12h,1wordne[ss1\n  fqsq1a \n\n3|]hioaoy";
...
Assert.assertEquals(Lib.countWordNum(testStr), 5 * 10000);
  • 測試統計有效行數
    任何包含非空白字符的行,都需要統計
String str = "  \nh53 jk,dne[ss1\n  fqqdfgg grga \n \t \n";
...
Assert.assertEquals(Lib.countValidLineNum(testStr), 2 * 1000);
  • 測試覆蓋率
    img
    img

如何優化覆蓋率

  • 代碼盡量簡潔
  • 不要生成寫太多無用的getter、setter

2.8、異常處理說明

  • 基本是I/O異常
  • WordCount類中,對文件傳入參數個數進行了異常處理
if (args.length < 2) {
    System.out.println("2 paths needed");
    return;
} else if (args.length > 2)
    System.out.println("choose tow font paths");

2.9、心路歷程與收獲

  • 復習了git和GitHub的使用,再一次感受到了用git工具管理代碼的優越性,以前都是一次性提交,感覺確實不一樣。
  • 通過這次作業初步學習單元測試,之前的測試方法都是直接運行程序,十分不便,但是使用單元測試,可以更加方便地對特定代碼進行測試。
  • 之前看見過正則表達式,以為賊難,初步學習了正則表達式,雖然規則挺多的,但是還是比較好理解。
  • 首次使用PSP,剛開始覺得感覺這都是隨意估計,沒什么依據,看了《構建之法》中的相關內容卻深有感受。
  • 切記不要再ddl前垂死掙扎,太痛苦了(還有建議電腦出現小故障及時去修,別等下崩了,啥也沒有)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM