停止使用花哨的技巧來(lái)編寫(xiě)優(yōu)雅的代碼吧!
在技術(shù)社區(qū)經(jīng)??吹揭恍┎┛屯茝V各種編程語(yǔ)言的“高級(jí)特性”和“高級(jí)模式”,并利用這些特性提供一些“優(yōu)雅”的代碼。
但是這種花里胡哨的東西真的可以提升我們的編碼效能嗎?
恐怕 不是的!
炫耀技能并不一定表明技術(shù)水平高超
許多人以使用語(yǔ)言和框架的晦澀功能為榮,通過(guò)利用各種罕見(jiàn)的 API 來(lái)展示他們對(duì)框架的熟悉程度。
他們認(rèn)為自己的編程和技術(shù)能力比那些寫(xiě)簡(jiǎn)單邏輯的同學(xué)要高。甚至還會(huì)產(chǎn)生 “傲慢” 的情緒。
這種觀點(diǎn)是 非常錯(cuò)誤 的!
我們來(lái)看下面的例子:
在招聘開(kāi)發(fā)人員時(shí),存在一個(gè)有趣的現(xiàn)象,即:更高級(jí)別的申請(qǐng)人較少審查具體的編碼技能,而更多地審查架構(gòu)能力、業(yè)務(wù)理解和工程質(zhì)量。
請(qǐng)注意,理解架構(gòu)、業(yè)務(wù)和工程并不是關(guān)于溝通或管理等軟技能,這是工程師堅(jiān)實(shí)的專(zhuān)業(yè)能力。
另一方面,技術(shù)社區(qū)中最受歡迎的內(nèi)容通常是各種框架和庫(kù)的“入門(mén)指南”。
從暢銷(xiāo)的《XXX從初學(xué)者到專(zhuān)家》指南到《一步一步教你如何學(xué)習(xí)XXX》,最熱門(mén)的內(nèi)容仍然圍繞著如何操作這些API并應(yīng)用某些技術(shù)知識(shí)模式。
考慮到這些現(xiàn)象和一些常識(shí),我們可以得出以下三個(gè)條件:
- 程序員的級(jí)別越高,技能樹(shù)中技術(shù)知識(shí)所占的比例就越小。
- 高級(jí)程序員的比例遠(yuǎn)小于初級(jí)程序員。
- 在編程界,大多數(shù)人關(guān)心的是技術(shù)知識(shí)。
根據(jù)這些條件,我們可以做出一個(gè)寬松的推論:最關(guān)心和癡迷于編程技能的人很可能是大多數(shù)處于初級(jí)水平的程序員。 因此,炫耀技能并不一定表明技術(shù)水平高超。
需要澄清的是,我們并不認(rèn)為編程技能不重要。相反,高級(jí)程序員對(duì)技能的理解比初級(jí)程序員要深刻得多,許多熟練的代碼可以在數(shù)量級(jí)上優(yōu)化和解決問(wèn)題。
那么我們應(yīng)該如何評(píng)估這樣的代碼呢?
能夠應(yīng)用各種高級(jí)功能的學(xué)生無(wú)疑被認(rèn)為是“聰明”的。然而,何時(shí)以及如何使用它們需要基于所謂“智慧”的判斷。
這與 Facebook 和 Google 的代碼標(biāo)準(zhǔn)中經(jīng)??吹降恼f(shuō)法類(lèi)似:
運(yùn)用你最好的判斷力。
雖然聽(tīng)起來(lái)很悅耳,但這是一個(gè)形式上的概念。
下面我們將進(jìn)行一些更具體的討論并提取一些常見(jiàn)的技巧。
常見(jiàn)技巧
幸福的家庭都是相似的;每個(gè)不幸的家庭都有自己的不幸。
——列夫·托爾斯泰
好的代碼就像好的文學(xué)作品,壞的代碼有它自己的壞處。
如果我們使用“聰明的技巧”來(lái)評(píng)估一段代碼,那么這段代碼的質(zhì)量可能不是很好。對(duì)于使用各種技術(shù)的代碼,我們還可以找到許多這些技術(shù)應(yīng)用不佳的場(chǎng)景來(lái)表明它們各自的問(wèn)題。
使用危險(xiǎn)的語(yǔ)義
很多人讀完《高級(jí)程序設(shè)計(jì)》之類(lèi)的書(shū)后,都會(huì)將自己對(duì)高級(jí)功能的理解運(yùn)用到實(shí)際項(xiàng)目中去炫耀。在前端領(lǐng)域,這些行為包括但不限于:
- 學(xué)習(xí) == 和 === 的區(qū)別,并在不同的情況下使用不同的符號(hào)進(jìn)行邏輯判斷。
- 了解變量提升(var)行為后,使用它來(lái)實(shí)現(xiàn)特殊的代碼執(zhí)行序列。
- 理解 prototype 和 constructor ,并利用它們實(shí)現(xiàn)各種繼承關(guān)系。
- 掌握 this 指向的各種規(guī)則,并使用特殊的規(guī)則來(lái)綁定上下文。
- ……
使用具有這些功能的代碼當(dāng)然可以運(yùn)行,但這里的問(wèn)題是這些語(yǔ)義都是 危險(xiǎn)的,或者是語(yǔ)言設(shè)計(jì)問(wèn)題造成的渣滓。
既然知道它們很難使用,并且有成熟的替代解決方案可用,為什么還要用它們來(lái)展示自己的技術(shù)技能呢? 然而在前端社區(qū),這樣的行為卻屢屢發(fā)生。
例如:
- 僅了解各種規(guī)則this指向就足以寫(xiě)一篇長(zhǎng)文章(這在許多技術(shù)社區(qū)中一直是一個(gè)無(wú)聊的話題)。
- 令人驚訝的是,像“==”這樣有無(wú)數(shù)陷阱的功能,很多人在閱讀博客后竟然想要 “合理” 地使用它們。
- 至于變量提升,這種完全違反直覺(jué)的設(shè)計(jì)缺陷已被一些人用來(lái)創(chuàng)建各種花哨的面試問(wèn)題。
當(dāng)然,這決不妨礙理解這些所謂的“高級(jí)功能”如何工作以及為什么它們會(huì)導(dǎo)致令人困惑的行為。
對(duì)于每個(gè)想要成長(zhǎng)的可靠學(xué)生來(lái)說(shuō),學(xué)習(xí)它們很重要。這里給出的建議是:
- 至少理解一次,達(dá)到能夠指出問(wèn)題所在的程度。
- 了解這些功能的替代解決方案,并了解如何避免陷阱。
- 除非維護(hù)底層庫(kù),否則在任何情況下都不要在代碼中使用它們。
每種編程語(yǔ)言在其開(kāi)發(fā)過(guò)程中都不可避免地留下遺留問(wèn)題。對(duì)于JS這樣一個(gè)一周誕生、向前兼容性要求非常高的語(yǔ)言來(lái)說(shuō),這個(gè)問(wèn)題就更加嚴(yán)重了。
然而,隨著軟件工程的發(fā)展,這些設(shè)計(jì)缺陷帶來(lái)的危險(xiǎn)語(yǔ)義已經(jīng)慢慢淡入歷史?,F(xiàn)在,像深入研究IE6兼容性問(wèn)題一樣,深入學(xué)習(xí)、掌握和使用它們已經(jīng)逐漸過(guò)時(shí)了。
應(yīng)用設(shè)計(jì)模式
設(shè)計(jì)模式也是技術(shù)文章中非常常見(jiàn)的主題。
例如,很多文章都把《設(shè)計(jì)模式》中的幾十種模式應(yīng)用到了 JavaScript 中,用上面提到的各種“高級(jí)特性”來(lái)模擬這個(gè)、實(shí)現(xiàn)那個(gè)。最后,他們還夸夸其談,這些模式都是“優(yōu)秀程序員必知”,所以在簡(jiǎn)歷上加上一句“精通各種設(shè)計(jì)模式”,就讓人印象深刻了!
設(shè)計(jì)模式的初衷是為了彌補(bǔ)Java等靜態(tài)語(yǔ)言的缺點(diǎn)。隨著編程語(yǔ)言的發(fā)展,許多“經(jīng)典”設(shè)計(jì)模式已經(jīng)成為語(yǔ)言機(jī)制的一部分。
比如:export 內(nèi)置對(duì)單例模式的支持,用函數(shù)層包裝內(nèi)容就是工廠模式,yield 也實(shí)現(xiàn)了迭代器模式等等。
此外,JS 的動(dòng)態(tài)性使得 JSON 的靈活性大幅超越了反射,而一等公民的函數(shù)設(shè)計(jì)使得 JS 回調(diào)函數(shù)比 Java 的回調(diào)接口或類(lèi)似 Visitor 的模式更加靈活。
很多提倡設(shè)計(jì)模式的文章之所以沒(méi)有傳播開(kāi)來(lái),是因?yàn)樗鼈內(nèi)藶榈刂圃炝瞬槐匾膹?fù)雜性,反而造成了一種誤解:“如果你不使用XX模式,就意味著你的技能缺乏”。
至少?gòu)奈覀€(gè)人閱讀優(yōu)秀的開(kāi)源項(xiàng)目源代碼來(lái)看,我還沒(méi)有發(fā)現(xiàn)機(jī)械應(yīng)用模式的實(shí)例;相反,需要解決的問(wèn)題被清楚地描述,然后提供可讀的抽象。
當(dāng)然,我們可以回顧性地識(shí)別其中的某些實(shí)施模式;然而,我更愿意相信作者并沒(méi)有以“這里我們需要使用XX模式”的心態(tài)來(lái)編碼。
許多缺乏經(jīng)驗(yàn)、缺乏洞察力的初學(xué)者可能會(huì)因?yàn)槿狈﹂喿x高質(zhì)量代碼的經(jīng)驗(yàn)或受到公司遺留項(xiàng)目中舊代碼的影響而最終遵循嚴(yán)格規(guī)定的方法。在我看來(lái),這是相當(dāng)遺憾的。
減少代碼行數(shù)
我們都知道,通過(guò)復(fù)制粘貼生成的冗長(zhǎng)且重復(fù)的代碼是不好的。
然而,大多數(shù)復(fù)制粘貼發(fā)生在期限緊迫且沒(méi)有時(shí)間進(jìn)行優(yōu)化的情況下。考慮到我們的工作強(qiáng)度,這是可以理解的。
另一方面,還有另一種極端行為,其目的是通過(guò)使用各種非常規(guī)手段來(lái)“簡(jiǎn)化”代碼,從而達(dá)到“最簡(jiǎn)潔”的代碼。
例如:
- 剛開(kāi)始學(xué)習(xí)函數(shù)式編程的同學(xué)可能對(duì)這樣的代碼情有獨(dú)鐘 a(b(c(d, e(f, g)))),認(rèn)為函數(shù)的深度嵌套可以大大減少中間變量,從而節(jié)省代碼量;
- 有些學(xué)生喜歡用邏輯運(yùn)算符連接各種條件邏輯,并將它們?nèi)繉?xiě)在一行中,例如 a || b && c && d;
- 同樣常見(jiàn)的情況是,實(shí)用程序函數(shù)最終會(huì)寫(xiě)入越來(lái)越多的參數(shù),直到它們?cè)谝恍兄幸淮涡詡鬟f。
我們?cè)倏紤]一下這樣的代碼是否增強(qiáng)了可讀性:
- 深度嵌套的函數(shù)調(diào)用會(huì)帶來(lái)大量的右括號(hào),例如:)))))),這在 Lisp 中長(zhǎng)期以來(lái)一直受到批評(píng);
- 單行條件邏輯不利于調(diào)試;
- 具有許多參數(shù)的函數(shù)往往表現(xiàn)復(fù)雜且難以調(diào)試。
這些編碼實(shí)踐可以輕松地替換為具有更好可讀性的表單,而無(wú)需太多麻煩。然而,故意創(chuàng)建這樣的代碼可能會(huì)讓后續(xù)的維護(hù)者感到不舒服。
對(duì)于有關(guān)換行和縮進(jìn)的具體實(shí)踐,JavaScript 標(biāo)準(zhǔn)樣式等工具可以有效地自動(dòng)處理大多數(shù)情況。
隱式重寫(xiě)常識(shí)
現(xiàn)代工程框架通常提供許多可定制的接口,允許開(kāi)發(fā)人員輕松修改框架的行為。
例如:React 公開(kāi)了上下文,而 Redux 和 MobX 等庫(kù)則利用此接口來(lái)極大地優(yōu)化深度prop傳遞的體驗(yàn)。
然而,框架內(nèi),存在許多隱含的約定和規(guī)范,當(dāng)在典型業(yè)務(wù)代碼中進(jìn)行不合理的定制時(shí),可能會(huì)導(dǎo)致重大挑戰(zhàn)。這些類(lèi)型的修改通常發(fā)生在不顯眼的地方,但可能會(huì)產(chǎn)生很大的影響。
例如:在我們之前維護(hù)的一個(gè)項(xiàng)目中,進(jìn)行了巧妙的修改,React.Component用基類(lèi)自己的XXX.BaseComponent行為替換了基類(lèi)。
定制的組件并沒(méi)有涉及任何業(yè)務(wù)邏輯相關(guān)的改動(dòng),只是增加了一些莫名其妙的初始化代碼。
結(jié)果,有關(guān) React 組件 Base 的隱含常識(shí)變得無(wú)效。維護(hù)時(shí),乍一看,替換的組件似乎很普通。但是,修復(fù)就會(huì)導(dǎo)致問(wèn)題。
此外,這些“黑魔法”代碼沒(méi)有評(píng)論或文檔,也不清楚為什么首先引入它們是為了解決什么問(wèn)題。對(duì)于這樣的編碼做法,也許除了過(guò)于聰明之外,沒(méi)有其他合理的評(píng)價(jià)。
該項(xiàng)目的另一個(gè)例子涉及另一種“聰明”的做法:
window.fetch 根據(jù)請(qǐng)求路徑將其替換為三到四個(gè)不同的自定義版本。這意味著當(dāng)維護(hù)人員編寫(xiě)新的獲取請(qǐng)求時(shí),他們不能依賴(lài)任何先前有關(guān)獲取的隱式知識(shí),而是必須通過(guò)跟蹤前輩的自定義版本來(lái)進(jìn)行調(diào)試!
還有一些隱性做法會(huì)因副作用而出現(xiàn)問(wèn)題。例如:當(dāng)看到 user = getUser(id) 時(shí),人們可能不會(huì)想到這個(gè)getUser函數(shù)不僅查詢(xún)用戶(hù),而且還會(huì)默默地顯示一條提示消息發(fā)送請(qǐng)求,然后還清除當(dāng)前數(shù)據(jù)。
當(dāng)然,在前端開(kāi)發(fā)本身中,管理大量與 UI 相關(guān)的網(wǎng)絡(luò)副作用本質(zhì)上會(huì)增加復(fù)雜性。
然而,如果調(diào)用一個(gè)函數(shù)會(huì)導(dǎo)致許多級(jí)聯(lián)結(jié)果,導(dǎo)致復(fù)雜性進(jìn)一步增加,許多維護(hù)者可能會(huì)選擇棄用和重寫(xiě)。
重新發(fā)明輪子
在技術(shù)社區(qū)中,經(jīng)??梢钥吹健白钊那岸藢?shí)用功能”之類(lèi)的合集,而且點(diǎn)贊數(shù)往往很高。然而,500合1的小霸王游戲卡比超級(jí)馬里奧更好玩嗎?
我有幸讀過(guò)一些這樣的文章,發(fā)現(xiàn)這些封裝的函數(shù)往往甚至沒(méi)有固定的主題:左邊是“ getCookie”,右邊是“ deepClone”,上面是isEmail,下面是scrollTop。每個(gè)實(shí)現(xiàn)在將英文函數(shù)名翻譯成中文的層面上只有幾行注釋?zhuān)瑳](méi)有測(cè)試用例、依賴(lài)配置或文檔。它們被稱(chēng)為“小而美”。
這樣的代碼值得復(fù)制到你的項(xiàng)目中以供重用嗎?坦白說(shuō),它們只是滿(mǎn)足“我能發(fā)明輪子”沖動(dòng)的產(chǎn)品。
當(dāng)然,我完全相信作者可以毫不費(fèi)力地寫(xiě)出優(yōu)雅的內(nèi)容。但項(xiàng)目不是采訪;而是項(xiàng)目。對(duì)于一個(gè)穩(wěn)定可靠的庫(kù),除了簡(jiǎn)單的實(shí)現(xiàn)之外,還需要很多與代碼無(wú)關(guān)的東西。
根據(jù)《人月神話》中的布魯克斯定律,軟件項(xiàng)目中實(shí)際編碼時(shí)間只占1/6;剩余的大部分時(shí)間用于測(cè)試、文檔和溝通。對(duì)于具有更高質(zhì)量要求的庫(kù)代碼——從在線源匆忙編寫(xiě)或復(fù)制(哦不!讓我們稱(chēng)之為內(nèi)聯(lián))代碼是否足夠?
在正式項(xiàng)目中使用庫(kù)時(shí),如果穩(wěn)定的現(xiàn)有依賴(lài)項(xiàng)能夠令人滿(mǎn)意地滿(mǎn)足要求,那么顯然這應(yīng)該是您的首選。如果你遇到需要自己重新發(fā)明輪子的情況,那么請(qǐng)確保在可靠項(xiàng)目留下的 5/6 之外編寫(xiě)代碼也做得很好;不要不必要地重復(fù)劣質(zhì)輪子。