教學班 | 羅傑、任建班周五3、4節 |
---|---|
gitlab項目位址 | Here it is. |
成員 | 周遠航(3004) 李辰洋(3477) |
結對項目實踐反思
實踐中出現的問題
在此次結對項目——記憶體中檔案系統的實作中,我組在兩次強測中均通過了測試,然而,在這背後,是在大大小小的問題不斷出現和被解決後産生的結果。出現的典型問題有:
下手倉促,沒有充分思考
由于本次作業的需求相對複雜,且包含多種邊界情況,是以理想的做法是做好系統的需求分析,對方法的每個分支都做好準備後再行實作。然而,有時為了追求效率,我們在了解指令的大概需求後就開始下手,導緻後期需要不斷補充對特殊情況的處理,降低了實作的效率,破壞了工作的原子性。
該問題出現的根源,其一是時間受限,其二是心急,其三是指導書的需求變動較多。
所幸由于最終修補工作做得比較全面,沒有造成失分。
測試過長
在寫單元測試時,我們以方法為機關構造了多種、多個複雜的測試用例。但是,我們存在一個排版失誤,就是将針對一個方法的所有用例集中在了一個方法中,且缺少注釋分割。這導緻後期回看和debug時造成了很大的可讀性難度。在第二次作業時,我們發現了此問題并進行了一定調整,注重了單個測試方法的長度和注釋情況。
以上是兩個典型的問題,當然我們在結對項目的需求分析、架構設計、功能實踐中的進度、品質、溝通管理等多個方面遇到了或多或少的問題,将在下文各階段展開詳述。
需求分析實踐體會
本次結對項目最難也最令人頭痛的一個地方,正是在于對該項目每階段的需求分析,也就是閱讀和分析指導書對于階段任務的相關明确要求。在每個階段任務的開始,最重要的即使先對指導書進行閱讀,了解指導書所規定的主要功能需求,以此來導向和限制我們接下來對項目架構的設計和實作。如果沒有前期對指導書的精細閱讀和深入分析,那麼在結構設計和代碼實作中就容易走彎路,甚至可能需要重構。
與此同時,在代碼的編寫過程中,我們也需要不斷對目前所實作的功能同指導書所規定的需求進行比較和檢查,以保證功能實作的正确性,而測試驅動的程式設計方法幫助我們減少了可能出現的對需求的了解偏差。
在我們結對項目的兩個階段的開發中,都在需求分析這一階段下足了功夫,“萬丈高樓平地起”,我們認為,甯願多花點時間讀懂指導書,也不要輕易開始編寫代碼,正是由于在最初的需求分析中的多一分付出,在後面的代碼編寫中,反而沒有太多需要在功能實作上出現問題而大改的地方,但确實也存在着對需求分析不夠充分而出現的一些bug,例如,在第一次作業中,編寫到最後,才發現對于
path/
這種結尾帶有
/
的路徑,我們的功能實作完全對此疏忽了考慮。
另外,還有部分bug的出現來源于指導書語義不清、存在歧義等情況,我們在對指導書的分析和代碼的編寫中,基于自己的了解,也為指導書的勘誤或更好的表述獻出了自己的一份力,例如,第一階段中關于
mkdir -p
的issue14,第二階段中關于
ln -s
異常順序的issue3,關于建立軟連結特殊情況的疑惑的issue4,關于
<dstpath>/<srcpath>
輸出路徑格式問題的issue12,關于
cp
指令是否重定向硬連接配接的issue26。并且,助教們對于issues積極的回複和切中肯綮的解答,真正讓我們感受到這門課程的與衆不同。
架構設計實踐體會
對于本次結對項目的架構設計,應該屬于其中較為成功的一個地方了。在最初的設計中,我們以Linux中“一切皆檔案”的理念為思路,并且以Google開源Jimfs檔案系統的設計架構為靈感,建構了我們最初的體系架構,即從File抽象類開始,繼承建構所有需求對象,我們利用多态的思想,統一管理以File為父類的目錄和普通檔案,在第二階段中軟硬連結檔案的加入,更是充分展現了這種設計在可擴充性上的巨大優勢。正是這種貼合Linux檔案系統設計思路,外加設計精妙的架構結構,讓我們在疊代中遊刃有餘,而這些設計,離不開精心的雕琢與前期對需求的細緻分析。
在性能優化上,第二次部落格-實作過程部分已經有所提及,我們利用JProfiler,以構造出來的極限資料為基礎,進行我們的項目性能測試和分析,在對絕對路徑的計算中,最終權衡利弊,放棄了緩存機制,改為使用Stringbuilder循環計算的方式生成絕對路徑。除了此處改動,我們在後期,又對普通檔案的content部分改用StringBuilder對象,并且針對
@n
替換問題,設計了效率更好的replaceAll方法,使得在記憶體資源占用基本不變的基礎上,針對
fappend
指令在不斷累加content的情況下,所需時間縮小為原來的1/10。
由于一開始的設計便抽象了檔案,架構較好的可擴充性讓我們有關架構設計不足導緻的問題較少,但在設計中卻是碰到相關的幾個問題:
1、在對檔案的查找和擷取方法的編寫中,最初在設計時将其置為一個專屬于MyFileSystem的私有方法,但是随着指令的增加、條件的增多以及檔案類型的多樣化,此類方法越來越多,且需要利用檔案查找的方法不僅僅隻是MyFileSystem了,是以,我們調整了設計,為其建構一個工具類并封裝起來,降低了結構的耦合性,同時提高了代碼的可複用性。
2、同樣針對這個查找檔案的工具類,由于在設計時疏忽了對Exception類型的設計,使得在不同情況下對異常的抛出常常為同一類型,這導緻了在特定情況需要捕獲某些異常,反而捕獲了其他無需處理的異常,導緻了bug的出現。
進度、品質和溝通管理實踐體會
進度管理
對于進度管理,主要通過切分任務的方式推動,對于一次作業,切分為完成基礎功能、通過弱測、完成代碼覆寫測試、完成邊界條件和特殊情況測試幾個大塊,根據作業最終結束時間和前期任務量估計來進行時間段的劃分。
對比兩個階段的個人開發流程PSP(Personal Software Process):
PSP2.1 | Personal Software Process Stages | stage_1預估耗時(分鐘) | stage_1實際耗時(分鐘 | stage_2預估耗時(分鐘) | stage_2實際耗時(分鐘 |
---|---|---|---|---|---|
Planning | 計劃 | 90 | 60 | 40 | |
Estimate | 估計這個任務需要多少時間 | 360 | 600 | 1200 | |
Development | 開發 | 270 | 300 | 500 | |
Analysis | 需求分析 (包括學習新技術) | 30 | |||
Design Spec | 生成設計文檔 | 10 | |||
Design Review | 設計複審 (和同僚稽核設計文檔) | ||||
Coding Standard | 代碼規範 (為目前的開發制定合适的規範) | ||||
Design | 具體設計 | ||||
Coding | 具體編碼 | 120 | |||
Code Review | 代碼複審 | 1000 | |||
Test | 測試(自我測試,修改代碼,送出修改) | ||||
Reporting | 報告 | ||||
Postmortem & Process Improvement Plan | 事後總結, 并提出過程改進計劃 | 20 |
可以看到,本次開發項目的進度流程有以下特點
- 計劃階段用時較少,因為架構方面隊員已經做到心中有數,達成共識。
- 預估總用時遠少于實際用時,在項目初期難以準确預估需要的時間。
- 盡管stage_2的代碼量要小于stage_1,但無論是開發時間、代碼複審環節還是測試環節,stage_2花費的時間都遠多于stage_1。這是由于第二階段提出的指令需求雖然數量較少,但是實作難度更大,而且指導書的研讀過程更加困難。另外,由于stage_2的測試難度較大,對标準答案的質疑也較多,我們也與其他組進行了題目了解上的交流(鳴謝),雖然花費了一些時間,但為我們的後期實作上了保險。
- 雖然stage_2的指令複雜,但是整體實作思路較為簡單,是以設計上第二階段并沒有花費比第一階段長的時間。
綜合來看,我組的架構設計能力和編碼能力較強,能夠做到短時間内完成完善的架構設計和主要的編碼工作。但是,我組的測試能力和對細節的把控能力相對較弱,是後期需要改進的方向。
顯然,從學生到職業程式員,并不是更加沒完沒了地寫程式—花在寫代碼上的時間反而少了許多。
——《建構之法》2.3 個人開發流程
通過建構之法中相關章節對軟體工程師和大學生編碼特點的分析,我們發現我組在項目用時上類似軟體工程師,即會在需求分析和測試上花費更多的時間。但我們所花時間發揮的真正價值和效能還有很大不足。
品質管理
對于品質管理,主要通過代碼規範和代碼複審來使得項目中的代碼可讀性高,且經過第二人審查,進而保證了高品質代碼的編寫,同時,測試驅動的代碼編寫和回歸測試也大大提高了項目的品質。
溝通管理
對于溝通管理,我們會在前中期線下見面進行需求分析、架構設計和搭建、以及主要代碼的編寫,而對中後期的完善,主要通過git倉庫同步兩人項目進度,并且依靠标注注釋來提出疑問、标示修改等,通過實踐,這不失為一種高效且精确的溝通方式。
上圖為我們在項目疊代開發中總共送出的commit數量,正是由于雙方需要不斷溝通交流、及時同步修改,因而産生了如此多的commit。
由于線下溝通依舊存在局限性,還是有着由于溝通缺失導緻雙方了解不一緻的問題。在關于mv中需要循環修改目錄所屬檔案和子目錄modify_time的功能上,由于L同學在功能完成2/3時臨時有事,說了幾句模棱兩可的話,希望剩下的功能能夠由Z同學完成,然而Z同學了解為這塊功能已經實作,于是雙方都沒有再去繼續實作剩下的功能,導緻這個bug被遺留,且在不經意間才被發現,令人虛驚一場。
結對項目實踐建議
- 規範代碼格式:在兩人程式設計時,要注重代碼格式規範,這才能讓領航者更好的了解代碼,同時,詳盡的注釋是必不可少的,除了幫助為他人解釋關于此處的功能、參數、異常、傳回值等資訊,也能夠作為一種協作中溝通交流的好方法。
- 分工明确、公平公正:結對程式設計中,最有可能發生的事情就是隻有一個人在寫代碼,而另一個人無所事事,在他人寫代碼的同時,領航員更應充分集中精神,在及時了解同伴代碼思路的同時,還需要及時思考方法實作的正确性、可行性,而領航員也沒必要一直做領航員,将一個任務分割開來,對于這個小功能,領航員實行領航職責,而對于另一個小功能,可以讓雙方職責反轉,這避免了由于雙方任務量懸殊而導緻的不公平,同時,在教學中也更好的讓雙方盡可能多的參與結對程式設計中的每個角色。
CI體驗
使用
階段設計
最初,我們在CI設定了如下三個階段:
- demo:build + 小樣運作
- test:單元測試
- submit:送出測評
後由于小樣的測試對于本次結對項目送出意義不大,且為了精簡CI流程,我們調整後僅保留兩個階段:
- test:build+單元測試
觸發條件調整
為了讓我們的開發生态更貼近真實的軟工現場,我們開辟了alpha分支,并在該分支上進行主要的開發工作,頻繁版控,并在版本達到穩定時merge到master分支。但是,由于alpha分支并不代表完善的代碼,在此分支觸發submit階段沒有意義且浪費評測資源,是以我們對submit分支進行了觸發條件限制,即添加僅master分支觸發。
私有變量使用
在學習中,我們了解到了CI添加宏定義變量的兩種方法:
- 添加到yml中,即公開變量
- 添加到項目的CI設定中,即私有變量
為了更加美觀地在CI中使用官方包,我們學習了這個分享中使用的方法,将官方包解壓密碼等涉及隐私安全的變量作為私有變量進行設定,對官方包下載下傳路徑、命名等非敏感資訊作為公開變量進行定義,通過CI實作了官方包的下載下傳、解壓、使用。
單元測試覆寫率渲染
我們使用maven的cobertura進行單元測試并使用coverage關鍵字渲染覆寫率,在README中展現結果。
感受
通過結對項目,我們體驗了CI/CD工具帶來的便捷和舒适。每次修改後觸發的CI測試可以幫助我們實時回報項目的build及unittest結果,一定程度上反應了該階段工作的有效性。另外,通過對單元測試的持續補充,test階段還能幫助我們進行回歸測試,檢視是否引入了新bug。
但是,由于本次項目的規模較小,且僅局限于課程作業,沒有部署和傳遞環節,是以對CD階段的感受較少。但或許觸發測評的過程也可以類比為CD,指把程式部署or傳遞給評測機?
另外還有一點針對gitlab-ci的感受,就是運作速度相對較慢,等待是煎熬的。
結對程式設計感想
結對方法
我們每一周期的結對基本都是以下流程:
從釋出作業到實作基本架構,我們一直采用面對面讨論和程式設計的方式,将此任務集中在一天時間内完成。
在之後的測試環節,由于戰線長、任務零碎難以集中,我們很難在同一時間線下交流,是以多采用線上交流,确定修改後領活,修改完後彙報的方式循環疊代進行。
我認為我們的結對方法在現實條件下屬于比較高效的。對于關鍵步驟,我們嚴格按照結對要求進行;對于細節。由于架構的高内聚低耦合特性,我們可以将各個功能闆塊分離,單獨實作,提高了任務的并行程度。當然,我們也遇到了一些問題。對于為了追求效率而單獨由隊友完成的闆塊,我始終存在了解程度不夠、測試不夠全面等問題,沒有那種江山盡在掌中的踏實感。
結合我的自身感受,我覺得結對的優點就在于一步一個腳印,走的很紮實,每一步都有雙重保險;缺點就是效率較低,存在一定的碼力浪費現象,而且領航員的精神集中力不好保證(我有時候會偷偷走神。在高壓快節奏的作業周期内,要想從頭結對到尾着實不易,這也讓我更深刻感受到了結對的優越性和缺陷。
評價隊友(Z同學著)
Bread
我的隊友具有很強的個人能力和廣闊的知識面,是他找到各種檔案系統源碼資料,并提出按照linux的一切皆檔案的理念來進行設計,為我們後續的架構打下了基礎,lcynb!
我的隊友對Java語言的特點了解頗多,對OO思想的認識也很深入,為後續的性能優化和壓測工作做出建設性貢獻,lcynb!
我的隊友非常細心可靠,對自己和團隊有很高的要求和期望,他能在飛機上繼續看指導書debug,也可以激發我原本已然消退的鬥志,lcynb!
Meat
我的隊友若是能在單元測試的強度上有所提高會更好。有時單元測試雖然覆寫率達到了,但是對于多種情況的覆寫率還略有欠缺,是以偶爾會有遺漏bug的情況出現。
我的隊友若是能更積極地和課程組互動會更好,比如可以嘗試自己發個issue來直接表達以下自己的看法之類的。
我和我的隊友具有共同的目标,就是把本次作業做到極緻,最終在我們的愉快配合下,取得了不錯的成果,建立了深厚的革命友誼,感謝我的隊友這倆周的辛苦付出,送上一瓶霸王以緻敬意。
最後,當然要po一張隊友的帥照,我的隊友就是最帥的lcy:
評價隊友(L同學著)
我的隊友具有很強的代碼編寫能力,寫起代碼來手速超快,一節課的時間過去,就能pull到修改了150+行的代碼,一下午的時間,基本上大半的功能實作就能夠搞定,不得不讓人大喊:zyhnb!
我的隊友是一個有責任有擔當的好夥伴,差一個功能沒實作,她沖在了前面,測試還需要完善,她又沖到了前面,前方有一波bug來襲,她還是沖在前面,不由得讓人懷疑,永動機竟是我隊友!?
我的隊友是一個優秀的bug排雷大師,在我看來已經臻于完美的代碼,她依舊能夠構造出奇妙的測試資料,找到那些隐藏的可惡臭蟲,zyh yyds!
我的隊友啊,有的時候看着你向前不斷沖刺的步伐,真的讓我充滿幹勁,我也變得利利索索,對任務一點也不敢拖沓,但這樣有時候還是趕不上你的進度,如果你能夠停下來多等等你的隊友,一起齊頭并進,那就更好啦。
但是,有一個如此充滿幹勁的隊友,真的讓人也熱血起來了,大家一起讓這個小項目變的完美、變得精妙起來,再苦再累也是開心快樂的,同樣,要感謝我的隊友在這幾次作業中的瘋狂輸出,在ddl的那一天,為了度假的我,一人救火,力挽狂瀾,現在,該去好好放風了ψ(`∇´)ψ
工具
作業相關
- gitlab:送出作業,版本控制
- gitlab-ci:CI/CD
- github:查找資料,學習架構
- issues:提出問題,和助教交流
- idea:代碼編輯器
- ubuntu18.04:檢視ubuntu處理行為
- JProfiler:對作業性能進行分析判斷
- JUnit4:建立測試單元
協作相關
- 微信:各種交流,包括但不限于工作、吐槽等
- typora:筆記、部落格共享
- 騰訊會議:遠端結對程式設計
- 共享空間咖啡杯:友情出演各種類、對象,幫助讨論架構
感悟和體會(Z同學著)
個人能力相關,我覺得本次結對作業喚醒了我塵封一年的OO技能,并逼迫我把他們發揮到極緻,甚至有所提高,新學了一些當時沒有接觸過的java特性。
結對協作相關,我熟練地掌握了git多人協作的正确姿勢,跟隊友的溝通也越來越流暢,更會表達自己的想法了 。
工程相關,我深刻的體會到了穩定的使用者需求對于項目開發的順利進展的重要影響。需求的不斷變動會使工程的開發周期被拉長,過程更加煎熬,就像一個無底洞,開發者永遠不知道是否會有新的需求出現,也摸不清目前的實作是否正确。同時,我也再次深刻體會到了,好的架構應當适應任何需求,需求的變動是牽動大局直接導緻重構,還是細節微調即可解決,很大程度取決于項目的架構底子打的好不好(圖為針對某一要求修改,我與其他組同學的交流)。
最想吐槽的和最需要改進的已經蘊含在前文中,其實問題還是挺明顯的,出題出的太趕,沒有時間好好驗題,加之題目主題本就細節零碎,造成了大家一些不太理想的體驗。但助教對于issue區的每一個提問都給予了回應和修改,能感受到課程組的辛苦和用心。
感悟和體會(L同學著)
結對程式設計,一種從未體驗過的新穎的合作方式,對我們而言,這段時間的最大的困難之一,正是在于合作,兩個人一個為駕駛員,一個為領航員,那麼我們如何能夠在保證任務能夠及時完成的情況下,既要讓不同職責下的兩人維持任務量的公平,也要讓雙方的合作盡量趨于結對程式設計的形式呢,這對于未曾合作過的兩人而言,有着巨大的考驗,你需要适應他人的程式設計習慣,需要合理的調配任務的配置設定,需要認真履行自己的責任。
很幸運,結對結到了一個認真負責的好隊友,從前期的有些生疏,在任務的不斷進行中越來越能找到協作的感覺了,合作最重要的就是交流和溝通,好的交流方式,真的讓合作程式設計變得不那麼讓人頭疼和無奈,在我與Z同學關于實作中一些細節存在分歧時,我們更多的在采用說服(persuade)的方式影響對方,如果你更有道理,那麼就按你說的來實作,不會那麼不近人情,也不會因為過于考慮對方的感受而将時間都消耗在溝通之上。同時,在合作中,大家在大體上都是平等的,但必然有一個人會更像一個leader,我認為,這種形式是合理的,且是有助于更好的進行協作,更好的向前推進任務進度的。
除了結對程式設計,順帶利用了軟工課中學習到的需求分析、架構設計、具體實作的項目流程來進行開發,深有體會,将更多的時間利用在分析項目需求和設計功能架構中,對後期代碼的編寫是有着巨大的好處的,其實在OO中已經漸漸有這種感覺,會為了一個完美的微分結構、一個精美的電梯排程模式,花很長很長時間去思考子產品與子產品間的聯系、層次。面向對象的繼承、多态、泛型,說白了就是尋找對象自身内部或其與外部世界的抽象關系,抽象,才是程式員最鋒利的武器。