軟件工程 作業二


軟件工程 作業二


1.碼雲項目地址

https://gitee.com/cxf1404445126/PersonalProject-Java/tree/master


2.PSP表格

PSP2.1 個人開發流程 預估耗費時間(分鍾) 實際耗費時間(分鍾)
Planning 計划 30 40
· Estimate 明確需求和其他相關因素,估計每個階段的時間成本 30 40
Development 開發 600 680
· Analysis 需求分析 (包括學習新技術) 60 100
· Design Spec 生成設計文檔 60 50
· Design Review 設計復審 30 15
· Coding Standard 代碼規范 30 15
· Design 具體設計 100 80
· Coding 具體編碼 180 180
· Code Review 代碼復審 30 20
· Test 測試(自我測試,修改代碼,提交修改) 70 180
Reporting 報告 80 90
· 測試報告 30 30
· 計算工作量 20 20
· 並提出過程改進計划 30 40

3.解題思路描述

  1. 首先當然先選擇好語言,嗯…Java無疑了。
  2. 下一步是理清題目中需要用到編程方面的知識點。
    • 很明顯要求的重點是通過文本文件對其進行統計,那就是要考慮Java中關於文件的應用了;
    • 其次是對讀取字符進行統計,包括大小寫、單詞等要求,那想來要用到到字符串匹配判斷方面的知識點;
    • 接着統計好的單詞要進行分類存放,使用什么呢,Map?List?Set?嗯都先記下來好了,等到要使用的到了再根據具體情況決定。
  3. 大概總結完知識點,然后是開始進行類的划分,確定一下開始的方向。
  4. 最后就是根據划分出的組織順序開始代碼的編寫,至於資料,一般是等到需要用到的時候直接在網上進行查找,個人感覺這樣目的性比較明確,效率會高一些。

4.設計實現過程

一、相關類設計

  • WordDeal類,用於存放統計字符數、行數、單詞數、詞頻等方法。
  • FileDeal類作為文件處理類,用於讀取文件內容以及寫入文件。
  • Main類,調用上面兩個類的方法,實現具體功能。
  • Test類,用於進行單元測試。

二、相關函數設計

  1. FIleDeal類中的方法

    • FileToString方法實現讀取一個文件,並且返回文件內容,保存在一個字符串中。
    • WriteToFile方法實現把統計結果寫入指定文件。
  2. WordDeal類中的方法

    • getCharCount()方法實現統計字符數。
    • getLineCount()方法實現計算行數。
    • getWordCount()方法實現計算單詞數。
    • getWordFreq()方法實現統計每一個單詞的詞頻。
    • sortMap()方法實現對Map中的數據進行排序,先按詞頻數后按字典順序。
    • ListToArray()方法實現取出詞頻數前十的數據。
  3. 關鍵函數流程圖




5.代碼說明

1. getCharCount()函數

public int getCharCount() // 統計文件字符數(ascll碼(32~126),制表符,換行符,)
	{
		char c;
		for (int i = 0; i < text.length(); i++) {
			c = text.charAt(i);
			if (c >= 32 && c <= 126 || c == '\r' || c == '\n'|| c == '\t') {
				charNum++;
			}
		}
		return charNum;
	}

這個函數是用於統計字符的數量,主要實現方法就是將逐個字符進行遍歷,判斷它是否在有效字符內,若為有效字符則進行記錄。

2. getWordCount()函數

	public int getWordCount() // 統計單詞總數(單詞:以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫。)
	{
		String t = text;
		t = t.replace('\n', ' ');
		t = t.replace('\r', ' ');
		t = t.replace('\t', ' ');
		String[] spWord = t.split(" +"); // 對字符串進行分詞操作
		for (int i = 0; i < spWord.length; i++) {
			if (spWord[i].length() < 4) { // 判斷長度是否大於等於4
				continue;
			} else {
				int flag = 1; // 判斷字符串的前四位是否是英文字母
				char[] ch = spWord[i].toCharArray();
				for (int j = 0; j < 4; j++) {
					if (!(ch[j] >= 'A' && ch[j] <= 'Z' || ch[j] >= 'a' && ch[j] <= 'z')) {
						flag = 0;
					}
				}
				if (flag == 1) {
					wordCount++;

				}
			}
		}
		return wordCount;
	}

