軟件工程結隊項目
項目參與成員
計科三班 3123006072 郭濤
計科三班 3123004548 袁智燊
| 這個作業屬于哪個課程 | <https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024> |
|---|---|
| 這個作業要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477 |
| 這個作業的目標 | <實現一個自動生成小學四則運算題目的命令行程序> |
聲明:本算法實現的測試環境為python 3.11
GitHub鏈接:https://github.com/Ascrio/3123004548
PSP表格相關記錄
| PSP2 1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
|---|---|---|---|
| Planning | 計劃 | 10 | 15 |
| Estimate | 估計這個任務需要多少時間 | 270 | 330 |
| Development | 開發 | 10 | 15 |
| Analysis | 需求分析(包括學習新技術) | 40 | 30 |
| Design Spec | 生成設計文檔 | 20 | 10 |
| Design Review | 設計復審 | 15 | 30 |
| Coding Standard | 代碼規范(為目前的開發制定合適的規范) | 30 | 20 |
| Design | 具體設計 | 40 | 50 |
| Coding | 具體編碼 | 120 | 120 |
| Code Review | 代碼復審 | 5 | 10 |
| Test | 測試(自我測試,修改代碼,提交修改) | 15 | 20 |
| Reporting | 報告 | 15 | 10 |
| Test Report | 測試報告 | 15 | 15 |
| Size Measurement | 計算工作量 | 20 | 20 |
| Postmortem & Process Improvement Plan | 事后總結,并提出過程改進計劃 | 15 | 20 |
模塊接口的設計與實現過程
FractionNumber類是核心數據結構類,用來處理分數運算。提供從字符串解析和轉換為字符串的功能
Node類族用于構建和處理數學表達式的抽象語法樹。這些節點類形成了表達式樹結構,便于生成、計算和格式化數學表達式
generate_exercises(n, r)是關鍵函數,它的作用是生成n道題目并保存到文件
題目生成流程通過遞歸構建表達式樹,并用哈希機制避免重復題目;答案批改流程先比較用戶答案和正確答案,再輸出統計結果到Grade.txt文件,有模塊化、嚴格的分數機制等設計特點
函數的調用關系如下圖所示

核心函數enerate_exercises流程圖如下圖所示

