代碼整潔之道

如今的軟件系統開發難度主要在於其複雜度和規模,客戶需求也再也不像Winston Royce瀑布模型指望那樣在系統編碼前完成全部的設計知足用戶軟件需求。在這個信息爆炸技術突飛猛進的時代,需求老是在不停的變化,隨之在2001年業界17位大牛彙集在美國猶他州的滑雪勝地雪鳥(Snowbird)雪場,提出了「Agile」(敏捷)軟件開發價值觀,並在他們的努力推進下,開始在業界流行起來。在《代碼整潔之道》(Clean Code),提出一種軟件質量,可持續開發不只在於項目架構設計,還與代碼質量密切相關,代碼的整潔度和質量成正比,一份整潔的代碼在質量上是可靠的,爲團隊開發,後期維護,重構奠基了良好的基礎。在這本書中做者提出了注重實際開發實踐的細節,而不是站在空洞的理論來談論整潔之道。web

什麼是整潔代碼?不一樣的人會站在不一樣的角度闡述不一樣的說法。而我最喜歡的是Grady Booch(《面向對象分析與設計》做者)闡述:數據庫

「整潔的代碼簡單直接。整潔的代碼如同優美的散文。整潔的代碼從不隱藏設計者的意圖,充滿了乾淨利落的抽象和直截了當的控制語句。」編程

整潔的代碼就是一種簡約(簡單而不過於太簡單)的設計,閱讀代碼的人能很清晰的明白這裏在幹什麼,而不是隱澀難懂,整潔的代碼讀起來讓人感受到就像閱讀散文-藝術的沉澱,做者是精心在乎締造出來。c#

一:命名數據結構

命名包括變量、函數、參數,類等,一個好的命名可以很好的表述其所承載的業務,從命名上就已經很好的答覆了爲何存在,作了什麼事,應該怎麼用等的大部分的問題,閱讀者看到它的時候沒必要去深究其實現細節,一切都在命名上一目瞭然。一個好的命名必須是名副其實,不存在歧義(雙關語或常見屬於衝突),直接了當(否認語句或者誤導性命名)。架構

二:函數:併發

從彙編/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利用率;可是併發編碼不是那麼容易,再加上臨界資源競爭死鎖。在併發編程的時候咱們必須儘可能遵照一些原則:

併發已經足夠複雜,咱們更須要代碼分離,分離線程相關代碼和非線程相關代碼,單一權責,儘量下降其複雜度。

限制臨街資源的做用域,爲臨界資源加鎖是防止併發的策略,可是必須正確的加鎖,若是造成等待環,就致使死鎖。

利用數據副本(值對象或者克隆)在線程之間傳遞數據,避免線程以前操做的併發影響;線程獨立,使其在本身的環境中運行,不能其餘線程共享數據。

對於臨界資源加鎖應儘可能保持加鎖範圍儘量的小。

更多關於簡單設計,迭進,逐步編程代碼,壞味道,併發示例請參見代碼整潔之道。

相關文章
相關標籤/搜索