摘要:在代碼整潔之道,提出一種軟件質量,可持續開發不僅在于項目架構設計,還與代碼質量密切相關,代碼的整潔度和質量成正比,一份整潔的代碼在質量上是可靠的,為團隊開發,后期維護,重構奠定了良好的基礎。
現在的軟件系統開發難度主要在于其復雜度和規模,客戶需求也不再像Winston Royce瀑布模型期望那樣在系統編碼前完成所有的設計滿足用戶軟件需求。在這個信息爆炸技術日新月異的時代,需求總是在不停的變化,隨之在2001年業界17位大牛聚集在美國猶他州的滑雪勝地雪鳥(Snowbird)雪場,提出了“Agile”(敏捷)軟件開發價值觀,并在他們的努力推動下,開始在業界流行起來。在《代碼整潔之道》(Clean Code),提出一種軟件質量,可持續開發不僅在于項目架構設計,還與代碼質量密切相關,代碼的整潔度和質量成正比,一份整潔的代碼在質量上是可靠的,為團隊開發,后期維護,重構奠定了良好的基礎。在這本書中作者提出了注重實際開發實踐的細節,而不是站在空洞的理論來談論整潔之道。
什么是整潔代碼?不同的人會站在不同的角度闡述不同的說法。而我最喜歡的是Grady Booch(《面向對象分析與設計》作者)闡述:
“整潔的代碼簡單直接。整潔的代碼如同優美的散文。整潔的代碼從不隱藏設計者的意圖,充滿了干凈利落的抽象和直截了當的控制語句。”
整潔的代碼就是一種簡約(簡單而不過于太簡單)的設計,閱讀代碼的人能很清晰的明白這里在干什么,而不是隱澀難懂,整潔的代碼讀起來讓人感覺到就像閱讀散文-藝術的沉淀,作者是精心在意締造出來。
一:命名
命名包括變量、函數、參數,類等,一個好的命名能夠很好的表述其所承載的業務,從命名上就已經很好的答復了為什么存在,做了什么事,應該怎么用等的大部分的問題,閱讀者看到它的時候不必去深究其實現細節,一切都在命名上一目了然。一個好的命名必須是名副其實,不存在歧義(雙關語或常見屬于沖突),直接了當(否定語句或者誤導性命名)。
二:函數:
從匯編/C時代開始的到現在函數一直都存在與我們開發中不可或缺的一部分,結構化組織,重用.作為函數式語言的一等公民,所有程序的第一組代碼。
好的函數必須足夠的小,其次還是足夠的小。很容易想像閱讀上千行的代碼,是多么巨大的自我心理挑戰,在實習的時候工作于毫無分層邏輯的WinForm平臺下,完全依賴RAD模式帶來后置cs頁面上千行的代碼,每次修改都令我惱怒,恨不得重寫整個業務邏輯。
一個函數在于短小精悍,只作一件事情,并做好這件事,只做一件事才能得到更好的利用函數名表述自己。
好的函數還應該是CQS(查詢命令分離)無副作用的(不存在隱藏歧義的背后邏輯),并對其他類型不存在“依戀情節(Feature Envy)“(類中的變量被所有的函數使用這是理想的高內聚,萬物皆有其位,而后物盡歸其位)。
函數的參數應該足夠的少,無最好,一次之,再次為二,盡量避免三個以及三個以上,對于太多的參數你可能該采用IntroduceParameterObject(引入參數對象)。
重復的代碼。重復在軟件系統是萬惡的,我們熟悉的分離關注點,面向對象,設計原則…都是為了減少重復提高重用,Don’t repeat yourself!(DRY)。
三:注釋、格式:
并不是寫出完備的注釋就是好的開發人員,如果代碼清晰的表述自己意圖,那么注釋反而多余。在《重構-改善現有代碼之道》中Martin Fowler指出多余的注釋是一種代碼壞味道。就是好的注釋隨著項目的維護不斷的重構很多時候也會變得不那么適應,而我們很少會去主動維護。再則誤導性的注釋更為使用者所憎恨。當然有時我們也得使用注釋,注釋并不是萬惡的,當我們沒法用代碼來描述自己的時候,我們需要注釋去描述意圖;多余有副作用的代碼給使用者提供警告注釋。TODO開發時進度控制,比如你在進行較大規模領域重構,目前有些邏輯不再適應,不那么自然,而對它的重構還在任務列表最后,你可以選擇標注在TODO中,最后完成從ToDoList中去掉每一個TODO任務。
良好的代碼格式,會使得我們閱讀更容易,一套共同的格式會讓我們查找理解更快速。每個團隊都應該遵循一套固定的代碼格式規范,整個軟件系統的統風格統一,而不是各自為政各成一體。
四:對象和數據結構:
數據結構指的就是數據的載體,暴露數據,而幾乎沒有有意義的行為的貧血類。最常見的應用在分布式服務,以wcf,webservice,reset之類的分布式服務中不可或缺的數據傳輸對象(DTO)模式,DTO(Request/Response)就是一個很典型的數據載體,只存在簡單的get,set屬性,并且更傾向于作為值對象存在。而對象則剛好相反作為面向對象的產物,必須封裝隱藏數據,而暴露出行為接口,DDD中領域模型傾向于對象不僅在數據更多暴露行為操作自己或者關聯狀態。
數據結構和對象之間看是細微的差別卻導致了不同的本質區別:使用數據結構的代碼便于在不改動現在數據結構的前提下添加新的行為(函數),面向對象代碼則便于不改動現 有函數的前提下添加新的類。換句話說就是數據結構難以添加新的的數據類型,因為需要改動所有函數,面向對象的代碼則難以添加新的函數,因為需要修改所有的類。在任何一個復雜的系統都會同時存在數據結構和對象,我們需要判斷的是我們需要的是需要添加的新的數據類型還是新的行為函數。
隱藏作為面向對象主要特性中的最重要特性,封裝隱藏是面向對象中最重要的特性,一個好的面向對象代碼肯定是對對象的內部細節做到很好的隱藏封裝,封裝過后才有是多態,委派之類的。一個好的面向對象的代碼一定是具有很好的隱藏封裝,易于測試,不穩定因素往往集中在一處很小或者固定的位置,不穩定因素的變更不會導致更大面積的修改擴散。
對象的隱藏要求:方法不應和任何調用方法返回的對象操作,換句話之和朋友說話,不和陌生人說話(迪米特法則,或被譯為最小知識原則),比如:ctxt.getOptions().getSearchDir().getAbsolutePath(),就是迪米特法則的反例模式。
五:異常處理:
每個軟件系統都避不開異常處理,需要防止它搞亂我們的邏輯。
利用異常處理代替返回異常編碼,返回異常編碼會是的代碼中充滿了if/else,switch/case擾亂我的代碼流轉。
對于特定異常撲捉,可以面向異常編程,編寫特定的異常類,使得對異常封裝轉化,更容易捕善后獲處理。
避免返回null,在軟件系統中最常見頭疼的就是NullReferenceException。在非特定場景下,我們應該極力的避免返回null。面對這種場景我們可以采用null object Pattern(空對象模式)返回特例對象,如c#類庫中的Guid.Empty,string.Empty;對于集合類型我們可以返回長度0的空集合而非null;
六:邊界:
在系統開發中不可能一切都得從零開始,自己寫所有的代碼,更好的方案是需要整合一些開源或者第三方的項目,為我所用。但是不能讓這些非自己的代碼滲侵中我們的代碼各處,有一些所以功能很強大的第三方產品,但不一定具有很好的抽象。很多時候我更寧愿花些時間抽象出我們自己所需要的接口在第三方類庫上外覆一層自己的抽象,這樣不僅便于TDD,因為我們能夠很好的創建偽對象,使的測試獨立不依賴外部資源,得到快速反饋;而且在設計上得到很好的擴展,當由于某些原因如第三方類庫不再能滿足業務需求,或者權益收費等等,我們可以很好的切換底層而使得修改不會擴散到系統各處。外覆類也是處理遺留代碼帶入測試容器的一種很好實踐。
七:單元測試:
TDD中測試代碼在往往和產品代碼差不多,在系統中占據一半的代碼量,不好的測試代碼也可能拖累項目的開發。整潔的測試代碼應該是遵循first原則的:
快速(Fast):測試應該快速,因為需要不斷的運行測試得到反饋,我們需要的快速反饋,錯誤的快速定位。所以你的測試就不能依賴太多的外部資源,數據庫,硬件環境等等,對于這些外部資源應該采用偽對象模式來隔離。
獨立(Independent):測試應該是獨立的,獨立于測試用例之間,獨立于特定的環境,獨立于測試的運行順利。數據的獨立通常采用兩種獨立方式,每個測試環境的獨立,很多時候我們希望每個測試運行完成后環境(如數據庫)和運行前保持一致,如數據庫高層次測試我們更希望在每次測試完成后不會帶來多余或者改變數據。再則就是數據的隔離,我們的行為測試(BDD,集成高角度的測試)都會依賴一些固定的信息,通常是登陸系統的人員,我們可以采用么個測試建立一個不同的登陸人員來使的每個測試之間的s數據隔離。
可重復(Repeatable):測試應該可以在任何環境下可重復,可運行,因為測試獨立于環境外部資源。
自足驗證(Self-Validation):測試應該有通過失敗的標示,從每一個測試上能得到一處代碼邏輯的通過失敗。每個測試都有對同一件事物的一種行為的斷言,也之斷言一件事,從而能夠很好的錯誤定位,避免高技巧性的測試。
及時(Timely):測試應該是及時編寫的,TDD要求測試必須在實現代碼之前,提前以使用者的角度定義使用接口方式。如果你是在編碼后補測試,你的測試覆蓋很可能不夠,而且容易定式于實現的邏輯寫測試,很多時候對于較低層次的測試也不是那么容易寫的。一個設計良好的代碼必須也是可測試的。
八:類:
面向對象的相似行為的抽象,函數代碼塊的組織形式,在面向對象中我們的軟件系統是由眾多的類和類之間的交互協作完成了。面向對象特征:封裝,繼承,多態度,委派。一個設計良好的類該是具有良好的封裝,站在使用者的調度考慮那些是使用接口,那些是內部細節;這是面向對象最主要的特征,但是有時會與測試沖突,可以適當的放開并僅限于于測試調用。繼承和多態在面向對象中可以實現重用,但我更傾向于繼承不是為了重用,而是隔離變化;大量的濫用繼承不干凈的繼承體系將會導致龐大的繼承體系,繼承體系中眾多職責重復在各個同級派生類,理想的繼承應該是滿足里氏替換原則(LSP:每個父類出現的地方都應該可以被派生類所替換,并且能正確的工作);面oo第二原則組合優先。而委派則是一個類把部分功能委派給其他類來完成,體現類之間的協作,類似組合。
類第一原則應是是小并足夠的小。但與函數不同的是函數以代碼行數統計,而類以權責統計。
單一原則(SRP),體現了類只應該做一件事,并且做好它,這樣變化修改的理由只有他所做的事。良好的軟件設計中系統是由一組大量的短小的類和他們之間功能協作完成的,而不是幾個上帝類。
內聚:高內聚低耦合:提出與結構化編程,內聚表述模塊內部功能不同操作邏輯之間的距離,如果一個類的每個變量都被每個方法所使用為最大的內聚;耦合描述模塊之間的依賴程度;高內聚低耦合以簡單的方式表述就是功能完備(高內聚)對象之間是通過穩定的接口(低耦合)交互的。
依賴倒置(DIP):描述組件之間高層組件不應該依賴于底層組件。依賴倒置是指實現和接口倒置,采用自頂向下的方式關注所需的底層組件接口,而不是其實現。DI模式很好的就是應用IOC(控制反轉)框架,構造方式分為分構造注入,函數注入,屬性注入;.net平臺流行的IOC框架有Unity,Castle windsor,Ninject,Autofac等框架支持,
九:并發編程:
并發是一種時間(When)和目的(What)的解耦,提供應用程序的吞吐量,提高cpu利用率;但是并發編碼不是那么容易,再加上臨界資源競爭死鎖。在并發編程的時候我們必須盡量遵守一些原則:
并發已經足夠復雜,我們更需要代碼分離,分離線程相關代碼和非線程相關代碼,單一權責,盡可能降低其復雜度。
限制臨街資源的作用域,為臨界資源加鎖是防止并發的策略,但是必須正確的加鎖,如果形成等待環,就導致死鎖。
利用數據副本(值對象或者克隆)在線程之間傳遞數據,避免線程之前操作的并發影響;線程獨立,使其在自己的環境中運行,不能其他線程共享數據。
對于臨界資源加鎖應盡量保持加鎖范圍盡可能的小。
更多關于簡單設計,迭進,逐步編程代碼,壞味道,并發示例請參見代碼整潔之道。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/64119.html
摘要:我們這里再介紹一下,朱重八家族的名字,都很有特點。取這樣的名字不是因為朱家是搞數學的,而是因為在元朝,老百姓如果不能上學和當官就沒有名字,只能以父母年齡相加或者出生的日期命名。所以說命名不僅僅是一種科學,更是一種藝術。 在小朱元璋出生一個月后,父母為他取了一個名字(元時慣例):朱重八,這個名字也可以叫做朱八八。我們這里再介紹一下,朱重八家族的名字,都很有特點。朱重八高祖名字:朱百六;朱...
摘要:我們這里再介紹一下,朱重八家族的名字,都很有特點。取這樣的名字不是因為朱家是搞數學的,而是因為在元朝,老百姓如果不能上學和當官就沒有名字,只能以父母年齡相加或者出生的日期命名。所以說命名不僅僅是一種科學,更是一種藝術。 在小朱元璋出生一個月后,父母為他取了一個名字(元時慣例):朱重八,這個名字也可以叫做朱八八。我們這里再介紹一下,朱重八家族的名字,都很有特點。朱重八高祖名字:朱百六;朱...
摘要:每個軟件系統都提供兩個價值給利益相關者表現和結構。來自利益相關者的觀點,開發者僅僅只提供了一些形態上的粗略改變,來自開發者的觀點,老板的需求越來越難。記住,作為一個開發者,你就是利益相關者,你需要維護的軟件里有你的利益。 每個軟件系統都提供兩個價值給利益相關者:表現和結構。軟件開發者應的確保這兩個價值盡量高負責。然而很不幸,程序員很多只關心其中一個而忽略另一個,甚至更不幸,他們可能關注...
閱讀 2914·2021-11-23 09:51
閱讀 1565·2021-11-15 11:36
閱讀 3022·2021-10-13 09:40
閱讀 1915·2021-09-28 09:35
閱讀 13101·2021-09-22 15:00
閱讀 1382·2019-08-29 13:56
閱讀 2936·2019-08-29 13:04
閱讀 2707·2019-08-28 18:06