結對程式設計專案
軟體工程 | 這就是連結 |
---|---|
作業要求 | 這就是連結 |
作業目標 | 熟悉在未結對情況下如何結對開發專案 |
Github與合作者
合作者(學號):
- 區德明:318005422
- 虛左以待
Github連結:
https://github.com/DMingOu/CalculateExercise
由於本來的合作者臨時有事不能和我一起結對程式設計,所以我也只好咬咬牙單刀赴會了。
因為本次的題目要求有圖形化介面,而我也學過移動開發的,所以一不做二不休,直接提刀殺進AndroidStudio,開始禿頭設計一款小學生也能用的四則計算題練習工具App,一來可以簡化方便操作和展示,二來也算是在眾多.exe中裡面可以獨樹一幟。
一、PSP
PSP2.1 | Personal Software Process Stages | 預估耗時 (分鐘) | 實際耗時 (分鐘) |
---|---|---|---|
Planning | 計劃 | 25 | 38 |
· Estimate | · 估計這個任務需要多少時間 | 25 | 38 |
Development | 開發 | 945 | 830 |
· Analysis | · 需求分析 (包括學習新技術) | 75 | 130 |
· Design Spec | · 生成設計文件 | 100 | 80 |
· Design Review | · 設計複審 | 60 | 35 |
· Coding Standard | · 程式碼規範 (為目前的開發制定合適的規範) | 30 | 60 |
· Design | · 具體設計 | 120 | 110 |
· Coding | · 具體編碼 | 260 | 260 |
· Code Review | · 程式碼複審 | 60 | 45 |
· Test | · 測試 (自我測試,修改程式碼,提交修改) | 95 | 110 |
Reporting | 報告 | 120 | 105 |
· Test Repor | · 測試報告 | 60 | 50 |
· Size Measurement | · 計算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 30 | 25 |
· 合計 | 1090 | 973 |
二、四則運算練習工具APP需求分析
需求描述 | 是否實現 |
---|---|
控制生成題目的個數 | 是 |
控制題目中數值範圍 | 是 |
計算過程不能產生負數,除法的結果必須是真分數,題目不能重複,運算子不能超過3個 | 是 |
顯示題目 | 是 |
顯示答案 | 是 |
能支援一萬道題目的生成 | 是 |
判定答案中的對錯並進行數量統計 | 是 |
顯示得分結果 | 是 |
具有圖形化的操作介面 | 是 |
三、APP開發計劃
功能 | 描述 | 開發者 | 進度 |
---|---|---|---|
生成題目 | 隨機生成運算元和運算子,組成有效的四則運算表示式 | 區德明 | 完成 |
計算結果 | 根據生成的表示式,計算生成正確的結果 | 區德明 | 完成 |
練習報告 | 輸出成績結果 | 區德明 | 完成 |
UI介面設計 | 設計軟體的介面設計XML | 區德明 | 完成 |
UI介面實現 | 使用Kotlin語言實現 Andoid APP | 區德明 | 完成 |
功能測試與故障修復 | 測試程式的功能,修復出現的故障 | 區德明 | 完成 |
效能分析與優化 | 分析程式執行的效能,優化效能表現 | 區德明 | 完成 |
結構圖
四、方案
4.1 生成題目的演算法設計思路
執行緒池多執行緒併發,建立指定數量的題目,並利用Set集合進行去重操作。
核心程式碼如下:
執行生成題目的執行緒任務類
/**
* 執行生成指定數量題目任務
* 執行緒類
*/
private class GenerateWorker(
private val exerciseNum: Int, //生成數量
private val numRange: Int, //範圍上限
private val workerIndex: Int, //工作執行緒的編號
private val countDownLatch: CountDownLatch,
private val tempList : MutableList<Exercise>
) : Runnable {
override fun run() {
try {
val start = System.currentTimeMillis()
val exerciseList = generateExercises(
exerciseNum, numRange
)
//將 去重但是未編號的 列表 加入緩衝列表中
tempList.addAll(exerciseList)
countDownLatch.countDown()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
執行完善收尾操作的執行緒任務:
/**
* 用於補充題目序號, 完善,的任務
*/
private class SupplyWorker(
private val exerciseNum: Int,
private val countDownLatch: CountDownLatch,
private val tempList : MutableList<Exercise> ,
private var startIndex : Int ,
private val mHandler: QuestionListHandler //回撥UI執行緒
) : Runnable {
override fun run() {
getExerciseOnCount(exerciseNum)
countDownLatch.countDown()
//根據已有的列表資料,進行編號
for(exercise in tempList) {
startIndex++
exercise.number = startIndex
}
updateGlobalExerciseList(tempList)
//通知 View層更新內容
val message = Message()
message.what = 666
mHandler.dispatchMessage(message)
}
}
提供給外界呼叫的入口:
fun produce(exerciseNumber: Int, numberRange: Int,startIndex : Int, handler: QuestionListHandler) {
if (exerciseNumber == 0 || numberRange == 0) {
return
}
//最小範圍是2
if (numberRange < 2) {
throw RuntimeException("範圍上限不可以少於2")
}
//手工計時器啟動
start = System.currentTimeMillis()
//設定本次載入的起始序號
this.startIndex = startIndex
//每個執行緒的工作量
val workload = 25
//記錄執行緒編號
var index = 1
//剩餘工作量
var remainWorkload = exerciseNumber
//啟動建立題目的執行緒組
val threadCount = if (exerciseNumber % workload == 0)
exerciseNumber / workload
else
exerciseNumber / workload + 1
//生成執行緒+一個輸出執行緒
countDownLatch = CountDownLatch(threadCount + 1)
//任務正式啟動前。清空快取列表,避免髒資料
tempExerciseList.clear()
while (true) {
//執行生成題目的任務執行緒,編號對應
remainWorkload -= if (remainWorkload > workload) {
execute(
GenerateWorker(
workload,
numberRange,
index++,
countDownLatch,
tempExerciseList
)
)
workload
} else {
execute(
GenerateWorker(
remainWorkload,
numberRange,
index,
countDownLatch,
tempExerciseList
)
)
break
}
}
//啟動完善每一道題的任務
execute(SupplyWorker(exerciseNumber, countDownLatch ,tempExerciseList ,startIndex , handler))
//資料建立完畢,重置狀態
countDownLatch.await()
clear()
}
生成題目的操作,實際執行:
@JvmStatic
fun generateExercises(exercisesNum: Int, numRange: Int , startIndex : Int = -1): List<Exercise> {
//控制編號
var index = startIndex
//如果沒有傳入編號則視為生成時不做編號處理
val isSetIndex = index!=-1
//控制生成迴圈的結束
var count = 0
val exerciseList: MutableList<Exercise> = ArrayList()
while (count < exercisesNum) {
val exercise = generateQuestion(numRange)
generateAnswer(exercise)
//有效題目加入List
if (validate(exercise)) {
count++
if(isSetIndex) {
index++
//設定序號
exercise.number = index
}
//生成可以輸出的題目樣式
EXERCISE_QUEUE.add(exercise)
exerciseList.add(exercise)
//放入去重Set,用作判斷
exercisesSet.add(exercise.simplestFormatQuestion)
}
}
return exerciseList
}
4.2 客戶端(使用者入口)的設計思路
採用單Activity+多Fragment的架構。
- util包提供演算法和資料來源的能力
- View包承載頁面UI的顯示,
- bean包存放實體類資訊
- widget包存放的是一些自定義控制元件
五、效能分析
通過大廠滴滴的開源效能工具平臺,DoKit,接入App,進行效能的進一步分析,如下:
5.1 程式效能
使用APP的出題功能,分別生成1000道,2000道,10000道題目不同的消耗記憶體情況
- 生成1000道題目時,只需要130MB
- 生成2000道題目時,只需要155MB
- 生成10000道題目的時候,就需要1005MB了
可以認為題目的數量影響著App的執行時所消耗的資源及記憶體,並隨著題目的數量成正比例關係。
cpu消耗如下:
頁面開啟跳轉的時長:
5.2 效能優化
首輪優化
可以看出執行緒池的執行緒配置對於大資料量非常重要,從生成10000條題目為例子,在4個執行緒在併發的建立物件的時候,因為設定的單個執行緒任務量為2000,無法完成剩下的2000任務,只能等4個執行緒任務都完成後再建立新執行緒執行任務。
解決方案:調整執行緒池的配置,提高執行緒的數量,提高每個生成題目的執行緒的任務數量
優化後的結果:
執行緒池複用執行緒,分頁載入50條資料,逐步的載入出全部的資料,分散記憶體和CPU的密集消耗。
優化後的生成10000條資料,耗時從10秒提高到了7秒,提升了30%。如果繼續對執行緒池的配置進行優化,效率還會繼續提高。
進一步優化
但是對於移動應用來說,載入時間的耗時始終是大問題,讓使用者覺得等待時間過長,同時會有卡頓的問題,亟待解決,所以下面要通過效能分析工具,分析哪個步驟流程才是在生成題目列表的過程中造成卡頓呢??通過DoKit的卡頓堆疊分析圖:
可以看出卡頓 與 開啟頁面,建立對應資料,資料建立後,列表UI開始載入資料, 頻繁大量地建立View物件息息相關。
進一步解決方案:分頁載入
資料量過大的時候,其實不需要一口氣全部載入進去,可以採用分頁載入的方式載入與建立物件,吞吐量減少的同時CPU和記憶體的佔用也降低了。從另一個角度講,頁面也無必要一口氣載入上千條乃至萬條的資料 ( 螢幕就這麼大 ,使用者看不過來)
設定分頁載入題目的方式,每頁固定數量為 50 ,若所需數量不足50條,則繼續載入所需的數量題目。
開啟效能檢測平臺的監控 CPU,記憶體 ,幀率
未載入資料前:
開啟頁面,載入10000條資料:
可以看出,優化後,載入大資料,對頁面的影響小了很多,CPU和記憶體的短時間消耗急劇降低,對App這種只擁有固定記憶體(少量)的程式,可以有很不錯的效果。
六、APP真機測試報告
6.1 測試1:程式生成四則計算題和答案是否符合題目要求
結果說明:
以上為測試生成100道數值10以內的題目的截圖
可以得出結論,看到題目是符合去重的要求且答案是正確的
6.2 測試2:程式是否能正確判斷使用者輸入答案的正確性
以 6 道題目為例子,檢視練習情況報告,可以看到可以給出正確答案提示,以及完成的情況,錯對情況,得分率,很詳細。
6.3 測試3:能否支援一萬道題目的生成與顯示
在出題模式下執行生成10000道數值範圍在5以內的題目的功能
最終效果
首頁 | 題目列表 |
練習做題 | 練習情況報告 |
七、總結
專案小結
獲得的經驗:
雖說只是做了一個小工程,從中也學習到了多執行緒的相關知識,資料結構的使用和APP介面的設計與佈局。
不足的地方:
沒有做檢視歷史做題的歷史記錄。
生成大批量的題目的時候,會隨著頁面載入越來越多的題目,隨著RecyclerView持有的子itemView數量逐漸增多會導致分頁載入,RecyclerView時會有卡頓感。
結對感受
期待ing,個人感覺是各司其職,對方請教時再給建議,要結合實際情況,不過分追求效果。