前言我們以前在寫程式很喜歡講直覺,感覺對了,演算法寫下去,執行結果對了,彷彿程式就這樣結束了,但我們從沒思考過,為什麼這個函式要這樣設計、為什麼這個東西要擺在那個類別、這個方法為什麼要透過那種方式呼叫,但從今以後,我們必需慢慢思考,明白自己真正的想法究竟是什麼,明瞭地以全域去思考自己的設計合不合理,不要再只憑本能設計。
我並不是鼓勵大家拋棄本能,而是我們不該以自我為中心,因為我們必需學會跟團隊的其他人溝通,學會關心其他人,因為程式碼不單純是一個人跟一臺機器的交流,很多時後它是人與人間的想法分享跟思想交流,專業的開發人員不會因為寫完了一長串偉大的陳述和演算法而因此得意洋洋,因為他們認識到,一旦把想法攤在所有人面前,就必需為他們的團隊成員設計程式,而不僅僅是他們自己。
誠然解決問題以及成為英雄的誘惑是如此巨大,但這樣的程式碼不具備團隊價值,甚至對整個軟體系統來說,任何有這種傾向的程式碼都是垃圾,因為在這種情境下你的程式碼未來將不得不被摧毀甚至重做。
基本觀念
儘管開發系統的方式自然有其依循模式,但是討論到程式本身應具備什麼樣的特性,這個很本質的問題卻鮮為被人重視:
相較於大量的程式碼閱讀,撰寫程式碼佔有的比例跟本微不足道,這也是為什麼我會說,好的命名是維持程式碼整齊的一大步。
程式編寫沒有結束的時後,客戶的需求會改變,我們永遠有機會對程式碼進行更動。
程式是語句和概念的載體,而這同樣也是我們堅持好命名的由來。
有時後我們會希望能從細節理解程式,我們有時後也會希望從抽象概念直接理解程式,全是根據系統的全域需求去理解。
透過這些基本觀念,我們可以理解今後很多章節背後探討的東西其核心為何,為什麼這些原則會是這樣做,這一章主要是希望讀者先在腦子裡有個輪廓,藉由今後的章節慢慢讓我們更加透徹的了解這些原則的精隨所在。
四大要素
紀律:
分工明確、不濫權、減少冗員、不在其位不謀其職,要記住,所有的特性都是建立在程式碼的紀律,程式碼的紀律通常指的是......
若模組與模組間太過親密,彼此有著緊密的利益關係連結,便會開始互相包庇,意味著改動它就會讓另一個模組有所反抗,官官相護的結果會讓你受不了,親密跟包庇有一個專有名詞來稱呼,叫作不當的耦合力。
若模組與模組的相似度太高,那就是冗員,它本該只有一個模組,其它相似的模組跟本沒有存在的必要。
若模組本身干涉太多與自己職位無關的工作,其工作內容超出本身該包涵的職務,這就叫濫權,濫權也是個高危險群,若是該模組決策失當,影響將遍及系統各處,濫權有一個專有名詞來稱呼,叫作不當的內聚力。
甚至有可能概念包裝錯誤,讓模組去做與其本身概念相抵觸的工作,此為掛羊頭賣狗肉。
以上現象皆是失去紀律的徵兆,一旦他們不假所思的貪贓枉法,便會讓整個系統非常難管制,甚至無從下手。
簡單:
不走高難度邏輯技巧,不炫耀自己過人之處,拋開自己的自傲和自滿,去掉不必要的複雜性,對於一個專業工具用的得心應手的開發人員來說,他所認同的簡單對初學者反而是難的,拋開那些花俏的技巧、刪除或包裝那些不提供意義資訊的敘述,只留下有價值的一幅圖。
溝通:
對軟體來說,如果閱讀者可以很快理解程式碼,便更容易進一步改動它,在設計程式的時後,我們容易從電腦的角度去思考,但好的程式必需一面進行設計的同時,也一併考慮其它人的感受,而上述的簡單便是達成溝通的第一步。
進一步,溝通必需做到,讓程式像一首富有情趣的散文,它要有段落、押韻、節奏,它要將程式中的大起大落表現的有條有理,使之親和友善。
而且在學會溝通跟試著溝通的過程中,我們將不斷理清自己的程式碼結構,事實上這個習慣跟過程將使得我們自己腦海中對於程式的輪廓更為清晰,它幫助我們從中理解更多東西,也更大程度上幫助我們自己更好找出 BUG 的來歷,甚至更進一步的知道如何幫程式進行最少代價的最佳化,因為最佳化往往會讓程式變得複雜,本質上與溝通有衝突,但我們透過這個過程更容易取得兩者間的平衡點。
靈活:
軟體要維持良好的靈活性,就等於是維持易於應付需求這個特質。
但靈活性和簡單性在某種情況是相抵觸的,我們有時後不需要某些永遠不可能使用到的靈活性,那我們也該適時取捨不需要去讓它變得靈活,因為有的時後這種靈活性可能以提高複雜性為代價,如果這件事帶來的效益遠高於代價,才值得我們做。
當然增進軟體的溝通同樣會影響靈活性,若能快速理解、易於修改,能夠產生的變化或選擇就多。
原則
為了達成以上四大要素,我們需要知道有哪些好的原則可以指導我們達成目的,這同時也告誡我們什麼事不該做。
局部化:
從普遍的角度來看,軟體開發是一種軟體會自然而然僵化的過程,但這其實都是缺乏紀律所造成的後果,歷朝歷代的制度之所以難以改動也是因為這個因素,拿人來比喻,人需要不斷去運動,才能維持身體各關節和肌肉間的良好紀律跟協調溝通,這同時也會讓人越來越敢做運動,軟體也同樣,如果不持續重視紀律,如果不對軟體進行定期的整修跟改造,軟體會僵化,最終將走向死胡同。
而這一切都是因為我們不敢保證我們任一次的更動是否只有軟體的局部範圍受到影響,如果軟體一個改動就會讓另一頭產生蝴蝶效應,那麼改動將會變成代價高昂跟高風險的舉動,一個高風險的舉動誰敢去做?
所以說,若設計程式有考量到局部化的原則,那麼我們就可以將影響的範圍縮到最小,同時局部化的過程也可以讓軟體更具溝通性,更具紀律,不必把細節全部攤在陽光下,也不必把許多不相關的東西移到不該放置的地方,令人無所適從。
最佳化測試涵蓋率:
上面說過軟體跟人一樣,不斷運動,才能維持各關節和肌肉間的良好紀律跟協調溝通,這同時也會讓人越來越敢做運動,對軟體來說,除了達成局部性的特徵,軟體還需要透過不斷的測試來驗證自己關節跟筋骨的強健性。
我們之所以不敢修改程式碼,通常是因為我們害怕失敗和錯誤,我們害怕誤觸了某項程式將帶來不可挽回的後果。
有了高涵蓋率的單元測試,我可以更佳確信我的更動不會帶來任何不良影響,這就讓我有了大膽更動程式的勇氣。
有了這個自動除錯工具,我可以更加確知我進度的可能耗費時間。
有了這個工具,我更可以大膽重組我的程式,讓我的程式擁有更完美的結構,它驅使我們開發速度加快、它驅使我們開發更強壯的系統。
它同時也使我們更加清楚哪些修改會影響系統哪些部份,這無疑是增加軟體靈活性的關鍵因素,事實上程式碼的靈活性有一半以上是單元測試主導的,剩下的一半才是局部化。
而且測試有了高涵蓋率以後,你會更常執行這些測試、你會更常將產品程式碼調動,就像運動一樣。
根除重複:
根除重複在跟本上有助於軟體達成局部化,因為若有太多重複,我修改一個副本,同時需要顧慮分散在系統各處的 N 份副本會不會受影響。
重複程式碼是軟體裡所有問題之重,記得嗎?許多系統在設計上都要求我們儘可能消除重複,例如資料庫的正規化就是一個例子,又如物件導向程式語言中將程式碼以類別為單位做包裝,以及C++的泛型,甚至是剖面導向設計,元件導向、元程式設計,這些作為也都是為了消除重複。
重複的程式碼在一般情況下不會造成問題,但是當程式碼在接二連三的維護過程中刪刪改改,就容易造成重複的部份不一致,當你的重複程式碼有 N 份副本,基本上它出錯的機率就會是 N 倍,如果視之而不理,它將會把影響的觸手延伸系統各處,而且這種錯誤通常開發人員不會有自覺,再加上本質上就有 N 份副本,使得除錯工作更為艱困。
資料與資料相關的邏輯包裝在一起:
這是達成局部化的手段之一,而這個手段便於我們預測系統的狀態變化,物件導向剛好符合這個特性,因為在沒有物件導向的時代,變數會被程式中的各種子程序使用,而這些子程序會動到哪一些資料有的時後我很難去預測,因為這些資料有的時後曝露在過大的區域範圍,容易被其它子程序濫用,這也讓我很難了解系統的整體狀態如何變更。
如果把資料跟邏輯依一定的規矩跟標準包在一起,將有助於我了解同一時間有哪些變數做了哪種更動,而且物件只會在它的執行區域下獨立執行它的成員,這讓我更容易達成局部化,意味著我不會因為修改某處的操作影響到遠處的其它子程序,因為它們被封裝在另一個大模組裡,彼此間保持了高度的邏輯獨立性。
變化率:
也是達成局部化的手段之一,把具有相同使用調調的資料和子程序擺在一起,有助於我們把它們局部化,例如如果只在某個子程序被使用的時後該變數才被使用,那該變數就該屬於該子程序的區域變數,如果該成員與其它成員不是同一個頻率或生命週期的調調,那它極可能該移到它應有的去處。
總結
所有相關的原則和概念,會在我們之後會在之後的章節揭示,這有助於我們了解物件導向的本質,這也有助於我們了解往後的原則其背後的由來跟意圖。