這個函數是用於判斷單詞的個數,因為文件中存在換行符制表符等分割符,所以方便分割字符,先統一將它們都替換成了空格,然后以空格為標志進行分割,接着按照單詞的要求進行單詞的判斷即可。

3. getLineCount()函數

	public int getLineCount() { // 統計有效行數

		String[] line = text.split("\r\n"); // 將每一行分開放入一個字符串數組
		for (int i = 0; i < line.length; i++) { // 找出無效行,統計有效行

			if (line[i].trim().length() == 0)
				continue;
			ValidLine = ValidLine + 1;
		}
		return ValidLine;
	}

該函數用於統計有效行數,只需要將文件的每一行進行分割,存入字符數組,然后判斷有無空行,即對應的字符去掉空格后長度是否為0,然后記錄下有效行數並返回。

4. getWordFreq()函數

	public Map getWordFreq() // 統計單詞詞頻(單詞:以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫。)
	{
		wordFreq = new HashMap<String, Integer>();
		String t = text;
		t = t.replace('\n', ' ');
		t = t.replace('\r', ' ');
		t = t.replace('\t', ' ');
		String[] spWord = t.split(" +"); // 對字符串進行分詞操作
		for (int i = 0; i < spWord.length; i++) {
			if (spWord[i].length() < 4) { // 判斷長度是否大於等於4
				continue;
			} else {
				// System.out.println(spWord[i]+" "+spWord[i].length());
				int flag = 1; // 判斷字符串的前四位是否是英文字母
				char[] ch = spWord[i].toCharArray();
				for (int j = 0; j < 4; j++) {
					if (!(ch[j] >= 'A' && ch[j] <= 'Z' || ch[j] >= 'a' && ch[j] <= 'z')) {
						flag = 0;
					}
				}
				if (flag == 1) { // 將字符串轉化為小寫
					spWord[i] = spWord[i].trim().toLowerCase();
					if (wordFreq.get(spWord[i]) == null) { // 判斷之前Map中是否出現過該字符串
						wordFreq.put(spWord[i], 1);
					} else
						wordFreq.put(spWord[i], wordFreq.get(spWord[i]) + 1);

				}
			}
		}
		return wordFreq;
	}

該函數用於單詞詞頻的統計,之前的部分先按照單詞的判斷方法進行判斷一個詞是否是單詞,然后使用一個Map進行存儲,具體方法為先判斷Map中之前是否存過該數據,若沒有存過,則將其存入並置value值為1,若存過,則將該值的value值加1。

5. sortMap()函數

	public List sortMap(Map wordCount) { // 對單詞詞頻的Map進行排序
		List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(wordCount.entrySet());
		Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {

			@Override
			public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {	//對Map中內容進行排序,先按詞頻后按字典順序
				if (o1.getValue() == o2.getValue()) {
					return o1.getKey().compareTo(o2.getKey());
				}
				return o2.getValue() - o1.getValue();
			}

		});
		return list;
	}

這個函數用於對Map詞頻進行排序,這里使用了Map類型的List來存放詞頻信息,然后通過重寫Comparator以及排序函數來實現相應要求的排序,最后返回排完序的List。


6.單元測試

