軟工實踐寒假作業(2/2)
| 這個作業屬於哪個課程 | 2021春軟件工程實踐|S班 |
|---|---|
| 這個作業要求在哪里 | 軟工實踐寒假作業(2/2) |
| 這個作業的目標 | 1、重讀閱讀之法提出問題 2、熟悉github的使用 3、編寫字符統計程序 4、學會單元測試 |
| 其他參考文獻 | 《碼出高效_阿里巴巴java開發手冊》 鄒欣老師博客關於單元測試和回歸測試 《淺析 Java Stream 實現原理》 《IDEA中使用JUnit4(單元測試框架)》 |
任務一 重新閱讀《構建之法》並提問
1、為什么有工作經驗的軟件工程師比學生在“需求分析”和“測試”上所花的時間多,但編碼時間短?我們應當怎么去安排“需求分析”、“測試”和“開發”的時間呢?
問題來源:
P36頁PSP相關內容:軟件工程師比大四學生多讀了3年書,多工作了3年,兩類人任務的質量要求也不一樣。我們可以看到,工程師在“需求分析”和“測試”這兩方面明顯地要花更多的時間(多60%以上);但是在具體編碼上,工程師比學生要少花1/3強的時間。顯然,從學生到職業程序員,並不是更加沒完沒了地寫程序——花在寫代碼上的時間反而少了許多。
我的思考和查證:
對於問題1,客觀原因:軟件工程師所參加的項目往往比學生所寫項目更加復雜且更少前例參考,所以軟件工程師花更多的時間在需求分析上。主觀原因:軟件工程師經過各類bug的磨練,有更強的編碼能力,所以在開發上時間更短,但在測試方面則更加嚴謹,所以花費更長的時間。(我的瞎想,哈哈哈哈哈)
對於問題2:三者的時間的分配應當依據實際項目的不同來權衡。對於結構復雜的項目在需求分析上可能需要花費更多的時間;對性能要求高的項目則可能在開發上需要 多下功夫,不斷進行優化;對容錯率較低的項目則適合在測試方面多下功夫。
2、怎么才能避免“為了自己的角色而做績效優化”?
問題來源:P321問題3
很多年前,我曾在一個軟件團隊里負責測試工作,職責之一是編寫各種測試用例,以保證系統的代碼覆蓋率達到80%以上。做過實際項目的工程師都知道,程序里的很多語句是用來處理種種異常情況的,這些情況大多都不會發生。但是若這些語句未被覆蓋的話,這個模塊的覆蓋率就會下降,我就達不到80%的目標。所以我花了很多時間構造各種奇怪的測試數據,把程序中的那些椅角昔兄都盡可能覆蓋掉。至於這些椅角春晃在實際中是否會發生,對用戶的影響如何,程序是否應該這樣設計,我都不太關心。只要覆蓋率達到80%,老子的活就干完了!
我的思考和查證:
看了作者的這個故事分享,我深有感觸。在日常學習作業中,我也總喜歡在有特定的某個參數上作巨大的努力,而忽視了某次任務的出發點。例如:每學期的《形勢與政策》論文,總是想在字數上勝人一籌,而忽視了文章的深度和簡潔度。又比如上學期的wen實踐編寫博客系統,只追求功能上的多,而不力求把求把某個功能深化做到極致。
為了避免在某個數字上迷失自我,我覺得“不忘初心”這個詞語就是個很好的方法。時刻記住自己做這件事情的出發點是什么,要求是什么,目的是什么。
此外,對於工作中將某個指標當做工作績效的行為,若有不合理之處,最好應當及時反饋,避免盲目賣力。
感謝兩位大牛老師對此問題的評論,受益匪淺,現將問題求解補充完善:
養成價值判斷習慣:做到“要事第一”,有明確的價值判斷,才能把精力放對地方,才能不忘做事初心。缺乏價值判斷習慣,往往是把做什么的決定權長期交給他人。凡事多掂量掂量,從全局優化出發來做價值判斷,避免局部過度優化反而損害了全局優化。
學會計划:列計划時應當避免計划過大,可在一段時間內將任務細化(重而知道什么事情才是最重要的,避免盲目努力),這樣可以有效的避免專注於細節而忽略整體。
3、產品經理要不要懂技術?
問題來源:P191
Product Manager :產品經理——正確地做產品。目前國內公司大部分PM都是指這個職位。產品經理對一個或多個產品或產品線負責,而互聯網產品涉及到這些方方面面:產品定位、市場發展、需求分析、運營、營銷、市場推廣、商務合作。產品經理橫跨這些部門,尋找資源,持續推進產品。隨着產品的發展,不同公司,對PM要求會不一樣。核心要求是,根據市場和用戶需求,協調各部門資源,正確地把握產品定位和方向,解決用戶的痛點,持續優化產品。
我的思考和查證:
前段時間“五彩斑斕的黑”的梗在各大論壇流行,這也揭示了程序員和“不懂技術”的產品經理之間的代溝。
但眾所周知,產品經理的主要任務是:有自己的產品思維,具備出色的判斷力,形成自己的商業分析邏輯,能夠幫公司賺錢(或具備這個意識),有獲取新用戶的意識。產品經理花時間在技術上到底值不值呢?
經過查閱資料以及和有工作經驗的程序員交流,我大致形成以下觀點:
產品經理的競爭力不在於代碼能力,並且個人精力有限,所以產品經理試圖提高代碼水平性價比並不高。
但是,懂技術的產品經理自然能夠更好地和程序員溝通,不至於提出過高的需求,或者給開發組很少的時間,導致工作量巨大(可能是996原因之一)。
所以說,技術方面只需懂個大概,知道什么框架能干啥,有啥優勢就好(和程序員大牛多交流)。
4、設計產品時是否需要考慮不直接給企業帶來利潤的用戶的體驗?
問題來源:P216頁
Stone網用戶畫像:
1.商戶:在網站上出售貨物的用戶
2.買家:在網站上購買貨物的用戶,還有越來越多的人通過手機訪問
3.瀏覽者:在網站上瀏覽、比較貨物,並不購買
我的思考和查證:
對於瀏覽者在網上瀏覽、比較貨物、但並不購買,這種不直接帶來利益,但有極大可能成為企業用戶的用戶在設計時是否需要考慮其需求?(放在Stone網中可能就是考慮未登錄能否瀏覽商品等等)
個人認為企業的最主要也是最直接的任務是賺錢,應當把重心放在最重要的位置去設計產品。當然,為了黏住潛在用戶,在不影響主要用戶體驗和公司利益的基礎上也可以考慮非主要客戶需求。
5、什么樣的軟件工程作業才叫好作業?
問題來源:
P37頁:很多老師反映軟件工程的作業題不好出,學生做的“大作業”也是了無新意,自學軟件開發的讀者往往也想不出什么有意義的題目來練習。怎么辦?師生們身處轟轟烈烈的軟件產業大環境,但是在軟件工程課上做的題目卻是非常簡陋,沒有起到應有的作用,這的確是一件很有諷刺意義的事情。
我的思考和查證:
書本中提到好的軟件工程作業應當顧及軟件工程的兩大要素(復雜性和易變性)。
對於復雜性方面考慮到不同同學有不同的水平,可在一定難度基礎上增加附加題目。
並且好的軟件工程作業最好具有實際意義,因為每次自己寫出能簡化生活的程序就會特別有成就感,真正的好作業應當師能讓學生有成就感,而不是“被迫”完成。
軟件工程發展的過程中的冷知識和故事
第一位程序員的故事
一般認為第一位程序員名叫阿達·洛芙萊斯(Ada Lovelace),是個不折不扣的女程序員,她的顏值很高,美貌過人,是英國著名詩人拜倫的女兒(白富美,豪門>貴族)。在1834年,阿達的朋友——英國數學家、發明家兼機械工程師查爾斯·巴貝其(Charles Babbage)——發明了一台分析機。阿達則致力於為該分析機編寫算法,並於1843 年>公布了世界上第一套算法。巴貝其分析機后來被認為是最早期的計算機雛形,而阿達的算法則被認為是最早的計算機程序和軟件。
為了紀念這位偉大的程序員:
美國國防制作了一個新的高級計算機編程語言——Ada,以紀念阿達·洛芙萊斯。
在微軟的Wins產品里也可以找到阿達的全息圖標簽。
英國計算機公會每年都頒發以阿達命名的軟件工程創新大獎。
任務二
作業描述
在大數據環境下,搜索引擎,電商系統,服務平台,社交軟件等,都會根據用戶的輸入來判斷最近搜索最多的詞語,從而分析當前熱點,優化自己的服務。 首先當然是統計出哪些詞語被搜索的頻率最高啦,請設計一個程序,能夠滿足一些詞頻統計的需求。
1、項目github鏈接
2、PSP表格
| PSP2.1 | Personal Software Process Stages | 預估耗時 | 實際耗時 |
| Planning | 計划 | 0.5h | 15min |
| • Estimate | • 估計這個任務需要多少時間 | 0.5h | 15min |
| Development | 開發 | 12h | 12h |
| • Analysis | • 需求分析 (包括學習新技術) | 2h | 3h |
| • Design Spec | • 生成設計文檔 | 1h | 0.5h |
| • Design Review | • 設計復審 | 0.5h | 0.5h |
| • Coding Standard | • 代碼規范 (為目前的開發制定合適的規范) | 15min | 10min |
| • Design | • 具體設計 | 2h | 1.5h |
| • Coding | • 具體編碼 | 4h | 6h |
| • Code Review | • 代碼復審 | 0.5h | 20min |
| • Test | • 測試(自我測試,修改代碼,提交修改) | 3h | 4h |
| Reporting | 報告 | 1.5h | 2.5h |
| • Test Repor | • 測試報告 | 0.5h | 1.5h |
| • Size Measurement | • 計算工作量 | 0.5h | 10min |
| • Postmortem & Process Improvement Plan | • 事后總結, 並提出過程改進計划 | 0.5h | 1h |
| 合計 | 14h | 15h |
3、解題思路
-
需求分析
- 從文件獲取輸入
- 統計字符數
- 統計單詞數
- 統計最多的10個單詞及其詞頻
- 輸出結果
-
初看需求的迷惑
First:命令行運行程序
在以往java學習沒有嘗試過cmd運行程序。不過想到以前C++課上老師特意提到過args數組的用法是用來傳遞外部參數,又想到java main函數也有args參數,由此試驗,果不其然。針對命令:java WordCount input.txt output.txt args[0] == input.txt args[1] == output.txt。
Second:怎么去分割單詞和計算有效行數
首先想到的技術是正則表達式,正則在字符匹配方面較為簡單。之后花了點時間重新復習正則表達式,確實有其可行之處(具體實現見下文)。
Third:排除非法單詞
同樣可用正則解決
Forth:單詞頻數統計
想的是用map,具體優化方法暫時未想到。
Fifth:單元測試的概念和怎么去實現
對於我來說,單元測試是全新的概念,單從字面理解的話:一個單元一個單元地測試代碼 (單元可以是一個函數)。通過查閱鄒欣老師的博客才有了進一步了解。並查閱資料得知可以借助IDEA采用JUnit框架完成測試。
4、我的代碼規范.md
5、計算模塊接口的設計與實現過程
設計兩個類,Lib和WordCount類
- Lib類中包含各種獨立的方法
- getWords
- getChars
- getLines
- getMaxCntWords
- WordCount類則是包含Main函數的類,包括程序的流程和文件讀寫處理。
-
getWords() 和 getMaxCntWord()方法的實現
- 在Lib類中設計了 handleWord() 方法用來計算字符數和統計出現頻率最高的10個單詞。
上訴兩個功能有重疊部分,故設計此函數一同處理。

