結對程式設計人員:050/184
1 結對程式設計
1.1 結對程式設計的優缺點
優點:
● 與單獨開發相比,結對能夠使人們在壓力之下保持更好的狀態。結對程式設計鼓勵雙方保持程式碼的高質量,即使在出現了讓人不得不飛快地編寫程式碼的壓力時仍然如此。
● 它能夠改善程式碼質量。程式碼的可讀性和可理解性都傾向於上升至團隊中最優秀的程式設計師的水平
● 它能夠縮短進度時間表。結對往往能夠更快地編寫程式碼,程式碼的錯誤也更少。這樣一來,專案組在專案後期花費在修正缺陷的時間會更少。
缺點:
● 對於有不同習慣的程式設計人員,可以在一起工作會產生麻煩,甚至矛盾。
● 兩個人在一起工作可能會出現工作精力不能集中的情況。程式設計師可能會交談一些與工作無關的事情,反而分散注意力,導致效率比單人更為低下。
● 可能讓某些人有濫竽充數的機會。
1.2 結對夥伴的優缺點
並肩程式設計的好夥伴~~!
*我*
優點是:偶爾比較機智,肯于思考,對演算法的設計比較有想法;
缺點是:約好的時間經常會遲到……,有時候粗心,有時候想得太多……
*我的小夥伴*
優點是:比較務實,做事不拖沓,對於程式設計任務的參與比較積極;
缺點是:有點小小的選擇恐懼症,細節再注意一點,耐心還要多一點。
總之合作還是很愉快的!兩人可以相互提醒,感覺不錯,多了一個視角。但是我有時自己想著就陷入自己了……半天回不過神。。。
2 Information Hiding, interface design, loose coupling
2.1 Information Hiding
資訊隱藏指在設計和確定模組時,使得一個模組內包含的特定資訊(過程或資料),對於不需要這些資訊的其他模組來說,是不可訪問的。
資訊隱藏是結構化設計與物件導向設計的基礎。在結構化中函式的概念和麵向物件的封裝思想都來源於資訊隱藏。軟體業對這個原則的認同也是最近十年的事情。
David Parnas在1972年最早提出資訊隱藏的觀點。他在論文"On the Criteria To Be Used in Decomposing Systems into Modules"中指出:程式碼模組應該採用定義良好的介面來封裝,這些模組的內部結構應該是程式設計師的私有財產,外部是不可見的。
(論文連結:http://www.cs.umd.edu/class/spring2003/cmsc838p/Design/criteria.pdf)
Fred Brooks在《人月神話》的20週年紀念版中承認了當時自己對Parnas的批評是錯誤的。他說道:“我確信資訊隱藏--現在常常內建於物件導向的程式設計中--是唯一提高設計水平的途徑”。
以下列舉了一些資訊隱藏原則的應用:
1. 多層設計中的層與層之間加入介面層;
2. 所有類與類之間都通過介面類訪問;
3. 類的所有資料成員都是private,所有訪問都是通過訪問函式實現的。
2.2 interface design
每一個大的系統都是有許多模組系統組成的,系統的開發是一個很大的工程,開發起來得難度也是比較大。因此任何一個有一定規模系統,通常會把系統做一定分解降低分析設計開發的難度,模組劃分是一個比較常見的方式,而模組與模組之間則是通過介面設計將它們整合在一起的。
實踐中,極有可能出現兩種狀況:介面維護失控或者過嚴而死板(而影響開發)。介面失控是因為介面的維護太過隨意,因為A模組的需要就輕易在B模組中新增一個介面(方法),導致該介面(方法)非獨立性(基本上只給模組A的這個功能點使用),或者是介面的控制過嚴,導致或者工作效率不高,或者介面的易用性不好。
一種可行的實踐是:不輕易為模組設計對外提供的介面(方法),除非是通過重構得來的;模組對外提供兩種類:一個是需要外部模組實現的介面(介面設計從本模組需要出發,當然每個介面儘管是為某個功能點服務,但也要注意其在模組內通用性),另一個是其它模組要求本模組實現的介面的實現類。即:A模組擁有一些需要B模組實現的介面(A模組對B模組的要求),而B模組中也有要求A模組實現的介面,因而A有這些介面的實現類。這種實踐方式的好處在於:模組的介面就多了一層隔離降低了耦合,把介面的通用性和介面的適應性分離,又明確了模組的邊界,使得介面在日後的優化和調整有了緩衝。
介面設計的關鍵是能夠將系統的每一個模組能夠很好的整合在一起,而且能夠讓系統能夠更好的執行。模組介面設計也是實現系統功能實現整體化的手段,而且是有益於系統拆分、整合等手段所必備的。
介面設計也有一些可查的原則,依據這些原則,我們能將程式完成得更加規範。
2.3 loose coupling
在過去常用的程式架構中,多數應用程式之間直接相互通訊。當應用程式需要修改或淘汰時,這種依賴便成為一個實際問題。任何修改都可能會按其自身的方式更新每條唯一的通訊線路。因此,這種變更可能代價高昂。這種情況被稱為應用程式間的緊耦合,也逐漸成為讓一些企業頭疼的問題。
另一方面,SOA(面向服務的體系結構) 將鬆耦合作為成功的企業級應用程式整合的一個主要原則。與緊耦合相反,鬆耦合是:
限制請求者應用程式程式碼和提供者應用程式程式碼的相互瞭解。如果耦合的服務任何方面有所變化,那麼,請求者或提供者的應用程式程式碼(更可能是兩者同時)必須改變。如果任何一方(請求者、提供者或中介基礎架構)對解耦的服務任何方面作出改變,那麼其它幾方不必隨之改變。
鬆耦合系統通常是基於訊息的系統,此時客戶端和遠端服務並不知道對方是如何實現的。客戶端和服務之間的通訊由訊息的架構支配。只要訊息符合協商的架構,則客戶端或服務的實現就可以根據需要進行更改,而不必擔心會破壞對方。鬆耦合通訊機制提供了緊耦合機制所沒有的許多優點,並且它們有助於降低客戶端和遠端服務之間的依賴性。但是,緊耦合性通常可以提供效能好處,便於在客戶端和服務之間進行更為緊密的整合(這在存在安全性和事務處理要求時,可能是必需的)。
2.4 總結
資訊隱藏、介面設計、鬆耦合都是物件導向程式設計的重要原則。在這次的程式設計中,我們也儘可能的規範變數或方法的屬性,不讓私有變數或方法洩漏,保證良好的封裝性。
對原始程式碼中一些不嚴謹的細節我們也做了修改,比如一些無故公用的方法。
3 Design by Contract, Code Contract
契約式設計或者Design by Contract (DbC)是一種設計計算機軟體的方法。這種方法要求軟體設計者為軟體元件定義正式的,精確的並且可驗證的介面,這樣,為傳統的抽象資料型別又增加了先驗條件、後驗條件和不變式。這種方法和商業契約的情況有點類似。所謂契約,也就是合約,規定兩個互動物件上的權利和責任。僱傭合同規定你的工作時數和你必須遵守的行為規則,作為公司則付你薪水,雙雙履行義務,雙雙受益。DbC的核心思想是對軟體系統中的元素之間相互合作以及“責任”與“義務”的比喻。
DbC六大原則:
原則1 區分命令和查詢。查詢返回一個結果,但不改變物件的可見性質。命令改變物件的狀態,但不返回結果。(應當是不一定返回結果)
原則2 將基本查詢同派生查詢分開。派生查詢可以用基本查詢來定義。
原則3 針對每個派生查詢,設定一個後驗條件,使用一個或多個基本查詢的結果來定義它。這樣我們只要知道基本查詢的值,也就能知道派生查詢的值。
原則4 對於每個命令都撰寫一個後驗條件,規定每個基本查詢的值。結合“用基本查詢定義派生查詢”的原則,我們現在已經能夠知道每個命令的全部可視效果。
原則5 對於每個查詢和命令,採用一個合適的先驗條件。先驗條件限定了客戶呼叫查詢和命令的時機。
原則6 撰寫不變式來定義物件的恆定特性。類是某種抽象的體現,應當將注意力集中在最重要的屬性上,以幫助讀者建立關於類抽象的正確概念模型。
DbC對於軟體工程是一個極大的理論改革,對於C/S模式造成了極大的影響和衝擊。對於C/S模式,我們看待兩個模組的地位是不平等的,我們往往要求server非常強大,可以處理一切可能的異常,而對client不聞不問,造成了client程式碼的低劣。
而在DbC中,使用者和被呼叫者地位平等,雙方必須彼此履行義務,才可以行駛權利。呼叫者必須提供正確的引數,被呼叫者必須保證正確的結果和呼叫者要求的不變性。雙方都有必須履行的義務,也有使用的權利,這樣就保證了雙方程式碼的質量,提高了軟體工程的效率和質量。
缺
點是對於程式語言有一定的要求,契約式程式設計需要一種機制來驗證契約的成立與否。而斷言顯然是最好的選擇,但是並不是所有的程式語言都有斷言機制。那麼強行
使用語言進行模仿就勢必造成程式碼的冗餘和不可讀性的提高。比如.NET4.0以前就沒有assert的概念,在4.0後全面引入了契約式程式設計的概念,使得
契約式程式設計的可用性大大提高了。此外,契約式程式設計並未被標準化,因此專案之間的定義和修改各不一樣,給程式碼造成很大混亂,這正是很少在實際中看到契約式編
程應用的原因。
在我們的程式設計中,函式間的呼叫基本運用了契約式程式設計的思想,要求傳入的引數必須滿足特定的要求。
4 Unit test
執行測試的結果:
程式碼覆蓋率:
程式碼覆蓋率只有78.67%,原因是在單元測試中需要自己編寫出所有可能的取值,才能保證覆蓋。出於時間關係,在編寫時類似的語句就沒有再進行取值覆蓋的過程。
5 UML圖
6 演算法詳解
6.1 演算法關鍵
總的來說是模擬現實中電梯的排程。
程式中有一個總排程器NaiveScheduler,它包含了一個請求佇列_PassengerQueue以及供它排程的電梯列表_Elevators。在初始化時,將4個電梯的所有資訊都載入到電梯列表裡;乘客每次產生一個方向請求,就將該請求新增至總排程器的請求佇列_PassengerQueue中。
對於4個電梯而言,每個電梯都有各自的排程器。我們為電梯類SenElevator增加了一個請求列表。每次進去一個乘客,便會產生一個目的地請求,該請求被新增至這個電梯的排程器的請求列表_allReq裡。
在每一個tick,都對總排程器的請求佇列進行遍歷,把請求新增到最佳電梯裡。具體的做法是每次迴圈都取隊頭指令,對於當前請求,依次遍歷電梯列表,尋找最佳電梯bestElev。最佳電梯的主要判斷依據是電梯的當前樓層CurrentFloor與請求的發出樓層DirectionReqSource距離最近。當然還必須滿足乘客限制、電梯容量以及可達到樓層等要求。遍歷完電梯列表後,如果能夠找到最佳電梯,則將當前請求新增至最佳電梯的請求列表_allReq裡;否則將當前請求移至請求佇列_PassengerQueue的隊尾。
此外,每一個電梯都實時更新target。具體的做法是每一個tick都遍歷請求列表_allReq,對每一個請求的型別加以判斷:如果是方向請求,則目標為發出請求的樓層DirectionReqSource;如果是目的地請求,則目標為乘客想到達的樓層DestinationReqDest。找出距離電梯的當前樓層最近的目標,即為電梯的target.
6.2 獨到之處
由每一層都停靠改為有需求才停靠,節省了大量的時間。
另外,實時地更新每一個電梯的請求列表(具體的思想是讓下一目標距離當前樓層最近)保證了可以順帶地帶上順路的乘客,也對效率有一定的提高。
後來進行優化時,我們想到可以用一個函式CheckRushHour()實現對與上班高峰和下班高峰的判斷。具體的做法是統計一段時間內向上的請求和向下的請求的比值,如果比值超過我們設定的閥值RushHourThresholdValue,則認為當前是上/下班高峰。
如果是上班高峰,我們讓所有閒置的電梯都停到它們的最低層LowestFloor;如果是下班高峰,則讓所有閒置的電梯停到它們的最高層HighestFloor。