這次的單元測試使用了Junit4,因為數量以及相似關系,僅展示WordDeal類中的一個函數的相關測試。

  • 這里采用的是白盒測試法進行測試,白盒法考慮的是測試用例對程序內部邏輯的覆蓋程度,要求是要盡可能的使其覆蓋程度更大一些。

  • 對於白盒法的覆蓋標准有如下幾個:

    語句覆蓋:是一個比較弱的測試標准,它的含義是:選擇足夠的測試用例,使得程序中每個語句至少都能被執行一次。它是最弱的邏輯覆蓋,效果有限,必須與其它方法交互使用。

    判定覆蓋(也稱為分支覆蓋):執行足夠的測試用例,使得程序中的每一個分支至少都通過一次。判定覆蓋只比語句覆蓋稍強一些,但實際效果表明,只是判定覆蓋,還不能保證一定能查出在判斷的條件中存在的錯誤。因此,還需要更強的邏輯覆蓋准則去檢驗判斷內部條件。

    條件覆蓋:執行足夠的測試用例,使程序中每個判斷的每個條件的每個可能取值至少執行一次;條件覆蓋深入到判定中的每個條件,但可能不能滿足判定覆蓋的要求。

    判定/條件覆蓋:執行足夠的測試用例,使得判定中每個條件取到各種可能的值,並使每個判定取到各種可能的結果。判定/條件覆蓋有缺陷。從表面上來看,它測試了所有條件的取值。但是事實並非如此。往往某些條件掩蓋了另一些條件。會遺漏某些條件取值錯誤的情況。為徹底地檢查所有條件的取值,需要將判定語句中給出的復合條件表達式進行分解,形成由多個基本判定嵌套的流程圖。這樣就可以有效地檢查所有的條件是否正確了。

    條件組合覆蓋:執行足夠的例子,使得每個判定中條件的各種可能組合都至少出現一次。這是一種相當強的覆蓋准則,可以有效地檢查各種可能的條件取值的組合是否正確。它不但可覆蓋所有條件的可能取值的組合,還可覆蓋所有判斷的可取分支,但可能有的路徑會遺漏掉。測試還不完全。

1. getCharCount()函數

根據上面的函數流程圖,設計了以下四個文件,使其盡可能的覆蓋所有的分支。
** 測試數據:**

  • text1.txt:空文件 字符數為0
  • text2.txt:純中文文件 字符數為0
  • text3.txt:純英文文件字符數為80
  • text4.txt:包含了英文、中文、回車換行符、制表符的文件字符數為73

測試代碼:

	@Test
	public void testGetCharCount() throws IOException {
		FileDeal fd = new FileDeal();
		String text1 = fd.FileToString("text/text1.txt");	
		String text2 = fd.FileToString("text/text2.txt");	
		String text3 = fd.FileToString("text/text3.txt");
		String text4 = fd.FileToString("text/text4.txt");
	
		
		WordDeal wd1 = new WordDeal(text1);
		WordDeal wd2 = new WordDeal(text2);
		WordDeal wd3 = new WordDeal(text3);
		WordDeal wd4 = new WordDeal(text4);

		int cn1 = wd1.getCharCount();
		int cn2 = wd2.getCharCount();
		int cn3 = wd3.getCharCount();
		int cn4 = wd4.getCharCount();
		
		
		assertEquals(0, cn1);
		assertEquals(0, cn2);
		assertEquals(80, cn3);
		assertEquals(73, cn4);
	
	}

各個函數的測試結果:

2. 分支覆蓋率截圖:


7.效能分析

效能分析中選用了一篇大小為2MB左右的英文小說,在測試代碼中重點測試了字符串處理類中的五個函數的運行時間。性能測試的代碼如下:

public class Perf_analy {

	FileDeal fd;
	String text1;
	WordDeal wd1;
	Map<String, Integer> wf1;
	List lis1;

	@Before
	public void setUp() throws Exception { // 初始化數據
		fd = new FileDeal();
		text1 = fd.FileToString("LittlePrince.txt");
		wd1 = new WordDeal(text1);
		wf1 = wd1.getWordFreq();
		lis1 = wd1.sortMap(wf1);

	}

