這個作業屬於哪個課程 | 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項目地址
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類調用
具體:
- 文件讀取:使用BufferedReader的read()方法
- 統計字符:由於不會出現合法ASCII以外的字符,所以打算直接獲取字符串長度
- 統計單詞數:感覺以前都是一個字符一個字符的判斷,所以剛開始也是這么做,但是之前看到了正則表達式感覺有助於這個統計,而且更加方便,由於之前只聞其名,這次就動手學了
- 統計有效行:剛開始想的是用'\n'結合無效行計算,但是最后上網看到了相關的正則表達,就用了后者
- 統計文件中各單詞的出現次數(對應輸出接下來10行):想的是用map存,排序的話之前沒怎么深入學習,就上網查了下相關用法
- 文件寫入:使用BufferedWriter的write()方法
- 資料來源:csdn、簡書、百度等
2.4 代碼規范鏈接
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)
}
函數調用圖:
功能實現
- 讀取文件:使用BufferedReader的read()方法讀取字符,通過StringBuilder拼接,最后返回文件字符串。
static String readFile(String infile) throws IOException {
...
while ((ch = reader.read()) != -1) {
builder.append((char)ch);
}
...
}
- 統計文件字符數:直接獲取文件字符串的length。
static int countCharNum(String str) {
return str.length();
}
- 統計文件有效行數:通過正則表達式匹配
static int countValidLineNum(String str) {
...
Pattern linePattern = Pattern.compile("(^|\n)\\s*\\S+");
Matcher matcher = linePattern.matcher(str);
while (matcher.find())
lineNum++;
...
}
- 統計文件單詞數:使用正則表達式匹配非空白符,進行分割,然后對每個分割完的串進行單詞判斷;同時在這一步進行單詞詞頻統計。
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);
}
}
}
...
}
- 單詞頻率排序:使用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();
}
}
});
...
}
- 寫入文件:使用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進行字符串拼接
- 使用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");
2. 使用StringBuilder對20w字符文件進行字符串拼接(這部分運行時間僅有15msreader = 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");
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);
- 測試覆蓋率
如何優化覆蓋率
- 代碼盡量簡潔
- 不要生成寫太多無用的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前垂死掙扎,太痛苦了(還有建議電腦出現小故障及時去修,別等下崩了,啥也沒有)