如何編寫高質量的C#代碼(一)


從”整潔代碼“談起

一千個讀者,就有一千個哈姆雷特,代碼質量也同樣如此。

想必每一個對於代碼有追求的開發者,對於“高質量”這個詞,或多或少都有自己的一絲理解。當我在長沙.NET技術社區群拋出這個問題時,眾說紛紜。有人說注釋齊全、可讀性高,就是高質量;有人說變量命名、代碼層次清晰,就說高質量的代碼;有人說那些使用了新特性的代碼,很多都是高質量代碼;也有人說,高質量的代碼是個偽命題,因為他往往要花大量的精力才能精心打磨,有這個時間,產品早就黃了。

說到”高質量“代碼,就不得不提”整潔代碼”。這個概念來源於暢銷書《代碼整潔之道》(The Clean Code)中,鮑勃大叔引入了這個整潔代碼的概念。

他認為:

寫整潔代碼,需要遵循大量的小技巧,貫徹艱苦習得的‘整潔感’”,這種“代碼感”就說關鍵所在。有些人生而有之。有的人費點勁才能得到。它不僅讓我們看到代碼的優劣,還予我們以借戒規之力化優為列的攻略。

缺乏”代碼感”的程序員,看混亂是混亂,無處着手,有“代碼感”的程序員,能從混亂中看出其他的可能與變化。“代碼感”幫助程序員選出最好的方案,並指導程序員指定修改行動計划,按圖索驥。

編寫整潔代碼的程序員就像藝術家,他能夠用一系列變化把一塊白板變作由優雅代碼構成的系統。

這本書值得擺在每一位程序員的案頭。許多熱衷於英文原作的讀者都會說國人翻譯的許多作品都失去了原作的韻味,但這本韓磊老師翻譯這本中文版十幾年過去了,印刷了許多版了,也能客觀證明這本譯作的價值。

也許初讀這本書,許多作者提到的手法我們無法短時間內認真體會,但許多讀過這本書都表示,許多想法在我們寫代碼的時候突然迸濺而出,使得思路能夠更加通達,並達到一種“人碼合一”的狀態。

”代碼感“

在我們大部分開發者看來,我們開發的代碼,往往無需涉及過於復雜的業務邏輯或底層技術,只需簡單的使用一些代碼拼湊,即可按時完成我們的任務,也就說所謂的”CRUD業務開發者“。

但業務系統本身也並非全靠所謂的“無代碼平台”或“代碼生成器”能夠自動開發完成,他依然需要開發者用心去設計其中的邏輯、變量、結構、流程,才能更好的運轉,尤其是要想讓應用系統能夠保持長久的生命力,更需要我們能夠編寫更高質量的代碼。

在《代碼整潔之道》中,作者將這種編寫高質量代碼的能力,稱為“代碼感”,這種感覺有時需要靈光一現,有時又需要花費大量的精力才能完成。

就像在《灌籃高手》中,安西教練讓大家培養球感:

兩萬個球?寫兩萬個類/方法/代碼行?確實是一種提高”代碼感“的好方法。

但跟投球要掌握方法一樣,簡單的重復寫兩萬行代碼估計很難提高代碼質量,依然需要大量刻意練習才能帶來質量上的提升。

而如何編寫高質量代碼,在軟件開發領域,也有一些前人總結出來的良好准則,人們將這些准則,總結為“設計原則”。除了設計原則外,還要許多良好的實踐模式,人們將它們稱為”設計模式“。設計原則就像是內功心法,設計模式,則像招數功夫。

也許我們無法完全遵循這些原則或模式,但能夠靈活的運用,總能給代碼質量帶來提升。

何為高質量代碼

我個人認為:高質量代碼是可讀性強、易於測試,它們能夠恰如其份的表達業務的需要,並能根據業務需要易於修改的代碼。 高質量的代碼也許與技術架構、特定API、特定的語言沒有太大關系,但高質量代碼或許都具備一些相似的特點。

代碼結構

結構是代碼的核心,就像高樓的支架,為整個代碼的完整運行奠定基礎。好的代碼一定結構清晰,讓人易於理解,並能快速定位問題、解決問題。

有人說好文章的結構特點便是:” 鳳頭、豬肚、豹尾“, 文章的起頭要奇句奪目,引人入勝,如同鳳頭一樣俊美精采;文章的主體要言之有物,緊湊而有氣勢,如同豬肚一樣充實豐滿;文章的結尾要轉出別意,宕開警策,如同豹尾一樣雄勁瀟灑。 代碼也許無需追求達到這么高的境界,但遵循一定清晰的代碼結構也能達到同樣的效果。

