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


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

這個作業屬於哪個課程 2021春軟件工程實踐|S班
這個作業要求在哪里 軟工實踐寒假作業(2/2)
這個作業的目標 閱讀《構建之法》並提問、WordCount編程
其他參考文獻

1.閱讀《構建之法》並提問

1.1閱讀並提問

1.在閱讀《構建之法》3.1 個人能力的衡量與發展 中的

初級軟件工程師如何成長呢?我認為有下面幾種成長

......

3.對通用的軟件設計思想和軟件工程思想的理解。這一方面就比較虛,什么是好的軟件設計思想?什么是好的軟件工程思想?一個工程師開了博客,轉發了很多別人的文章,這算有思想么?另一個工程師堅持做任何設計都要畫UML圖,這算有思想么?

我有一個問題:學習了軟件設計思想和軟件工程思想的知識之后,顯然並不是任何時候都要死板的按照所學的知識進行開發設計,軟件思想的運用邊界在哪里?何時該使用,何時不該使用?

我在網絡上搜索開發者何時會使用UML圖,有些基本不用,有些認為在有正規流程規范的公司工作才會使用,有些甚至不知道UML,軟件思想似乎在實際開發中的適用范圍似乎和模糊。

思考:同樣是UML,我在學習完UML之后恨不得每做一個項目都畫類圖,這樣顯然不太對。個人認為編程需要軟件思想的指導,但是不能死板地用所學的理論框住自己。但是對這一部分的理解還是有限,這個問題似乎有點大,我依舊無法清晰地知道自己在什么時候應該按理論要求開發,何時不要。

2.在閱讀《構建之法》3.2 軟件工程師的職業發展 中的

21世紀以來,中國大陸每年招收六百萬大學生,其中的百分之十是在學習各種IT相關的專業(計算機科學與技術、計算機工程、計算機軟件、軟件工程、管理信息系統等)。扣除讀研究生(最終大部分也會走上工作崗位)、出國等分流,同時考慮到培訓機構給就業市場貢獻的大量勞動力,每年大致有四十萬到六十萬左右的“職業軟件工程師”進入工作崗位。

我有一個問題:科班出身的軟件工程師和非科班出身的有哪些區別?如何在就業市場就業壓力如此大的情況下發揮科班出身的優勢?

前段時間看到一檔綜藝節目,里面有個工作室的程序員都不是科班出身的,原來有在工地搬磚的,有當護士的,有當電話客服的,讓我感受到了這個行業的競爭之激烈。知乎上的一個回答指出,兩者之間的差距在於非科班缺少根基,他們會去學習那些更有“價值”的東西,而忽略了基礎。

思考:我認為知乎的這個回答是很有道理的,要提升自己的競爭力,基礎知識就是拉開差距的重要點,這是優勢也是今后學習應該顧及的點。

3.在閱讀《構建之法》10.1 典型用戶和典型場景 中的

阿超:所謂“Person-a”,就是典型用戶,吳石頭/石頭他爹就是我們系統的兩個典型用戶。我們的確需要了解我們軟件系統的用戶(不是公司的商業客戶),那么,什么是典型用戶?在產品開發的過程中,我們經常需要描述一組典型的用戶。以前大家通常是以一些抽象的名詞來表示用戶,如“家用電腦初學者”、“經驗豐富的系統管理員”,現在我們建議用一個“典型用戶”來代表。典型用戶不再是一個抽象的概念,而應該是一個活生生的人物。典型用戶一般有哪些特性?一個典型用戶往往描述了一組用戶的典型技巧、能力、需要、想法、工作習慣和工作環境。

我有一個問題:如何確定軟件系統的典型用戶?

查閱網絡資料,確定典型用戶需要先收集用戶數據,找到不同用戶之間不同的行為,確定行為變量,最后用行為變量來找到典型用戶。

思考:感覺網絡上的這種方法先收集用戶數據依舊講的和模糊,收集哪些用戶數據呢?這種方法感覺會有很多的不確定性。

4.在閱讀《構建之法》6.1.2 敏捷流程概述 中的

沖刺期間,每天要開一個每日例會,團隊成員大多站着開會,所以又稱每日立會。大家依次報告:

我昨天做了啥

我今天要做啥

我碰到了哪些問題

每日立會強迫每個人向同伴報告進度,迫使大家把問題擺在明面上。

我有一個問題:如果團隊成員在每日例會中迫於壓力、礙於面子對自己的實際情況撒了謊,可能會打亂整個流程,該如何處理?

思考:老拖延症了。這種情況應該很考驗Scrum Master ,需要Scrum Master協調好團隊,搞好整個團隊的氛圍,營造一種大家都能實話實說,有問題大家一起解決的環境。其實這個問題可以擴展成:如果敏捷開發過程中出現意外,流程被打亂該如何處理?我覺得敏捷開發計划的周期比較短,應該不會造成很嚴重的后果。

5.在閱讀《構建之法》16.1.2 迷思之二:大家都喜歡創新 中的

如果使用QWERTY鍵盤,那么只有10%的英語單詞能在手指不離開鍵盤中列的情況下敲出來。但是如果使用Dvorak鍵盤布局,你可以在鍵盤中列打出60%的常用單詞!這樣會減輕手指和相關肌肉的負擔,減少勞損,同時加快打字速度。......但是,長期以來,人們已經習慣了QWERTY鍵盤,所謂先入為主。

我有一個問題:當前中國人最離不開的軟件微信,是否正在成為中國的“QWERTY鍵盤”?這樣的軟件存在是否會打擊行業創新的積極性呢?

思考:我覺得微信不是中國的“QWERTY鍵盤”,至少現在不是,畢竟微信有痛點,也不是不思進取。微信確實給聊天軟件的創新帶來了巨大的阻礙,壟斷行業讓創新很難被注意,但是有意義的創意被行業注意只是時間問題,不思進取,被行業淘汰也是時間問題。

1.2附加題

RSA算法在軟件開發中是常用的一種加密算法,它的命名是由三個提出者Ron Rivest、Adi Shamir、Leonard Adleman的姓氏首字母組成的。

Rivest在閱讀一篇新論文后,對文章中前所未有的思路興奮不已,將其中的思想忘我地對Adleman講解,Adleman卻並沒有太感冒。於是Rivest找到隔壁的Shamir拉來做同盟,開始他們的探索之旅。盡管 Adleman 不情願參與其中,他們還是會把結果拿給 Adleman,Adleman 的角色就是逐個擊破這些方案,找出各種漏洞,給那兩個頭腦發熱的人潑點冷水,免得他們走彎路。最后論文發表時,按照慣例,Rivest 應該按姓氏字母序將三人的名字署在論文上,也就是 Adleman、Rivest、Shamir,但 Adleman 總覺得自己貢獻微乎其微,不過是潑潑冷水,不至於還要署個名,便要求 Rivest 拿掉自己的名字。在 Rivest 的堅持下,他最終要求至少把Adleman的名字放到最后。也正因為如此,RSA 叫做 RSA沒有被叫做 ARS。雖然 Adleman 一開始認為這注定是他諸多論文中最不起眼的一篇,RSA 走紅后他還是調侃說,越來越覺得 ARS 更順口了。http://localhost-8080.com/2013/12/history-of-rsa/

Adleman的謙讓很有風度,非常瀟灑。我們應該看到這種行為對團隊成員的啟發,做好自己的事,不邀功,不搶別人的功勞。此外,我認為Adleman的角色在這三個人中,並不是可有可無,對成員進行鞭策糾正是很重要的工作。

2.WordCount編程

2.1Github項目地址

項目地址:TarsSE/PersonalProject-Java

2.2PSP表格

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

2.3解題思路描述

思考過程:

1.第一次看整個程序的需求,腦子亂亂的,要求不多但是組織的有點亂,一下子看不過來,就決定先將程序分解成多個類,各自處理一些功能,最后再整合起來

2.按照對文件處理粒度先分為三個類分別處理字符,單詞,行,因為從一整行內容中將單詞分離感覺比較復雜,將這些類處理的基本單位定為字符,單詞按照一個一個字符組成,行按照一個一個字符組成

3.處理字符的類主要用於判斷ASCII碼