	@Test
	public void testGetCharCount() throws IOException { // 測試統計字符數量函數

		long startTime = System.currentTimeMillis();
		wd1.getCharCount();
		long endTime = System.currentTimeMillis();
		System.out.println("getCharCount函數的運行時間為" + (endTime - startTime) + "毫秒");

	}

	@Test
	public void testGetWordCount() throws IOException { // 測試統計單詞數量函數
		long startTime = System.currentTimeMillis();
		wd1.getWordCount();
		long endTime = System.currentTimeMillis();
		System.out.println("getWordCount函數的運行時間為:" + (endTime - startTime) + "毫秒");

	}

	………………
	………………
	………………
}

最后運行得到的每個函數的運行時間如下圖所示:

可以看出相比於其他函數,統計單詞個數的函數的運行時間要多得多。於是查看相應函數進行效能的改進:

	public int getWordCount() // 統計單詞總數(單詞:以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫。)
	{
		String t = text;
		t = t.replace('\n', ' ');
		t = t.replace('\r', ' ');
		t = t.replace('\t', ' ');
		String[] spWord = t.split(" +"); // 對字符串進行分詞操作
		for (int i = 0; i < spWord.length; i++) {
			if (spWord[i].length() < 4) { // 判斷長度是否大於等於4
				continue;
			} else {
				int flag = 1; // 判斷字符串的前四位是否是英文字母
				char[] ch = spWord[i].toCharArray();
				for (int j = 0; j < 4; j++) {
					if (!(ch[j] >= 'A' && ch[j] <= 'Z' || ch[j] >= 'a' && ch[j] <= 'z')) {
						flag = 0;
					}
				}
				if (flag == 1) {
					wordCount++;

				}
			}
		}
		return wordCount;
	}

對於上面代碼,在改進時考慮到在分詞操作時可以使用\\s來進行空白字符的匹配,這樣就可以省去使用replace()函數所需要的時間,再一個就是在循環判斷前四個字符是否為英文時使用了效率更高charAt代替了toCharArray(),改進后的代碼如下:

	public int getWordCount() // 統計單詞總數(單詞:以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫。)
	{
		String t = text;
		String[] spWord = t.split("\\s"); // 對字符串進行分詞操作
		for (int i = 0; i < spWord.length; i++) {
			if (spWord[i].length() < 4) { // 判斷長度是否大於等於4
				continue;
			} else {
				int flag = 1; // 判斷字符串的前四位是否是英文字母
				char c;
				for (int j = 0; j < 4; j++) {
					c = spWord[i].charAt(j);
					if (!(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
						flag = 0;
					}
				}
				if (flag == 1) {
					wordCount++;
				}
			}
		}
		return wordCount;
	}

再次運行效能分析文件,結果如下,可以看出運行時間上已經有了明顯的減少。


8.心得體會

​ 這次的實驗其實暴露出了我的很多不足的地方,之前的作業我一般都是以代碼為核心,個人習慣一拿到題目就馬上開始寫代碼,但這次要求是要按照流程來進行一步步的操作。最開始的對PSP表格的估計就耗費了蠻多時間,因為之前沒有過按照規范來做的項目,所以對每一個模塊應該用多少時間在觀念上還是比較模糊,最后導致實際和預估時間相差還蠻大的。

​ 在整個作業中錯誤處理和單元測試部分是我覺得最難的部分了,在上面花費的時間也是所有部分中最多的,但是最后的完成的效果還是不太滿意,對於異常,我一向是很少會考慮,在寫代碼的時候大部分也是按照理想的輸入狀態來,所以在對異常處理的部分還是有很多不足。至於單元測試,可能對於測試樣例的設計還是不太熟悉,所以會有冗余現象。

​ 不過這次作業給我的收獲還是很大的,學到了比較規范的項目流程,發現了自己很多細節地方的欠缺,還了解到很多新知識。



免責聲明!

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



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