結構按照我個人的理解,可能包括以下幾種層面:1、項目文件夾命名;2、分層;3、模塊命名;4、代碼格式。

1、項目文件夾

對於復雜項目,打開文件夾和解決方案的第一眼,是清晰還是紊亂,往往就是我們對於項目的第一印象。許多資深研發工程師,都會傾向於用數字來對文件夾進行編號,例如對於復雜項目,我們使用如下命名方式對定義解決方案文件夾,雖然不會花特別多的功夫,但會給開發過程帶來許多便利。

當然,由於在Visual Studio中,項目文件夾本身屬於sln解決方案文件中定義的層級結構,並不會在資源管理器文件夾中體現,所以有時還需要在資源管理器文件夾中也定義類似的層級結構。

01 基礎服務
02 框架服務
03 應用服務
   01 工作流服務
   02 權限服務
   03 日志服務

2、分層

分層式架構大家都習以為常,其中尤其以三層架構(用戶表現層,業務邏輯層,數據訪問層)已經深入人心,成為許多.NET開發者的普遍認可,而領域驅動設計最常見的則是四層式領域驅動設計(用戶界面層,應用層,領域層,基礎設施層)。

分層式架構體現了”關注度分離“的原則,在進行軟件開發過程中,可以根據需求,找到對應的邏輯分層,進行代碼實現;有時不同邏輯分層的組件會以各自不同的發展速度迭代以滿足不同的需求;在適當的情況下,還能采用分布式架構,讓不同層運行在不同的基礎設施中,期間通過rpc等方式保持通信,給架構留下了足夠的彈性空間。

設計分層式架構並非越多越好,盡量控制在三到四層就足夠了,不然會陷入”千層餅“的陷阱,過多的分層和過少的分層,其實沒有任何區別。

對於后端工程師來說,理解分層式架構並不困難,難的是要識別哪里邏輯代碼應該歸屬於哪一層;而許多對於方興未艾的前端技術來說,如何分層,卻似乎並不是一件容易的事,由於前端業務要適應來自用戶層面的無窮變化,很容易就陷入“意大利面”式的代碼混亂中。vuex框架為前端開發者提供了一種良好的示例,有時無需深入了解vuex的機制,只需"模仿"這種分層方法,就能寫出更加易於維護的前端代碼了。

3、模塊(類庫)

模塊的設計和耦合性

在.NET開發中,模塊有時是一個獨立的項目,並以一個獨立dll(類庫)的形式進行分發。模塊也是最為常見的一種代碼實踐,但在《領域驅動設計·軟件核心復雜性應對之道》一書中,作者埃里克·埃文斯卻指出模塊的運用,引起了“認知過載”的問題:

認知負荷理論認為,在問題解決和學習過程中的各種認知加工活動均需要消耗認知資源,若所有活動所需的資源總量超過個體擁有的資源總量,就會引起資源的分配不足,從而影響個體學習或問題解決的效率,這種問題就說“認知過載”。

這段理論確實有點拗口,對應到軟件開發過程中,用通俗的說法,就是這個包承載的知識量太大了,把原本可以分離到多個模塊中的邏輯代碼都囊括進來,使得其反而降低了開發的效率。

尤其是類庫的定義,不同的開發者有不同的習慣,有時按技術來划分,有時又按業務場景來划分,有時分拆,有時組合,“千人千面”,不連貫的設計思想,和“能用就行”的想法混合在一起,很容易就造成了一鍋粥的情況。

在.NET項目中,每用一個using,就引入了一種耦合,而使用了new方法,創建了一個對象的示例,又引入了一個對象的耦合。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using xxx.Core;
using xxx.Infrastructure.Extension;
using Google.Protobuf.Collections;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;

而設計優良的代碼模塊,則可以讓依賴盡可能的減少。

模塊其實也是實踐“高內聚,低耦合”思想的主要陣地,如果業務相關性很高的對象被划分到不同的模塊中,往往會使得開發者很難理解它們在業務上的作用,也會導致模塊間的耦合進一步提高。

因此,好的模塊設計應該將那些具有緊密概念關系的模型元素集中在一起,並能描述該模型元素的職能,使之成為一個內聚的概念集合。

組件設計的原則

關於如何設計模塊,在《敏捷軟件開發 原則、模式與實踐》一書中,作者引述了以下設計原則基於粒度這個角度為組件的內聚性進行描述:

重用-發布等價原則(REP)

