這個作業屬于哪個課程 | 2021春軟體工程實踐|W班(福州大學) |
---|---|
這個作業要求在哪裡 | 寒假作業2/2 |
這個作業的目标 | 閱讀《建構之法》,學會怎樣更好地提出問題;熟悉GitHub的運用以及git相關文法的使用;完成WordCount指令行程式 |
其他參考文獻 | CSDN |
作業正文 |
目錄:
- part1:閱讀《建構之法》并提問
- part2:WordCount程式設計
- Github項目位址
- PSP表格
- 解題思路描述
- 代碼規範制定連結
- 設計與實作過程
- 性能改進
- 單元測試
- 異常處理說明
- 心路曆程與收獲
part1:閱讀《建構之法并提問》
第二章2.1.3提到了回歸測試,說到“如果一個子產品或功能以前是正常工作的,但是在一個新的建構中除了問題,那麼這個子產品就出現了一個‘退步’”,下文又說“對于‘回歸測試’中的‘回歸’,我們可以将其了解為‘回歸到以前不正
常的狀态’”,那麼回歸測試到底是處理舊的未發現的問題,還是處理由于子產品更新而産生的新的問題?我的理
解是在新版本上重新測試已經發現的bug,然後看是否引起了問題,需要進行相應的更新修改,查閱了資料之
後發現也有部分聲音認為回歸測試指對于以前曾經測試過的内容(未出現bug)在新版中的重複性測試,是以
應該怎麼去了解?
第六章提到了靈活流程,其中提到了靈活的三個步驟,“第一步:找出完成産品需要做的事情;第二步:決定
目前的沖刺;第三步:沖刺”。從字面上了解來看,再和平時所認知的開發流程進行對比,感覺差别不是很大。
對靈活還是無法了解,靈活是不是就是要做什麼事都要及早,及早發現問題,及早送出可運作的軟體。我查
閱了相關資料,傳統的瀑布模式和靈活開發模式适用于不同程熟度的軟體公司,“軟體成熟度較好的手機軟體
開發公司,引入了PM,按照CMM流程重視軟體開發過程控制以及軟體開發技術積累,同時為了能适應手機
軟體開發需求變化比較快的特點,不采用傳統瀑布模式軟體開發,引入了靈活開發模式,在軟體實踐過程中,
引入了FDD,ASD,XP的靈活開發模式,在軟體開發過程中,強調以構架為中心,以需求為驅動的疊代開發模
式,通過構架,確定軟體的可擴充性和接口合理性,強調接口設計,友善于疊代和合作開發”。還是無法很好
地了解靈活,那照查閱的資料來看,我們團隊做普通的項目會有一些技術上的限制,是不是就無法采用靈活
流程了?
第七章7.3提到了MSF團隊模型,并且提到“在MSF團隊模型中,人和技術項目都必須達到特定的關鍵品質目标,才能夠被認為是成功的項目。任何一個角色無法實作其目标,都将危及整個項”,這個問題可能在我們後面團隊合作的項目中都會遇到,每個人的能力不同,根據每個人的所長最初配置設定好了各自的職責,但是到最後可能由
于各種各樣的原因而導緻一部分目标無法完成,那這樣是不是在最初配置設定任務的時候多強調能者多勞,但是那
樣會給少部分人配置設定更多的工作時間,而且可能會讓能力差的人産生依賴的心理,或者自身也可能會嫌棄自己
的團隊成員,而使得團隊的氣氛下降。是以遇到這種角色無法實作其所配置設定的既定目标時該如何處理?作為成
員或者PM?
- 第十章10.4提到了功能驅動設計FDD,并且提到FDD适用于團隊成員對于需求沒有切身體會的情景,例如要實作不熟悉的行業(銀行、證券、物流等)的業務系統,僅從紙面上看,FDD對單元測試之外的測試的講述不足,那麼有什麼方法能在遇到這種情況下來替代FDD呢?查閱了資料,“PDD(Product-Feature Driven Design)産品特性驅動設計,我們可以将它使用在我們的日常設計工作中,它可以運用在你的大小設計項目中,這是一種行為模式,一種思考角度,或者我們把它作為一種指導方法.它依靠産品的自身特性來驅動設計”。我可以把它了解為更大衆化的FDD,能夠在不同規模的項目設計中進行使用,那麼一些身在其中的行業的項目可能就可以換個思路,不知道我這樣了解是否妥當?
第十六章16.2出現了一張技術采用生命周期圖,到早期采用者會出現一個鴻溝,“大衆平均值再往前一步就是‘早期采用者’那個區間,有時一個嶄新的技術,推出的時機太早,它就跨不過那道溝”,如何了解?查閱了資料,“早期采用者是遠見家,他們看的遠,站的高,能夠想到新科技産品帶來的巨大影響,但這樣的人又非常少,不可
能直接和早期大衆互動。遠見家通常扮演着成了都是自己的功勞,敗了拍拍屁股走人的形象。是以和實用主義
者們并不是很協調。實用主義者選擇接受一個新高科技産品,是要考慮它的實際價值的。是以,這兩類人差異
比較大,很多東西在遠見家眼裡前途無量,在實用主義者眼裡根本就沒法落地”。按照我的了解,推出的時機太
早主要影響的是技術的可行性、适用性,而新産品一定要在解決實用的問題的前提下再進行推出工作才能夠跨
越鴻溝。這樣解決的是早期采用者與早期大衆的跨越,那如何讓更多的一部分人能夠承擔早期采用者的角色呢?
技術應該何時推出才能夠更好地把握這個點?
附加題
在程式中bug一詞用于技術錯誤。這一術語最初由愛迪生在1878年提出的,但當時并沒有流行起來。在這的幾年之後,美國上将Grace Hopper在她的日志本中,寫下了她在Mark II電腦上發現的一項bug。不過實際上,她說的真的是“蟲子”問題,因為一隻蛾子被困在電腦的繼電器中,導緻電腦的操作無法正常運作。的确這樣看來用bug來表示技術錯誤很是形象,技術錯誤和蟲子相對應,程式項目和繼電器相對應,也能更好地了解bug一詞。
我的Github項目位址
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 10 | |
·Estimate | ·估計這個任務需要多少時間 | ||
Development | 開發 | 720 | 780 |
·Analysis | ·需求分析(包括學習新技術) | 180 | |
·Desgin Spec | ·生成設計文檔 | 30 | 35 |
·Design Review | ·設計複審 | 20 | |
·Coding Standard | ·代碼規範(為目前的開發制定合适的規範) | 40 | |
·Design | ·具體設計 | 60 | |
·Coding | ·具體編碼 | 300 | 360 |
·Code Review | ·代碼複審 | 45 | |
·Test | ·測試(自我測試,修改代碼,送出修改) | ||
Reporting | 報告 | 50 | |
·Test Repor | ·測試報告 | ||
·Size Measurement | ·計算工作量 | ||
·Postmortem&Process Improvement Plan | ·事後總結,并提出過程改進計劃 | ||
合計 | 770 | 840 |
需要解決的問題:
1.解決檔案資料的讀入問題
2.解決字元數的統計
3.解決單詞數的統計
4.解決有效行的統計
5.解決出現頻率top10單詞的統計
6.解決資料輸出到指定檔案的問題
如何去解決:
1.首先第一個問題,最初的版本沒有考慮将檔案資料的讀入單獨拿出來處理,而是在每個功能的實作下都增加了從檔案讀入的代碼,就導緻總體代碼過長而且性能下降。最後考慮将其單獨,拿出使得每次運作隻需要讀取資料一次。讀取方法的考慮,選擇的是BufferedReader,起初的想法使用readline方法去讀取,然後發現難以處理一些特殊的檔案資料,如最後一行隻有單個'\n',後來的版本改成用read逐一讀入。
2.解決字元數的統計,隻需統計ASCII範圍内的字元,直接進行範圍的判斷即可。
3.單詞數的統計,先用正規表達式來分隔字元串,再用符合規則的正規表達式去比對,最後用Map<String, Integer>來存儲讀取到的單詞以及其對應的出現次數,為單詞頻率的排序提供資料。
4.有效行的統計需要忽略空行,是以用正規表達式去比對符合條件的行并統計行數。
5.top10單詞的統計需要用到第三步所得到的Map<String, Integer>對象,通過用比較器對Map.entrySet轉換的list進行排序,按照value值大小以及key的字典序進行排序。
6.資料輸出到指定檔案,用BufferedWriter将拼接好的字元串按utf-8的方式輸出到指定檔案。
codestyle.md
主要的功能函數都放在Lib類中,對上述的六個問題用六個函數來解決
1.實作檔案資料的讀入,從最初的readline讀入資料改為現在的read讀入
public static String readFromFile(String filePath) {
int temp;
//建立輸入流
BufferedReader br = null;
StringBuilder builder = null;
try {
br = new BufferedReader(new FileReader(filePath));
builder = new StringBuilder();
//按字元讀入檔案資料
while((temp = br.read()) != -1) {
builder.append((char)temp);
}
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
}catch(IOException e) {
e.printStackTrace();
}
}
return builder.toString();
}
2.字元數的判斷隻需判斷其是否在ASCII的範圍内即可
public static int getCharactersCount(String str) {
int count = 0;
char[] ch = str.toCharArray();
for(int i = 0; i < ch.length; i++) {
if(ch[i] >= 0 && ch[i] <= 127) {
count++;
}else continue;
}
return count;
}
3.單詞數的統計以用split分割字元串,再用正規表達式比對符合要求的單詞,最後把單詞存到Map中供排序使用,其中wordsMap是靜态變量不用傳入
public static int getWordsCount(String str) {
int count = 0;
//用正規表達式比對分隔符分割字元串
String[] strs = str.split(BREAK_RE);
//周遊字元串數組,比對符合正規表達式的單詞
for(int i = 0; i < strs.length; i++) {
if(strs[i].matches(WORDS_RE)) {
//忽略大小寫,添加單詞到Map中
String temp = strs[i].toLowerCase();
if(wordsMap.containsKey(temp)) {
int num = wordsMap.get(temp);
wordsMap.put(temp, 1 + num);
}
else {
wordsMap.put(temp, 1);
}
count++;
}
}
return count;
}
4.行數的統計,也是用正規表達式去比對即可
public static int getLineCount(String str) {
int count = 0;
Matcher matcher = Pattern.compile(LINE_RE).matcher(str);
while(matcher.find()) {
count++;
}
return count;
}
5.top10單詞的排序,用比較器來實作
public static List<Map.Entry<String, Integer>> sortHashmap() {
//将words.entrySet()轉換為list
List<Map.Entry<String, Integer>> list;
list = new ArrayList<Map.Entry<String, Integer>>(wordsMap.entrySet());
//通過比較器實作排序
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>(){
public int compare(Entry<String, Integer> m1, Entry<String, Integer> m2) {
//按照字典序以及value的值排序
if(m1.getValue().equals(m2.getValue())) {
return m1.getKey().compareTo(m2.getKey());
}else return m2.getValue()-m1.getValue();
}
});
return list;
}
6.用BufferedWriter将拼接的字元串傳入指定檔案
public static void writeToFile(int characters, int words, int lines, String filePath) {
//擷取将要輸出的字元串資訊
String str = "characters: " + characters + "\nwords: " + words + "\nlines: " + lines +"\n";
List<Map.Entry<String, Integer>> list = sortHashmap();
int i = 0;
for(Map.Entry<String, Integer> map : list) {
if(i < 10) {
str += map.getKey() + ": " + map.getValue() + "\n";
i++;
}else break;
}
//得到輸出流
FileOutputStream fos = null;
OutputStreamWriter writer = null;
BufferedWriter bw = null;
try {
fos = new FileOutputStream(filePath);
writer = new OutputStreamWriter(fos, "UTF-8");
bw = new BufferedWriter(writer);
bw.write(str);
bw.flush();
}catch(IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
writer.close();
bw.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
性能的改進就是用了BufferedWriter和BufferedReader,使得對檔案的讀取和寫入更快一些。另外從最初的的版本中把這兩個方法單獨拿出來而不用多次調用也會對性能有所提高。
運作10w+個單詞的性能測試,以及輸入和輸出

對字元數、行數、單詞數以及單詞的排序都進行了單元測試,并對所得值與期望值進行比較,選取的字元串也可以針對一些比較細節的問題,如空行,字母的大小等等,都可以通過修改其中的字元串進行測試,這裡隻取一部分有代表性的問題進行測試。
1.字元數的測試
@Test
void testGetCharactersCount() {
String str = "word\nfile\nFile\nwindows98\nwindows2000\n123file\r\nfil\n\n";
int loopTimes = 10000;
String testStr = "";
for(int i = 0; i < loopTimes; i++) {
testStr += str;
}
//調用Lib的字元數統計方法
assertEquals(Lib.getCharactersCount(testStr), 510000);
}
2.行數的測試
@Test
void testGetLineCount() {
String str = "word\nfile\nFile\nwindows98\nwindows2000\n123file\r\nfil\n\n";
int loopTimes = 10000;
String testStr = "";
for(int i = 0; i < loopTimes; i++) {
testStr += str;
}
//調用Lib的行統計方法
assertEquals(Lib.getLineCount(testStr), 70000);
}
3.單詞數的測試
void testGetWordsCount() {
String str = "word\nfile\nFile\nwindows98\nwindows2000\n123file\r\nfil\n\n";
int loopTimes = 10000;
String testStr = "";
for(int i = 0; i < loopTimes; i++) {
testStr += str;
}
//調用Lib的單詞統計方法
assertEquals(Lib.getWordsCount(testStr), 50000);
}
4.單詞排序的測試
@Test
void testSortHashmap() {
//測試所用的不同形式字元串,用不同的循環次數以模拟單詞出現的不同次數
/* 這裡省略testStr1的構造方式 */
String[] result = {"bean", "boot", "course", "nike", "poor", "file", "windows2000",
"windows98", "word"};
Map<String, Integer> wordsMap = new HashMap<String, Integer>();
System.out.println(Lib.getWordsCount(testStr1));
List<Map.Entry<String, Integer>> list = Lib.sortHashmap();
int i = 0;
for(Map.Entry<String, Integer> map : list) {
assertEquals(map.getKey(),result[i]);
i++;
}
}
對于兩個對檔案的操作隻是讀入和輸出,沒有對所得資料做特殊的測試,下面是測試截圖以及代碼覆寫率
覆寫率截圖,一些檔案的異常的判斷和輸出會降低檔案覆寫率,如讀入和輸出兩個函數
提高代碼覆寫率,要防止一個方法體過大,并且要盡可能的覆寫所有可能的路徑,正确的、錯誤的都要包括在考慮的範圍之内
異常情況,沒有添加自己構造的異常類,主要的異常有FileNotFoundException和IOException兩種,但運作時出現異常會輸出異常資料。
再一次熟悉了Java尤其是對檔案的操作,資料的讀入輸出等相關知識,同時在不斷地更新、調試的過程中學着用GitHub來存放和更新自己的代碼,為今後的軟體工程學習又添了一項新技能。當然,在完成作業的過程中,自己也會去查閱各種各樣的資料,也是一個不斷進步的過程,同時,在和大佬們的交流中也收獲到了許多,認識到了自己的不足。項目難在不斷的測試和優化,這是最深的體會,今後也會更加努力地完成每一個項目,盡力做到更好。