目錄
- 基本資訊
- part1:閱讀《建構之法》并提問
- 一、《建構之法》
- 1.如何避免過早優化?
- 2.在實際開發中要如何權衡的軟體品質成本?
- 3.要不要使用goto?
- 4.當測試人員與開發人員産生沖突時,如何讓他們摒棄前嫌更好的協作呢?
- 5.當有兩個團隊邀請你,一個是較好的團隊但工作風格與自己差異較大,一個一般團隊但工作風格相似,應如何選擇?
- 二、附加題
- 一、《建構之法》
- part2:WordCount程式設計
- 1.Github項目位址:
- 2.PSP表格
- 3.解題思路描述
- 4.代碼規範
- 5.子產品接口的設計與實作過程。
- 6.性能改進
- 7.單元測試展示
- 8.異常處理說明
- 9.心路曆程與收獲
| 這個作業屬于哪個課程 |2021春軟體工程實踐S班(福州大學)|
---|:---|:---:
| 這個作業要求在哪裡 | 軟工實踐寒假作業(2/2) |
| 這個作業的目标| 1、重讀閱讀之法提出問題
2、熟悉git和Guihub Desktop的使用
3、編寫字元統計程式
4、掌握單元測試
5、掌握性能測試|
|作業正文| 作業正文 |
| 其他參考文獻 | 《建構之法》
github簡單教程
github fork 與pull request
關于單元測試和回歸測試
《碼出高效_阿裡巴巴java開發手冊》|
p53 原文:過早優化:既然軟體是“軟”的,那它就有很大的可塑性,可以不斷改進。放眼望去,一個複雜的軟體似乎很多子產品都可以變得更好。一個工程師在寫程式的時候,經常容易在某一個局部問題上陷進去,花大量時間對其進行優化;無視這個子產品對全局的重要性,甚至還不知道這個“全局”是怎麼樣的。這個毛病早就被歸納為“過早的優化是一切罪惡的根源”:
思考與查證:一開始剛看到過早優化的這四個字的時候,腦子想的是優化難道不是越早考慮越好嗎,當看到書中的例子以及網上查閱的資料才知道是自己想得過于簡單了。過早設計有兩個結果:一些設計根本不可能用上,另一些等到用上的時候,發現當初設計的時候考慮不全,還不能用,正所謂半吊子。是以為避免過早優化我們每次優化前可以先問自己三個問題:1,優化之後,代碼量會減少嗎?2,優化的地方是系統瓶頸嗎?3,你的優化方案,是否依賴于某些隐藏條件,或是為系統增加了限制?如果可以滿足這三個條件,就大膽去優化吧!
p314 原文:要達到一定的軟體品質,是要付出成本的。SWEBOK特别定義了軟體品質成本的組成部分,其中包括預防、評審、内部故障、外部故障這四個方面,作者認為還要加上流程分析改進、投資改進等各種成本
思考和查證:菲根堡姆和朱蘭《全面品質管理》書中指出:産品品質必須始終同成本聯系在一起.離開"成本"去談"品質"是毫無意義的.實行全面品質管理的過程中,來分析 研究品質成本,有利于控制和降低成本,有利于滿足使用者品質成本方面的要求;促進上司重視産品品質,支援品質管理工作,有利于操算最适宜的品質水準.我認為就像書中所說磨刀不誤砍柴工,想砍柴不磨刀是絕無可能的,我們能做的就是努力去提高自己學習新事物的能力和提高自身職業技能來減少“磨刀”時間。
p75 原文:函數最好有單一的出口,為了達到這一目的,可以使用goto。隻要有助于程式邏輯的清晰展現,什麼方法都可以使用,包括goto
思考和查證:我的觀點是反對的,從一開始接觸到goto老師就反複強調這個語句最好不要使用,goto語句會更改程式執行的正常順序,使程式邏輯變得非常複雜,不易閱讀而且使用goto語句使得分析和驗證程式尤其是涉及循環的程式的正确性的任務非常困難。在1968年E·W·代克斯特拉甚至提出了“GOTO語句是有害的”論點。
p147 原文:
問:那有沖突怎麼辦?
答:那就吵呗。各個角色的利益是有一定沖突的,MSF 沒有掩飾這一點。MSF 團隊模型的核心是,成功的技術項目必須符合各種利益相關人(Stake holder)完全不同且常常對立的品質觀點。
問:這麼說在團隊中有沖突是正常的了?
答:對!例如,使用者代表覺得新增加一個功能很酷,因為新功能“讓産品更好用”,但是項目經理角色覺得會影響“按限制條件内傳遞産品”的目标,測試會覺得“保證所有問題都得到處理”的目标受到威脅,用大白話說,就是“我沒有時間測試你的新功能,是以不能加這個功能”。這就要各方在整個項目的共同利益之下,協商解決,尋求多赢。
思考與查證:在一個團隊中測試人員和開發人員之間的溝通不暢容易進一步影響程式的釋出日期而當開發人員和測試人員合作時,他們能夠更好地進行交流。正确的溝通有助于確定更好地了解兩個團隊之間的需求,進而加快項目傳遞速度。查閱資料後有以下幾個方法:1.測試人員從早期開始測試,左移測試!2.随時了解彼此的活動3.確定測試團隊參與代碼審查4. QAOps:持續測試的關鍵5.自動化的單元測試。
p97 原文:軟體團隊有各種形式,适用于不同的人員和需求。基于直覺形成的團隊模式未必是最合适的。
思考:我的想法是如果選擇了較好的團隊由于工作風格的差異可能自己需要花費大量的時間去适應他們的工作風格,然而工作風格并不是短時間就能形成的,這個過程必定是漫長的。若是選擇一般的團隊,大家工作作風相似可以互相幫忙共同鼓勵互勉,可能一開始大家沒那麼優秀,但成長的這個過程必定是愉悅且充實的。畢竟現實還沒遇到這種情況,以上隻是我不成熟的想法。
有這樣一個笑話:一個旅客走進矽谷的一家寵物店,浏覽展示的寵物。這時,走進一個顧客,對店主說:"我要買一隻C猴。"
店主點了點頭,走到商店一頭的獸籠邊,抓出一隻猴,遞給顧客說:"總共5000美元。"顧客付完款,然後帶走了他的猴子。
另一位旅客非常驚訝,走到店主跟前說:“那隻猴子也太貴了!"
店主說:"那隻猴子能用C程式設計,非常快,代碼緊湊高效,是以值那麼多錢。"
這時,那位旅客看到了籠子中的另一隻猴子,它标價10000美元。于是又問:"那隻更貴了!它能做什麼?"
店主回答:"哦,那是一隻C++猴;它會面向對象的程式設計,會用Visual C++,還懂得一點Java,是非常有用的。"
旅客又逛了一會兒,發現了第三隻猴子,它獨占一個籠子,脖子上的标價是50000美元。旅客倒抽一口氣,問道:"那隻猴子比其他所有猴子加起來都貴!它究競能做什麼?"
店主說:"我們也不知道它究竟能做什麼,不過它是做項目顧問出身的。
https://bbs.csdn.net/topics/10123801
思考:這雖然是個小笑話,但是其中蘊含着許多現況。項目管理在當今社會中占有重要的地位。一個項目的成功和與否,關鍵一點就是,看項目管理是否得當。是以,項目管理是項目成功和的核心部分,是項目的靈魂,然而項目管理人才一直是“稀缺物種”,一個優秀的軟體公司必定擁有優秀的項目管理人才
Github項目位址
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 30 | 20 |
• Estimate | • 估計這個任務需要多少時間 | ||
Development | 開發 | 1225 | 1700 |
• Analysis | • 需求分析 (包括學習新技術) | 420 | 600 |
• Design Spec | • 生成設計文檔 | 10 | |
• Design Review | • 設計複審 | 15 | |
• Coding Standard | •代碼規範 (為目前的開發制定合适的規範) | 40 | |
• Design | • 具體設計 | 180 | 120 |
• Coding | • 具體編碼 | ||
• Code Review | • 代碼複審 | ||
• Test | • 測試(自我測試,修改代碼,送出修改) | ||
Reporting | 報告 | 90 | |
• Test Repor | • 測試報告 | 60 | |
• Size Measurement | • 計算工作量 | ||
• Postmortem & Process Improvement Plan | • 事後總結, 并提出過程改進計劃 | ||
合計 | 1345 | 1900 |
剛拿到題目要求時,通讀了一遍全文,發現要求編寫的幾個功能函數都是相對基礎的,但是也遇到了幾個新事物:1、git和GitHub 2、性能測試 3、單元測試
于是制定了以下幾個步驟:
1、通過檢視部落格和視訊資料學習git和GitHub的使用
2、通過Github Desktop編寫代碼并commit
3、學習如何測試性能并改進部分函數的性能
4、學習單元測試的概念和使用方法,測試代碼
5、編寫部落格,完成作業
代碼規範
整個程式包括兩個類分别是:WordCount.java和Lib.java
WordCount.java中的函數:
- WordCount
- main
- readFile
- writeFile
Lib.java中的函數:
- countChars
- countLines
- countWords
- getSortedList
關鍵函數關系:
readFile通過讀取檔案得到一個字元串并指派到countChars、countLines、countWords三個函數中,這三個函數傳回字元數、行數、單詞數到writeFile.
countWords傳回單詞數的同時也為map指派,getSortedList通過指派過的map得到一個有序的清單并傳回到writeFile.
關鍵代碼:
1、統計字元數
讀取檔案所獲得的字元串長度便是字元數。
public static int countChars(String text) {
return text.length();
}
2、統計行數
通過使用split()将換行符指定為邊界将字元串分割成字元串數組,并将空白删除。
public static int countLines(String text) {
int count = 0;
String[] textArrays = text.split("\n|\r\n");
for (String validLine : textArrays) {
if (!validLine.replaceAll("\r|\n", "").trim().equals("")) {
count++;
}
}
return count;
}
3、對map進行排序
通過将map轉化為List并通過根據指定比較器産生的順序對指定清單進行排序。
public static List<HashMap.Entry<String, Integer>> getSortedList(Map<String, Integer> map) {
List<Map.Entry<String,Integer>> lstEntry = new ArrayList<>(map.entrySet());
List<Map.Entry<String,Integer>> list = new ArrayList<>();
int count;
Collections.sort(lstEntry,((o1, o2) -> {
if (o1.getValue().equals(o2.getValue()))
return o1.getKey().compareTo(o2.getKey());
else
return o2.getValue().compareTo(o1.getValue());
}));
if (lstEntry.size() < 10) {
count = lstEntry.size();
} else {
count = 10;
}
for (int i = 0; i < count; i++)
list.add(lstEntry.get(i));
return list;
}
4.調用Lib中的函數進行指派、初始化
public WordCount(String fileName) throws IOException {
map = new HashMap<String, Integer>();
text = readFile(fileName);
countChars = Lib.countChars(text);
countWords = Lib.countWords(text, map);
countLines = Lib.countLines(text);
lstEntry = new ArrayList<>(map.entrySet());
lstEntry=Lib.getSortedList(map);
}
改進前代碼:
public static int countLines(String text) {
int count = 0;
String[] textArrays = text.split("\n|\r\n");
for (String validLine : textArrays) {
if (!validLine.replaceAll("\r|\n", "").trim().equals("")) {
count++;
}
}
return count;
}
改進前性能測試圖:

通過性能分析圖我們可以看到countLines中的replaceAll占了一大部分時間,是以要進行性能改進可以從這個方面下手,通過思考和測試後發現由于split()函數已經不存在換行符了,這時replaceAll函數是多餘的,應将其删除,提高性能。
改進後代碼:
public static int countLines(String text) {
int count = 0;
String[] textArrays = text.split("\n|\r\n");
for (String validLine : textArrays) {
if (!validLine.replaceAll("\r|\n", "").trim().equals("")) {
count++;
}
}
return count;
}
改進後性能測試圖:
1、統計字元測試
測試函數中包含了空格,水準制表符,換行符出現的情況
public class CountCharTest {
public static void main(String[] args){
String string = "he l\nl\t";
String text = "";
for(int i = 0; i < 500; i++){
text += string;
}
if (countChar.countChar(text) == 3500)
System.out.println("pass");
}
}
2、統計行數測試
測試函數中測試了出現空白行情況
public class CountLineTest {
public static void main(String[] args){
String text = "ab\n\n\nfdg3\n\n";
if (countLine.countLines(text) == 2)
System.out.println("pass");
}
}
3、統計單詞數測試
測試函數中測試了至少以4個英文字母開頭,跟上字母數字元号、不以字母開頭以及開頭字母不足4個的情況。
public class CountWordTest {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
String string = "hello,123kkk,kkk*";
String text = "";
for (int i = 0; i < 500; i++) {
text += string;
}
if (countWord.countWords(text, map) == 500)
System.out.println("pass");
}
}
4、獲得頻率最高的十個單詞測試
測試函數測試了兩種情況分别為頻率最高先輸出、頻率相同先輸出字典序靠前的單詞。
public class GetSortedListTest {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
String string = "file1,file1,file2,file3,file4,file5,file6,file7,file8,file9,filea,fileb,";
String text = "";
for (int i = 0; i < 500; i++) {
text += string;
}
countWord.countWords(text,map);
List<Map.Entry<String,Integer>> lstEntry = new ArrayList<>(map.entrySet());
lstEntry=getSortedList.getSortedList(map);
Iterator<Map.Entry<String,Integer>> iter = lstEntry.iterator();
int num = 0;
while (iter.hasNext()) {
Map.Entry<String,Integer> m = (Map.Entry<String,Integer>) iter.next();
System.out.println(m.getKey()+":"+m.getValue());
num ++;
if (num == 10)
break;
}
}
}
測試結果:
file1:1000
file2:500
file3:500
file4:500
file5:500
file6:500
file7:500
file8:500
file9:500
filea:500
覆寫率:
1、輸入參數不足兩個抛出IO異常
public static void main(String[] args) throws IOException {
if (args.length != 2)
System.out.println("wrong");
return;
}
2、檔案不存在時抛出IO異常
public static String readFile(String path) throws IOException {
BufferedReader br = null;
StringBuffer textBuffer = new StringBuffer();
String content = null;
int s;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
while ((s = br.read()) != -1)
textBuffer.append((char)s);
content = textBuffer.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
br.close();
}
return content;
}
一開始看到作業的要求心情是非常複雜的,因為出現了許多之前沒有接觸過的新事物,當看到編寫代碼的要求時,發現所要實作的代碼功能較為基礎,是以迫不及待的想要開始編寫代碼。但是,作業要求需要接觸新的工具GitHub于是上網開始查閱資料,由于資料的複雜和自己激進的心态使得自己十分浮躁。而在當我閱讀《建構之法》第十四章品質保障時,我豁然開朗。書中舉了一個例子移山公司的工程師隻有20%的時間來寫新功能的代碼,其他時間都消耗在了提高職業技能、學習新的技術等。“磨刀不誤砍柴工”,隻有不斷使自己的“刀”更加鋒利,才能做一個更優秀的“砍柴工”!