重用的粒度就是發布的粒度。REP指出,一個組件的重用粒度可以和發布粒度一樣大。我們所重用的任何東西都必須被發布和跟蹤。簡單的編寫一個類,然后聲稱它是可重用的做法是不現實的。只有在建立一個跟蹤系統,為潛在的使用者提供所需要的變更通知、安全性以及支持后,重用才有可能。

共同重用原則(CRP)

一個組件中的所有類應該是共同重用的,如果重用了組件中的一個類,那么就要重用組件中的所有類。

共同封閉原則(CCP)

組件中的所有類對於同一種性質的變化應該是共同封閉的。一個變化若是對一個封閉的組件產生影響,則將對組件中所有的類產生影響,而對其他組件則不造成任何影響。

從穩定性的角度為組件的內聚性進行描述:

無環依賴原則:

在組件中的依賴關系圖中,不允許存在環。

穩定抽象原則

朝着穩定的方向進行依賴。

設計不能是完全靜態的。要使設計可維護,某種程度的易變性是必要的。我們通過遵循共同封閉原則來達到這個目標。使用這個原則,可以創建對某些變化類型敏感的組件。這些組件設計為可變的。我們期望他們變化。

穩定抽象原則

組件的抽象程度應該與其穩定程度一致。

4、代碼格式

類的基本結構

代碼格式,就是一個C#代碼文件的邏輯結構。寫代碼其實是一件成本很低的事,但維護代碼,卻是一件成本很高的事。開發一個功能,只需短短幾十分鍾時間,但如果我們要去找出代碼中存在的缺陷,卻往往需要花費大量的時間。

這就客觀上要求,我們書寫的代碼應該盡量方便閱讀(可讀性)、檢索(快速找問題)、易於維護,而書寫出“格式化”的代碼,大概是我們能夠提高代碼質量的第一步。

對於書寫的代碼,大部分都是從上往下閱讀,在需要閱讀的代碼較多量時,往往會選擇折疊到定義,這樣就能一眼看出每個方法的用途,要達到這個效果,就意味着我們需要精心設計安排代碼的垂直格式。有經驗的開發者往往會按照這種結構。

私有字段:定義類內部的基本成員,高層次概念,常量,和引入的算法。

構造函數:定義類的創建過程。

公共方法:定義類為外部暴露的行為。

私有方法:定義類為內部提供的行為。

類的格式要求

在《代碼整潔之道》這本書中,作者介紹了他對於代碼的格式要求:

垂直格式

代碼文件的長度控制在200-500行左右,且短文件通常比長文件易於理解。垂直閱讀時,頂部是粗線條概述,隱藏了故事細節,然后再不斷展開。

每行展示一個表達式或一個子句,尤其是C#的鏈式語法,盡量一行代碼就是一個方法。

entity.Property(e => e.Memo)
.HasMaxLength(500)
.IsUnicode(false)
.HasComment("備注");

每組代碼行展示一個完整的思路,思路間用空白行隔開。垂直方向上,靠近的代碼可以展示它們之間的緊密關系,能夠讓代碼更好閱讀。

變量聲明應盡可能靠近其使用位置,因為函數很短,本地變量應該在函數的頂部出現。一個函數調用了另外一個函數,應該把它們放到一起,且調用者應該在被調用者上面。概念相關的代碼應該放到一起,相關性越強,彼此之間的距離就該越短。

橫向格式

橫向首先表現在代碼的寬度上,盡量控制在一行代碼不超過120個字符。

水平方向上,可以用空格字符把彼此緊密相關的變量或對象連接在一起,也可以用空格將相關性較弱的對象分割開。

注意水平縮進和左對齊,尤其是上面提到的鏈式語法,如果點號沒對齊,簡直讓人難受。

entity.Property(e => e.UserId)
   .HasMaxLength(10)
  .IsUnicode(false)
.IsFixedLength();
小結

本文對如何編寫高質量代碼進行了一些簡單的概述,介紹了代碼的分層、組件(包)的設計、以及整潔代碼中的一些開發實踐,通過了解這些知識,能夠讓我們逐漸形成自己對於代碼的體會,並通過不斷的練習,將能夠提高我們的代碼能力。

當然,有時,寫文檔、適當的做一些軟件工程設計,看起來與完成代碼編寫無關,但也同樣是提高代碼質量的一種手法,通過就像許多好文章往往會先搭框架,好代碼也同樣如此。

根據業務需要,畫一波流程圖、領域模型圖、類圖、時序圖能夠讓我們的思路提前沉淀,讓我們的開發過程更加流暢,更能開發出高質量的代碼。

下一篇,將對規范命名、注釋、設計向量、設計原則、設計模式進行一些討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM