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