-
拆分Word采用正則表達式(拆分單詞和判定是否為合法單詞一並進行,提高效率)
Pattern wordPattern = Pattern.compile("(^|[^A-Za-z0-9])([a-zA-Z]{4}[a-zA-Z0-9]*)"); Matcher matcher = wordPattern.matcher(text); //匹配 -
采用Map<String,Interge>存放單詞和出現的頻率,單詞作key,頻率作Value。
-
getLines()方法的設計
-
Pattern linePattern = Pattern.compile("(^|\n)\\s*\\S+"); //行數匹配正則表達式 lines = 0; //行數置類 Matcher matcher = linePattern.matcher(text); //匹配 while (matcher.find()) { lines++; } -
同樣采用正則表達式進行匹配
-
-
getChars()方法設計
chars = text.length();- 直接計算待處理字符串長度即可。
6、性能改進
- 讀寫文件
- 讀寫文件是選擇采用BufferReader和BufferWriter類
- 帶有緩存的讀寫類能夠減少操作系統訪問文件的次數,加快程序的運行速度(訪問文件是個慢操作,要盡量避免)。
- 對Map排序
maxCntWords = maxCntWords .entrySet() .stream() .sorted(Map.Entry.<String, Integer> comparingByValue() //按值降序 .reversed() .thenComparing(Map.Entry.comparingByKey()))//按key字典序升序 .limit(10) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));
- 采用stream流
- Stream 處理數據的過程可以類別成工廠的流水線。數據可以看做流水線上的原料,對數據的操作可以看做流水線上的工人對原料的操作。
- 第一次使用流處理Collection,受益很大。參考資料
7、單元測試和性能分析
單元測試使用的是Java的JUnit框架,IDEA對其有很好的支持。 [參考博客](https://blog.csdn.net/weixin_44425934/article/details/99858528)
7.1測試獲取字符數測試
- 用循環生成測試字符串
@org.junit.jupiter.api.Test
void getChars()
{
String oriString = "h*8i2|.\ndss";
int loopCnt = 666;
for(int i = 0; i < loopCnt; i++)
{
text += oriString;
}
-----
}
7.2測試行數測試
- 測試了三種情況:空白,正常,含空白行
@org.junit.jupiter.api.Test
void getLines()
{
text = "124\n12312\n\n\n\n3123\n\n"; //包含空白行
lib = new Lib(text);
assertEquals(lib.getLines(),3);
}
7.3獲取單詞總數測試
- 構建的字符串:hello1,hello2,hello3,123jki2,iii\n,並用循環加長。
@org.junit.jupiter.api.Test
void getWords()
{
String oriString = "hello1,hello2,hello3,123jki2,iii\n"; //包含多種非法單詞
int wordsCnt = 3;
int loopCnt = 666;
for(int i = 0; i < loopCnt; i++)
{
text += oriString;
}
-----
}
7.4獲取頻率最高十個詞測試
//普通情況
@org.junit.jupiter.api.Test
void getMaxCntWords()
{
text = "";
String strs[] = {"hello1","hello2","hello3","hello4","hello5","hello6","hello7","hello8","hello9","hello10","hello11"};
for(int i = 0; i < strs.length; i++)
{
for(int j = 0; j < 100; j++)
text += strs[i] + " ";
}
----------
}
//同頻率,按key字典序排序情況
@org.junit.jupiter.api.Test
void getMaxCntWords()
{
text = "";
String strs[] = {"hello1","hello10","hello11","hello2","hello3","hello4","hello5","hello6","hello7","hello8","hello9"};
for(int i = 0; i < 100; i++)
{
for(int j = 0; j < strs.length; j++)
text += strs[j] + " ";
}
---------
}
7.5性能測試
1、十萬字1000種可能字符(字符長度5-9)
/*構建文檔*/
BufferedWriter fileWriter = new BufferedWriter(new FileWriter("input.txt"));
Random r = new Random(1);
for(int i = 0; i < 100000; i++)
{
fileWriter.write("hello" + r.nextInt(1000) + " ");
}
fileWriter.close();
-----
用時:582ms
2、一百萬字10000種可能字符(字符長度5-10)
/*構建文檔*/
BufferedWriter fileWriter = new BufferedWriter(new FileWriter("input.txt"));
Random r = new Random(1);
for(int i = 0; i < 1000000; i++)
{
fileWriter.write("hello" + r.nextInt(10000) + " ");
if(i % 30 == 0)
fileWriter.write("\n");
}
fileWriter.close();
用時:1997ms
3、現實文章測試
characters: 463539
words: 27440
lines: 15484
output: 1764
print: 1568
string: 1568
system: 1372
null: 1176
file: 980
inputbfd: 980
inputfilereader: 980
stringbuilder: 980
args: 784
用時:639ms
7.6覆蓋率截圖

所有函數和代碼均有測試
8、異常處理說明
主要包含異常處理的部分在輸入輸出部分,計算部分未作過多處理。
-
傳入參數不等於兩個
if(args.length != 2) { System.out.print("程序只接受兩個參數"); return; //退出程序 } -
讀寫文件錯誤:try catch finally
try { ---- } catch (Exception e) { System.out.print(e.getMessage()); System.out.print("\n文件寫出錯!"); } finally { if(output != null) output.close(); }
9、心路歷程與收獲
- 熟悉了github的使用
- clone,fork,pull request,merge
- 我的關於GitHub的博客
- 版本控制好用,有時候程序寫着寫着就崩了,找bug又極其困難時候,返回上一個版本就好了。這也是老師所要求的代碼有進展就及時簽入的好處。
- 復習鞏固java知識,特別是Map的使用,以及了解了Stream在Collection中的使用和好處。
- 制訂Personal Software Process (PSP, 個人開發流程,或稱個體軟件過程),讓自己在開發時提前規划,開發起來更加順手,並且有種計划感和緊迫感,自我感知開發效率有一定的提高。
- 養成良好的代碼規范習慣十分重要,在未來編碼中會對自己提高要求。
- 單元測試:
- 以往開發中沒有重視到的點,系統地測試能夠使自己的代碼更加健壯。
- IDE自帶的測試模塊也能夠極大簡化測試過程(以前只會手工測試)。