代碼分析及思路說明
本代碼基于遞歸思想,設計了表達式樹結構,其核心為一共分六部分關鍵代碼,相關思路如下:
1.分數類FractionNumber:負責封裝分數運算,確保分數始終保持最簡形式,同時自動處理符號,保證分母始終為正,該類支持帶分數表示(如2'1/3)
2.表達式樹結構類BinaryOpNode:使用二叉樹表示表達式,便于遞歸求值和去重,并在求值時進行約束檢查(此約束為題目要求約束)
3.題目生成算法generate_expression:遞歸構建表達式樹,并隨機選擇運算符位置,該模塊還配備了以下處理機制:
(1).除法特殊處理:確保運算生成合理題目
(2).重試機制:當生成不符合條件的運算表達式時自動重試
4.去重函數get_structure_hash:為每個表達式生成結構哈希值,對滿足交換律的運算符進行左右子節點順序標準化,并通過哈希集合檢測重復題目
5.數字生成策略函數generate_number:利用完全隨機的選擇策略,設定20%概率生成整數,20%概率生成真分數,20%生成任意分數,20%概率生成帶分數,其余20%完全隨機生成數,并確保數值在指定范圍內,實際概率可根據需求進行修改
6.答案批改函數comare_answers:解析題目表達式并計算標準答案,并精確比較分數值而非字符串,最后生成詳細的批改報告
該算法涉及表達式樹,去重哈希等算法,具備一定高效性
模塊接口的性能改進
設計之初的代碼性能分析圖如下

后經驗證發現,該算法耗時成本很高,同時存在以下缺陷
1.最大公約數篩選函數gcd采用的基于遍歷的遞歸算法,該算法效率低下
2.generate_number采用的隨機生成算法存在隨機性過低,且該情況會隨著范圍的縮小降低(比如降至-r 1時只會生成0與1的數值),同時一些生成數值的邏輯效率過低,有大量冗余算法
經過代碼改進,將gcd函數中基于遍歷的遞歸算法改為歐幾里得算法,同時將generate_number函數加入隨機性邏輯,優化了部分選數邏輯后,效率有所提高
更正后的代碼性能分析圖如下

經比較,更正后的代碼性能更優,且能快速生成大量運算題
運行結果展示
1.生成題目
輸入python math_generator.py -n <數值a> -r <數值b>,系統會生成a道范圍不大于b的運算題Exercises.txt和對應的答案Answers.txt

具體內容請參考測試運行展示部分
2.批改題目
輸入python math_generator.py -e <exercise.txt> -a <answer.txt>后,系統會判斷指定的答案文件相對于練習文件的對錯

同時,會將結果存入Grade.txt中

測試運行展示
考慮篇幅限制,此處僅展示10類測試用例
1.基本功能-運算測試
命令行輸入python math_generator.py -n 5 -r 10生成5個數值范圍不大于10的運算題目,生成對應的兩個txt
Exercises.txt內容如下

Answers.txt內容如下

經測試,答案正確且運行結果符合預期
2.參數缺失測試
只輸入-n 5,此時命令行會發生報錯

經測試,答案正確且運行結果符合預期
3.大量數據生成測試
命令行輸入python math_generator.py -n 10000 -r 10生成10000個數值范圍不大于10的運算題目,生成對應的兩個txt
Exercises.txt內容如下

Answers.txt內容如下

經測試,生成了10000道運算題,答案正確且運行結果符合預期
4.參數錯誤測試
命令行輸入python math_generator.py -n 10 -r abc檢測系統是否報錯
輸入后系統發生報錯,內容如下

經測試,結果符合預期
5.異常數量測試
命令行輸入python math_generator.py -n 10 -r -1檢測系統是否報錯
輸入后,系統發生報錯,內容如下

經測試,結果符合預期
6.分數生成隨機性測試
為驗證分數生成的隨機性,命令行輸入python math_generator.py -n 10 -r 1檢測生成分數情況
得到Exercises.txt文件,文件內容如下

可見分數生成隨機性很高,驗證測試成功
7.基本功能-批改測試
命令行輸入python math_generator.py -e Exercises.txt -a Answers.txt,系統會生成批改結果
此處為驗證預期結果,將部分題目對應的答案故意修改至錯誤,再次運行后,系統會生成批改結果,其中包括了答案正確的和答案錯誤的批改情況說明

同時,會將結果存入Grade.txt中

經過測試,批改功能結果符合預期
8.空文本測試
將其中的Exercises.txt改成空的文本,并在命令行輸入python math_generator.py -e Exercises.txt -a Answers.txt,系統會發生報錯

經過測試,結果符合預期
9.錯誤文本測試
故意修改Exercises.txt中的運算符號

修改后運行,系統會批改除了有異常符號的題目的其余部分題目,并在出問題的題目處給予報錯信息

經過測試,結果符合預期
10.文件不存在測試
刪除Answers.txt文件,并運行python math_generator.py -e Exercises.txt -a Answers.txt,系統會發生報錯,內容如下

經過測試,結果符合預期
模塊部分異常處理說明
異常處理一:規則檢查處理
目標: 檢查數學運算中不符合題目規則要求的情況
對應代碼片段如下:
def __truediv__(self, other: 'FractionNumber') -> 'FractionNumber': # 除零異常檢查
if other.numerator == 0: # 檢查是否存在除數為零的情況
raise ZeroDivisionError("Cannot divide by zero.")
new_num = self.numerator * other.denominator
new_den = self.denominator * other.numerator
return FractionNumber(new_num, new_den)
def __sub__(self, other: 'FractionNumber') -> 'FractionNumber': # 減法結果負值異常檢查
new_num = self.numerator * other.denominator - other.numerator * self.denominator
new_den = self.denominator * other.denominator
return FractionNumber(new_num, new_den)
elif self.op == '-': # 檢查是否存在e1 < e2的情況
if l.value() < r.value():
raise ValueError("Subtraction would result in negative.")
return l - r
def is_proper_fraction(fraction: FractionNumber) -> bool: # 真分數約束檢查
return abs(fraction.numerator) < fraction.denominator and fraction.denominator > 1
if op == '/': # 在除法運算后的驗證
res = l / r # 嚴格檢查除法結果是否為真分數
if not is_proper_fraction(res):
raise ValueError("Division result must be a proper fraction...")
異常處理二:表達式解析異常處理
目標:處理用戶輸入表達式格式錯誤
對應代碼片段如下:
def evaluate_expression(expression: str) -> FractionNumber:
expr_processed = preprocess_expression(expression)
try:
result = eval(expr_processed)
except Exception as e:
raise ValueError(f"表達式求值失敗:{e}")
if isinstance(result, Fraction): # 類型檢查
return FractionNumber(int(result.numerator), int(result.denominator))
elif isinstance(result, int):
return FractionNumber(result, 1)
else:
raise ValueError(f"無法識別的計算結果類型:{type(result)}")
異常處理三:文件操作異常處理
目標:確保文件讀寫操作的可靠性
對應代碼片段如下:
def compare_answers(exercise_file: str, answer_file: str):
try:
with open(exercise_file, 'r', encoding='utf-8') as f:
exercises = f.readlines()
with open(answer_file, 'r', encoding='utf-8') as f:
answers = f.readlines()
if len(exercises) != len(answers):
print(f"? 錯誤:題目數量({len(exercises)}) 與答案數量({len(answers)}) 不相等。")
return
except Exception as e:
print(f"批改時發生錯誤:{e}")
異常處理四:題目生成重試機制
目標:處理生成過程中的臨時錯誤,確保最終輸出
對應代碼片段如下:
def generate_expression(r: int, max_ops: int = MAX_OPERATORS) -> Tuple[str, FractionNumber, str]:
num_tries = 0
max_tries = 1000
while num_tries < max_tries:
num_tries += 1
try: # 生成邏輯
return expr_str, val, structure_hash
except Exception as e:
continue # 靜默重試
raise RuntimeError("Failed to generate valid expression after many tries.")
異常處理五:參數驗證異常
目標:確保輸入參數的合法性
對應代碼片段如下:
def main(): # 參數解析
if r < 1:
print("? 錯誤:-r 后的數值范圍必須 >= 1,例如:-r 10")
return
except ValueError:
print("? 錯誤:-n 后必須跟一個整數,表示題目數量。例如:-n 10")
return
項目總結
通過此次結對項目,通過結對編程,代碼質量明顯提升,減少了很多低級錯誤,同伴在算法設計方面的經驗幫助我更好地理解了表達式樹的構建和比較,諸如在解決題目去重問題時,我們共同討論的多種方案最終找到了最優解,同時通過合作,我們共同攻克了分數運算和表達式生成的難點,在這次合作中,同伴在代碼規范和模塊化設計方面給了我很多啟發,為我后續開發其他項目奠定基礎
通過這次結對編程項目,我們不僅成功實現了需求規格中的所有功能,還在合作過程中相互學習、共同成長。結對編程模式顯著提高了代碼質量和開發效率,減少了后期調試的時間。我們也總結出不少彼此的閃光點:袁智燊同學的閃光點是善于編碼,且有豐富的調試和性能分析經驗。建議以后也可以把代碼編寫和調試的任務再進一步拆分。郭濤同學的閃光點是能夠從整體架構角度思考問題,避免陷入細節陷阱,同時在調試復雜邏輯時表現出極大的耐心和細致
同時在此次項目中,我們也意識到我們在邏輯嚴謹性上仍舊存在進步空間,同時對代碼的問題查找效率略慢,在此后的項目設計中仍需改進,但是總體而言,這次項目設計相當成功,所有的輸出均符合期望結果
浙公網安備 33010602011771號