细节之中自有天地,整洁成就卓越代码
? ??
? ? ?
?溪源?| 長(zhǎng)沙.NET技術(shù)社區(qū)
開(kāi)篇
我們總是很容易就能寫(xiě)出滿足某個(gè)特定功能的代碼,卻很難寫(xiě)出優(yōu)雅代碼。又最欣賞那些優(yōu)雅的代碼,因?yàn)閮?yōu)雅代碼更能體現(xiàn)一個(gè)開(kāi)發(fā)者的積累。
就像寫(xiě)一篇散文,有的就像初學(xué)者不得其門(mén)而入,遣詞造句都非常困難,然后糾糾結(jié)結(jié),最終不了了之。或者啰哩吧嗦,看起來(lái)說(shuō)了一堆,其實(shí)就像是村婦閑聊,毫無(wú)重點(diǎn),不過(guò)是口水文而已。
好代碼應(yīng)該是這樣的,如涓涓細(xì)流、如同一首詩(shī),一篇優(yōu)美的故事,將作者編寫(xiě)代碼時(shí)的情感慢慢鋪墊開(kāi)來(lái),或是高潮迭起,此起彼伏,或是平鋪直述,卻蘊(yùn)含道理。我始終相信優(yōu)秀的代碼是有靈魂的,代碼的靈魂就是作者的邏輯思維。
編寫(xiě)整潔代碼 or 非整潔代碼,就像平時(shí)生活中是否注意愛(ài)護(hù)環(huán)境的一點(diǎn)點(diǎn)小習(xí)慣,一旦壞味道代碼沒(méi)有及時(shí)處理,就會(huì)成為破窗效應(yīng),然后逐漸的代碼越寫(xiě)越爛,最終這些代碼要么以重構(gòu)收?qǐng)?#xff0c;要么就被拋棄。
我們見(jiàn)過(guò)太多沒(méi)有毫無(wú)質(zhì)量可言的代碼,許多時(shí)候開(kāi)發(fā)者們由于能力原因、或者時(shí)間有限,寫(xiě)了許多能夠滿足當(dāng)前工作的代碼,然后就棄置高閣,不再理會(huì)。于是,代碼寫(xiě)之前的只有自己和上帝能理解代碼的意思,而寫(xiě)完了之后,只有上帝能懂了;還有一些開(kāi)發(fā)者說(shuō):我只會(huì)寫(xiě)代碼,不會(huì)優(yōu)化代碼,他們仿佛特別勤奮,每天都會(huì)比其他人都熱衷于熬工時(shí),但是寫(xiě)出的代碼,實(shí)際上是一個(gè)個(gè)難以維護(hù)的技術(shù)債。而且許多代碼的作者總喜歡找各種借口來(lái)抵賴(lài),例如喜歡說(shuō)代碼出了問(wèn)題都是底層框架太垃圾了、或者別人的代碼封裝得太差。他們總是抱怨這抱怨那,但是即便有優(yōu)秀的框架、技術(shù),就一定能寫(xiě)出優(yōu)秀的代碼么?
在這里筆者列舉了平時(shí)看到過(guò)一些自認(rèn)為不太整潔的代碼,以及與《代碼整潔之道》(Clean Code · A Handbook of Agile Software Craftsmanship)一書(shū)中相對(duì)應(yīng)的范例,歡迎大家一起來(lái)拍磚。
(經(jīng)驗(yàn)有限,時(shí)間倉(cāng)促,請(qǐng)輕噴。)
一些栗子
1、命名規(guī)則
1.1 變量命名和方法命名
在我們剛剛開(kāi)始學(xué)習(xí)寫(xiě)代碼的古老時(shí)代,或許會(huì)有下面這種習(xí)慣。
這是一個(gè)喜歡用自己的姓名來(lái)命名類(lèi)和方法的作者,在他的代碼中,經(jīng)常可以看到這樣奇怪的對(duì)象定義,而且他還喜歡用a,b,c,d,e,f或者s1,s2這樣的命名,仿佛他的代碼自帶混淆特效。這樣的代碼嗅起來(lái)會(huì)不會(huì)覺(jué)得充斥著奇怪的味道?
另外,有沒(méi)有發(fā)現(xiàn)有許多開(kāi)發(fā)者喜歡用 GetData() 來(lái)定義獲取數(shù)據(jù)的方法?然后這個(gè)方法就成為一個(gè)萬(wàn)金油的方法,不管是爬蟲(chóng)采集、或者數(shù)據(jù)綁定,無(wú)論是 C# 寫(xiě)的后端或者 Java 寫(xiě)的后端代碼,或者用 vue 寫(xiě)的前端代碼,仿佛在任何場(chǎng)景、任何數(shù)據(jù)應(yīng)用都可以看到這樣的方法。
如果一個(gè)項(xiàng)目中,有十幾個(gè)地方都出現(xiàn)了這個(gè) GetData() 方法,那種感覺(jué)一定非常難受。
1.2 Model、Dto 傻傻分不清楚
隨著技能的增長(zhǎng),或許我們會(huì)學(xué)到一些新的代碼概念,例如,Model、DTO 是經(jīng)常容易弄混淆的一種概念,但是在某些代碼中,出現(xiàn)了下面的命名方式就有點(diǎn)令人窒息了。
這位大概是一位對(duì)概念嚴(yán)重消化不良的資深開(kāi)發(fā)者,居然同時(shí)把 Model 和 DTO 復(fù)用在一個(gè)對(duì)象上,
(當(dāng)然,一個(gè)開(kāi)發(fā)者定義變量的背后一定有他的動(dòng)機(jī))。
他到底是想要的是用來(lái)在 MVC 模式解決數(shù)據(jù)傳輸和對(duì)象綁定的模型對(duì)象?還是用于傳輸數(shù)據(jù)的 DTO 呢?
--其實(shí)他定義這個(gè)對(duì)象,是為了定義存儲(chǔ)數(shù)據(jù)對(duì)象的實(shí)體( Entity )。
1.3特殊情況術(shù)語(yǔ)和字段對(duì)照表非常重要
近年來(lái)開(kāi)發(fā)者素質(zhì)越來(lái)越高,所以許多優(yōu)秀開(kāi)發(fā)者會(huì)傾向于使用翻譯軟件來(lái)翻譯變量名,然后用英語(yǔ)來(lái)命名,但是即便如此,許多政務(wù)項(xiàng)目總是能嗅出一些奇怪的味道。
例如前不久看到一條這樣的短信:(原圖已經(jīng)消失)
xxx公積金中心提醒您:您于{TQSJ}日進(jìn)行了{(lán)TQCZ}操作,賬上剩余金額為{SYJE}元。這是個(gè)bug將xxx公積金中心的某些秘密透露在大家面前。作為一個(gè)嚴(yán)謹(jǐn)?shù)捻?xiàng)目,居然使用中文首字母大寫(xiě)命名法,這讓習(xí)慣于大駝峰、小駝峰的我看了之后尷尬癌犯了,很不舒服。但是這也是許多政務(wù)信息化項(xiàng)目的中字段命名的規(guī)范,而且在這種情況下,往往會(huì)輸出一份非常規(guī)范的數(shù)據(jù)庫(kù)字段對(duì)照表,確保中文和首字母的語(yǔ)義不讓人產(chǎn)生歧義。
所以特定語(yǔ)境下,變量和方法本身沒(méi)有嚴(yán)格的規(guī)定,但是一定要使用恰當(dāng)?shù)恼Z(yǔ)境概念,對(duì)于這樣的特定場(chǎng)景,盡量維護(hù)一份實(shí)時(shí)更新的術(shù)語(yǔ)表吧。
2、狀態(tài)碼返回值
2.1業(yè)務(wù)邏輯狀態(tài)碼
似乎在對(duì)外提供接口時(shí),使用下列接口狀態(tài)碼是一種比較常見(jiàn)的慣例。提供統(tǒng)一格式的 code 狀態(tài)碼以及返回的消息和成功返回結(jié)果時(shí)的填充數(shù)據(jù),能夠讓開(kāi)發(fā)者高效的完成接口對(duì)接,無(wú)需關(guān)心http狀態(tài)碼背后的含義。
2.2用 http 狀態(tài)碼為什么不夠?
上面這是一種經(jīng)典的流派,還有一種流派則會(huì)使用http狀態(tài)碼來(lái)返回指定的數(shù)據(jù),事實(shí)上 http 協(xié)議本身已經(jīng)提供了許多狀態(tài)碼,例如下面的這些大家都非常熟悉的狀態(tài)碼。
但是這些狀態(tài)碼為啥不夠?主要是為了減少前后端、服務(wù)上下游之間接口對(duì)接的難度,也是一種提高效率的方式。但是 http 狀態(tài)碼是一種通用的格式,應(yīng)盡量使用這種方式,而不應(yīng)該通過(guò)解析正常響應(yīng)后的 json 來(lái)判斷是否正確操作。
3、switch 語(yǔ)句與判斷語(yǔ)句
3.1 面向過(guò)程式或面向?qū)ο笫?/span>
我曾經(jīng)跟小組中一位大佬交流他的一段代碼,他的這段代碼大概是這樣的。
且不說(shuō)這位大佬的代碼是寫(xiě)得好或者不好,僅僅就這200多行代碼的4個(gè)大switch讀起來(lái)大概會(huì)讓人便秘難受吧。于是在我讀完這段代碼之后,我冒死向他請(qǐng)教這么寫(xiě)代碼的原因,他說(shuō)我這個(gè)流程處理就是一個(gè)簡(jiǎn)單的用例場(chǎng)景,哪里還有什么可以?xún)?yōu)化的余地?
我跟他介紹了20分鐘代碼封裝的必要性,于是,他把代碼寫(xiě)成了這樣。
這酸爽令人簡(jiǎn)直難以置信。(事實(shí)上這個(gè)新鮮出爐的遺留應(yīng)用,正是這樣一點(diǎn)點(diǎn)堆積了許多總代碼行超過(guò)千行的類(lèi)文件)
《代碼整潔之道》書(shū)上有一個(gè)類(lèi)似的例子,大概與上文類(lèi)似,Robert 大叔給出了這樣的建議:
對(duì)于switch 語(yǔ)句,我的規(guī)矩是如果只出現(xiàn)一次,用于創(chuàng)建多態(tài)對(duì)象,而且隱藏在某個(gè)集成關(guān)系中,在系統(tǒng)中其他部分看不到,就還能容忍。當(dāng)然也要就事論事,有時(shí)我也會(huì)部分或全部違反這條規(guī)矩。
上文我給出的示例,有點(diǎn)像面向過(guò)程的代碼風(fēng)格,而 Robert 大叔在他的書(shū)中寫(xiě)下的示例是這樣的(抽象工廠模式的示例)。
? ? ? ? ? ? ?
這清爽的感覺(jué),讓人很舒服啊。
3.2 孰優(yōu)孰劣?
當(dāng)然,原示例是一個(gè)流程處理的例子,似乎大家的流程處理代碼都習(xí)慣于使用這種面向過(guò)程風(fēng)格的寫(xiě)法,反正要加判定條件,就加一個(gè) case 就可以了。
而在某些特定情況下,甚至用 if / else 來(lái)寫(xiě)邏輯判斷更簡(jiǎn)單,于是我們經(jīng)常在某些銷(xiāo)量很好的快速開(kāi)發(fā)平臺(tái)中,看到這樣的例子。
? ? ? ? ? ? ?
這些典型的面向過(guò)程風(fēng)格的代碼,確實(shí)讀起來(lái)似乎更加簡(jiǎn)單、而且也易于實(shí)現(xiàn)。
Robert 大叔是這樣說(shuō)的:過(guò)程式代碼(使用數(shù)據(jù)結(jié)構(gòu)的代碼)便于在不改動(dòng)既有數(shù)據(jù)結(jié)構(gòu)的前提下添加新函數(shù),面向?qū)ο蟠a便于在不改動(dòng)既有函數(shù)的前提下添加新類(lèi)。
反過(guò)來(lái)講也說(shuō)得通:過(guò)程式代碼難以添加新數(shù)據(jù)結(jié)構(gòu),因?yàn)楸仨毿薷乃泻瘮?shù),面向?qū)ο蟠a難以添加新函數(shù),因?yàn)楸仨毿薷乃蓄?lèi)。
所以究竟是使用面向過(guò)程式代碼,還是面向?qū)ο笫酱a?沒(méi)有萬(wàn)試萬(wàn)靈的靈丹妙藥。
4、奧卡姆剃刀定律、得墨忒耳律
4.1“如非必要,勿增實(shí)體”
一旦開(kāi)始初步掌握面向?qū)ο箝_(kāi)發(fā)的基本原則,于是我們就會(huì)新建許多各種不同的模型對(duì)象。尤其是在webapi接口開(kāi)發(fā)過(guò)程中,更是如此。
切勿浪費(fèi)較多東西去做,用較少的東西,同樣可以做好的事情。
4.2 得墨忒耳律
假設(shè)有一段代碼是這樣的。
會(huì)不會(huì)為了獲得某些數(shù)據(jù),而寫(xiě)出這樣的代碼呢?
這樣就是典型的對(duì)得墨忒耳律的違背。這個(gè)原則指出:
模塊不應(yīng)了解它所操作對(duì)象的內(nèi)部情形。
更準(zhǔn)確的說(shuō),得墨忒耳律認(rèn)為,類(lèi)C的方法f只應(yīng)該調(diào)用以下對(duì)象的方法:
C(本身)
由方法f創(chuàng)建的對(duì)象。
作為參數(shù)傳遞給f的對(duì)象;
由C的實(shí)體變量持有的對(duì)象。
對(duì)象不應(yīng)調(diào)用由任何函數(shù)返回的對(duì)象的方法。換言之,只跟朋友說(shuō)話,不與陌生人說(shuō)話。
在上文中我舉的例子,祖父只跟自己的親兒子(Father)說(shuō)話,而不跟孫子(Me)說(shuō)話。
5、圈復(fù)雜度
在軟件測(cè)試的概念里,圈復(fù)雜度用來(lái)衡量一個(gè)模塊判定結(jié)構(gòu)的復(fù)雜程度,數(shù)量上表現(xiàn)為線性無(wú)關(guān)的路徑條數(shù),即合理的預(yù)防錯(cuò)誤所需測(cè)試的最少路徑條數(shù)。圈復(fù)雜度大說(shuō)明程序代碼可能質(zhì)量低且難于測(cè)試和維護(hù),根據(jù)經(jīng)驗(yàn),程序的可能錯(cuò)誤和高的圈復(fù)雜度有著很大關(guān)系。
據(jù)說(shuō)在Oracle數(shù)據(jù)庫(kù)中有一些屎山代碼,是通過(guò)一堆標(biāo)識(shí)量來(lái)判斷某些特定邏輯的,大概是這樣的。
(示例僅供參考,由于資源限制,未能考證,還請(qǐng)大佬指正一二。)
這是一個(gè)圈復(fù)雜度非常復(fù)雜的方法,我想任何一個(gè)讀到這樣代碼的開(kāi)發(fā)者都會(huì)對(duì)自己的人生充滿了積極而樂(lè)觀的判斷,那就是“活著比一切都好”。
對(duì)于這樣的代碼,我們應(yīng)該盡可能的降低代碼的圈復(fù)雜度,讓程序滿足基本可讀的需求。
6、注釋
我曾經(jīng)參加過(guò)一個(gè)使用objectc編寫(xiě)的應(yīng)用的,其中有一段代碼是這樣的,這個(gè)flag大概是魔法值,作者未經(jīng)考證直接就在代碼中使用了。然后一直流傳下來(lái),成為一段佳(gui)話(hua)。
還有這樣的注釋。傻傻分不清楚。
還有這樣的。
Robert大叔如是說(shuō):
什么也比不上放置良好的注釋來(lái)得有用。什么也比不會(huì)亂七八糟的注釋更有本事搞亂一個(gè)模塊。什么也不會(huì)比陳舊、提供錯(cuò)誤信息的注釋更有破壞性。
當(dāng)然很多中國(guó)程序員自稱(chēng)其變量命名是自注釋的,例如大概是這樣的。萬(wàn)能的 Is 命名法,只要是判斷狀態(tài)皆可用。
(每個(gè)程序員能夠成功的生存下來(lái)都不容易,他一定有異于常人的本事。)
7、霰(xian 第四聲)彈式修改
CRUD開(kāi)發(fā)者或許經(jīng)常會(huì)看到這樣的代碼,例如,如果我要對(duì)某一個(gè)對(duì)象的狀態(tài)( Status)進(jìn)行更改,可能會(huì)這么做:
這種霰彈式代碼中,一處代碼規(guī)則的變化,可能會(huì)需要對(duì)許多處代碼進(jìn)行同步修改,使得我們的代碼異常的難以維護(hù)。
8、異常
有時(shí)候可能會(huì)遇到這樣的代碼,在方法中定義一些文本的狀態(tài)碼,然后調(diào)用方法時(shí),再去判斷這個(gè)狀態(tài)碼的內(nèi)容,當(dāng)返回錯(cuò)誤碼時(shí),要求調(diào)用者立即處理錯(cuò)誤。
不如直接拋出異常,讓異常處理機(jī)制進(jìn)行處理吧。
9、邊界
9.1 模塊間的邊界
即便是簡(jiǎn)單的CRUD應(yīng)用系統(tǒng),優(yōu)秀的開(kāi)發(fā)者也能更好的處理應(yīng)用程序模塊間的邊界。某種意義上講,應(yīng)用程序內(nèi)部的邊界看起來(lái)或許沒(méi)有明確的界限之分,但是稍不留心就可能導(dǎo)致應(yīng)用程序間關(guān)系過(guò)于紊亂,讓其他開(kāi)發(fā)者捉摸不透。
例如,假設(shè)有一段代碼是這樣的,在用戶(hù)操作類(lèi)中,加入了一個(gè)獲取應(yīng)用數(shù)據(jù)的方法,確實(shí)會(huì)讓人很費(fèi)解吧。
9.2應(yīng)用間的邊界
相對(duì)而言,或許應(yīng)用間的邊界似乎能相對(duì)清晰的分析出來(lái)?并非如此。
在當(dāng)今時(shí)代,我們很少開(kāi)發(fā)完全與其他應(yīng)用系統(tǒng)沒(méi)有任何關(guān)聯(lián)的獨(dú)立軟件,這意味著我們或許無(wú)時(shí)無(wú)刻都得與其他第三方應(yīng)用進(jìn)行接口行為或數(shù)據(jù)的交換。這讓我們必須確保采取措施讓外來(lái)代碼干凈利落地整合進(jìn)自己的代碼中。
假設(shè)有一段代碼是這樣的:
在《代碼整潔之道》書(shū)中,Robert大叔推薦應(yīng)該第三方接口進(jìn)行隔離,通過(guò)Map那樣包裝或者使用適配器模式將我們的接口轉(zhuǎn)換成第三方提供的接口。讓代碼更好地與我們溝通,在邊界兩邊推動(dòng)內(nèi)部一致的用法,當(dāng)?shù)谌酱a有改動(dòng)時(shí)修改點(diǎn)也會(huì)更少。
總結(jié)
寫(xiě)代碼是開(kāi)發(fā)者的基礎(chǔ)技能,無(wú)論你是.NET 開(kāi)發(fā)者,或者 Java 開(kāi)發(fā)者,你都在努力用代碼實(shí)現(xiàn)自己的夢(mèng)想。如同韓磊老師在譯作《代碼整理之道》封面上總結(jié)全書(shū),寫(xiě)下的那句詩(shī)
“細(xì)節(jié)之中自有天地,整潔成就卓越代碼”。
卓越代碼從來(lái)不僅僅只是功能完善、代碼齊全,做好細(xì)節(jié),每個(gè)細(xì)節(jié)就是一方小天地。優(yōu)雅的代碼,不僅僅只是開(kāi)發(fā)者的個(gè)人能力的體現(xiàn),更是開(kāi)發(fā)者的立足之本。努力改善壞習(xí)慣,提高代碼質(zhì)量,時(shí)刻消除異味,時(shí)刻提高自己,更是個(gè)人技能的全面發(fā)展的必然要求。
總結(jié)
以上是生活随笔為你收集整理的细节之中自有天地,整洁成就卓越代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .NET Core 3.0及ASP.NE
- 下一篇: 推荐几个华为,字节跳动、蚂蚁金服等大佬的