4.處理單詞的類要實現單詞構建,單詞校驗,單詞總數統計,各單詞頻數統計,以及按照頻率和字典序排序單詞的功能

5.處理行的類要實現行構建,有效行判斷的功能

6.因為將程序分為多個功能,為了保證每個部分都可靠,減少后期返工的麻煩,單元測試部分打算每完成一個功能就測試一個功能,最后再測試整合后的程序

7.各個分離的功能都實現后寫main函數和構造接口都是很自然的,會涉及文件的處理

8.最后對程序進行性能測試和優化,優化后再完整的進行一次單元測試

找資料過程:

1.《構建之法》

2.要求中提供的資料

3.百度/Google

2.4代碼規范制定鏈接

代碼規范地址:Tars的Java代碼規范

2.5設計與實現過程

1.在一開始就把程序分出了多個類,分別處理字符、單詞和行,三個要求的接口都封裝到WordCountCore類下,此外使用一個類保存一些全局常量,方便修改一些需要經常用到的量,以及WordCount類包含main函數,共6個類

2.處理字符的類AsciiCharCounter主要用於判斷字符類型

    public boolean isAsciiChar(int inputChar) {
        boolean result = false;
        if (inputChar < 128) {
            result = true;
        }
        return result;
    }

函數通過字符編碼判斷是否為ASCII字符

  • 統計文件字符數

    將讀入字符使用isAsciiChar判斷,若為ASCII字符則字符數加一。

3.處理單詞的類WordProcessor我認為主要的難點在於如何將單詞從文件中通過一個一個讀取字符,將可能符合要求的單詞提取出來

StringBuilder possibleWord;
    public boolean buildPossibleWord(char scanChar,boolean isEndOfFile) {
        isPossibleWordAvailable = false;
        if (!isEndOfFile) {
            if (isLetterChar(scanChar) || isDigitChar(scanChar)) {
                if (!isInPossibleWord) {
                    isInPossibleWord = true;
                    possibleWord = new StringBuilder(INIT_WORD_MAX_LENGTH);
                }
                possibleWord.append(scanChar);
            } else {
                if (isInPossibleWord) {
                    isInPossibleWord = false;
                    isPossibleWordAvailable = true;
                }
            }
        }else {
            isPossibleWordAvailable = isInPossibleWord;
        }
        return isPossibleWordAvailable;
    }

單詞提取邏輯

函數在讀取到字母數字以外的字符或讀取到文件末尾時,之前讀取到的字符就有可能組成全部是字母和數字的字符串,從而提取出文件中獨立的字符數字串,進而進行進一步的合法單詞判斷。

  • 統計單詞數

    將分離到的可能是單詞的字符串使用isLegalWord函數進行判斷

    isLegalWord函數對合法單詞的判斷過程為

    合法單詞邏輯

    讀取到合法單詞就將合法單詞數加一

  • 統計各單詞出現次數

    private Map<String,Integer> wordSumMap = new HashMap<>();
    
    public boolean individualWordSumUp(String legalWord) {
        boolean isCounted = false;
        legalWord = legalWord.toLowerCase();
        if (wordSumMap.containsKey(legalWord)) {
            wordSumMap.replace(legalWord,wordSumMap.get(legalWord) + 1);
            isCounted = true;
        }else {
            wordSumMap.put(legalWord,1);
        }
        return isCounted;
    }
    

    使用individualWordSumUp函數對各個單詞頻數進行統計,統計過程為:

    1.將單詞都轉換為小寫,忽略大小寫的影響,同時也符合輸出時使用小寫的要求

    2.在存有單詞和頻數對的Map中查找要統計的單詞是否之前出現過

    3.若單詞之前已統計,則將對應的頻數加一,否則構建新的單詞頻數對添加到Map中,頻數設為1

  • 輸出頻率最高的10個單詞及頻數

    要統計詞頻最高的十個單詞,需要對單詞-頻數對進行排序,使用Java自帶庫將Map轉換為List再使用List接口的sort方法對List排序

    其中sort方法需要提供自定義的比較器Comparator,對照作業要求,比較器先比較單詞頻率,再比較字典序,字典序的比較使用String類的compareTo方法

        
        private final Comparator<Map.Entry<String,Integer> > wordComparator = new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> stringIntegerEntry, Map.Entry<String, Integer> t1) {//s<t1返回正
                int result = t1.getValue() - stringIntegerEntry.getValue();
                if (result ==0 ) {
                    result = stringIntegerEntry.getKey().compareTo(t1.getKey());
                }
                return result;
            }
        };
    
        public List<Map.Entry<String,Integer> > getSortedWordCountList() {
            List<Map.Entry<String,Integer> > wordList = new ArrayList<>(wordSumMap.entrySet());
            wordList.sort(wordComparator);
            return wordList;
        }
    

4.處理行的類重點和處理單詞的類相似,如何把行內容從文件中提取出來

​ 提取行內容的方法與提取單詞的方法相似,只是把分隔符換為'\n',對行內的內容不做要求,換湯不換葯,這里不再贅述。

  • 文件有效行數的判斷

    public boolean isEffectiveLine(){
        return (lineContent.toString().trim().length() != 0);
    }
    

    因為Java8沒有isBlank函數,因此使用了一種曲線救國的方法,刪去空白符,再根據字符串長度判斷是否為有效行。從后面的性能測試來看,這種方法還是會消耗比較多的時間,不夠直接。

5.接口封裝

​ 程序功能分解的比較細,計算模塊的接口就是上述類方法的組合

  • 統計字符數

    1.將字符從文件中讀入

    2.使用AsciiCharCounter對字符統計判斷

  • 統計單詞數

    1.將字符從文件中讀入

    2.使用WordProcesser的buildPossibleWord方法對單詞進行構建,若構成了可能合法的單詞,使用WordProcesser的allWordSumUp進行驗證和統計。

  • 統計最多的10個單詞及其詞頻

    1.將字符從文件中讀入

    2.使用WordProcesser的buildPossibleWord方法對單詞進行構建,若構成了可能合法的單詞,並且經過驗證是合法單詞,使用WordPricessor的individualWordSumUp對各單詞分別進行統計

    3.最后使用WordCount的getSortedWordCountList方法獲取排序后的單詞-頻數對構成的List,再對List進行修剪,最多保存10對。

6.WordCount實現

​ WordCount進一步對各個功能進行了整合,使用上述各種類方法,主函數執行時能夠只讀一遍文件完成對字符的統計,單詞的構建校驗和統計,各單詞的詞頻統計以及排序,行的構建以及有效行的判斷和統計。

2.6性能改進

性能測試方面使用一個8.17MB 包含8500k+字符的文件進行測試

優化前,使用JProfiler進行測試發現整個程序中InputStreamReader的read和StringBuffer的append方法耗費了較多時間,於是上網找資料進行優化:

1.使用InputStreamReader構造BufferedReader,用BufferedReader的read方法替換InputStreamReader的read方法,BufferedReader帶有緩沖區可以將一定大小的文件內容讀入內存,不必頻繁地進行系統調用,讀取硬盤,減少IO時間

2.經過網絡搜索,發現對字符串的拼接有三種方式:String的concat,StringBuffer的append,StringBuilder的append,其中StringBuilder的append效率最高,StringBuilder的append次之,String的concat效率最低,看別人對這三種方法的測試,StringBuilder的append和StringBuilder的append差距並不大,將StringBuilder的append換為StringBuilder的append之后,性能是有所提高的。

3.在代碼復審時發現了一個遺留問題,在實現各單詞頻數統計時使用的Map是TreeMap,TreeMap是基於紅黑樹實現的,查找和插入的時間復雜度為O(log n),而使用HashMap時間復雜度為O(1),在頻繁插入的情況下,HashMap會快得多,統計各單詞詞頻使用的Map功能基本上都是基於查找和插入實現的,因此將TreeMap換為HashMap,進一步提高效率。

性能優化后的調用數及執行時間:

性能展示

在性能優化方面大概用了3個小時,包括學習JProfiler的使用,以及各種優化方法的學習

2.7單元測試

2.7.1單元測試覆蓋率

覆蓋率截圖

2.7.2測試函數

單元測試大量使用到文件,將測試數據保存在文件中,這樣的好處在於:1)一個測試數據文件可供多個測試函數使用,提高利用率;2)不必在測試類中保存測試數據,在重復使用到測試數據時,不用大量復制數據,讓代碼更干凈

例如以下對核心模塊的部分測試代碼,一個文件可供多個測試函數使用,在其他測試類中也可使用,測試類中沒有保存測試數據,整體比較整潔

class WordCountCoreTest {
    WordCountCore wordCountCore = new WordCountCore();
    File testFile1 = new File("testText\\test1.txt");
    
    @Test
    void countChar() {
        int characterNum = wordCountCore.countChar(testFile1);
        int actual = 237;
        assertEquals(characterNum,actual);
    }
    
    @Test
    void countWord1() {
        int wordNum = wordCountCore.countWord(testFile1);
        int actual =  25;
        assertEquals(wordNum,actual);
    }
}

測試數據的構造,先按照比較正常的數據測試保證基本功能的正確實現,再到比較詭異的測試數據,測試在極端情況下程序的執行,構造測試數據,需要足夠了解自己寫的代碼,照顧到方方面面。這一點證明開發人員要負責自己所寫代碼的單元測試是合理科學的

2.7.3如何優化覆蓋率

利用工具,比如使用IDEA測試,已覆蓋的代碼在左側行數條上會被綠色覆蓋,未測試到的為紅色,根據非測試到的代碼構造相應的測試數據就可以提高覆蓋率。當然,單元測試覆蓋率不能說明軟件質量,依舊需要構建合適的測試數據進行進一步測試。

2.8異常處理說明

程序對文件不存在進行了異常處理,這種異常出現在用戶傳入的文件路徑不存在時。

單元測試樣例:

	String notExistFilePath = "testText\\test1";//不存在的文件路徑

 	@Test
    void main4() {
        WordCount.main(new String[]{notExistFilePath,"output.txt"});
    }

此外還有UnsupportedEncodingException和IOException。UnsupportedEncodingException在傳入的編碼字符串錯誤時拋出,在程序中只有源代碼被修改,代碼里的編碼字符串錯誤時才會出現,一般很少出現。IOException在讀寫異常時出現。

2.9心路歷程及收獲

2.9.1心路歷程

  • 一開始看到編程作業的要求感覺會是一個很復雜的系統,再看截止日期,心里就想,好日子到頭了。很慶幸一開始就決定把任務分解成好多個小的部分,其實助教在作業要求里面也是在拼命暗示分解,最后實現的時候目標就比較明確,而且做完之后感覺也沒那么復雜。
  • 剛開始要分解是沒錯,但是我在實際的分解中依舊糾結,該怎么分,這樣分對嗎,依舊不是很有頭緒。
  • 在這次作業中實踐了單元測試,感覺測試能給人成就感。做完一個部分,測試一下,這只有問題,改一改,再測一遍,通過,唔,我真的很不錯。

2.9.2收獲

  • 更加熟練Git和GitHub的使用。以前使用GitHub進行團隊開發是真的小白,每次提交都會有很多沖突,大家都是用GitHub Desktop手動處理沖突,現在進一步深入使用學會了.gitignore的使用,每次commit也越來越熟練了。
  • 學會使用用工具性能測試進行性能測試。在這次作業中,我初步學習了JProfiler的使用,能夠根據測試結果有針對的對效率低的代碼進行優化,摒棄了以前想到哪里可以優化就優化到哪里的盲目行為。
  • 單元測試是個好東西。原來測試是有章法的,以前對程序的測試也是盲目的,一般都是整個項目完成之后才會開始測試,這個時候測試通常都很頭疼:項目已經有了一定規模,很難照顧到方方面面;找到bug卻很難定位,改要改一大堆。現在把單元測試分散到開發過程中,問題好解決了,而且有工具輔助,讓測試可視化,成就感滿滿。通過這次作業,還親自體會了測試覆蓋率不等於代碼正確率的血一般的教訓。
  • 重拾Java。這次選Java進行開發,對Java一些基礎的運用都有所涉及,自己也通過網絡搜索等方式對一些生疏了的知識進行了復習。


免責聲明!

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



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