如何编写无法维护的代码_编写可维护的前端代码
大家好,我叫王力國(guó),目前是 RPA 前端團(tuán)隊(duì)負(fù)責(zé)人,過(guò)去一年我們從零構(gòu)建了 RPA 前端平臺(tái),目前前端維護(hù)的代碼行數(shù)在 13 萬(wàn)行左右,其中超過(guò) 92% 以上是 TypeScript 代碼,主要有以下三個(gè)活躍迭代的代碼倉(cāng)庫(kù):
在這一段時(shí)間里經(jīng)過(guò)大家的努力,應(yīng)該來(lái)說(shuō)目前看來(lái)以上三個(gè)倉(cāng)庫(kù)的代碼是比較優(yōu)雅,維護(hù)成本是比較低的(大家應(yīng)該已經(jīng)半年沒(méi)有加班了,哈哈)。我本人在這段時(shí)間里做的主要工作是架構(gòu)設(shè)計(jì)和代碼評(píng)審,過(guò)程積累了一些經(jīng)驗(yàn),非常開(kāi)心今天能夠有機(jī)會(huì)同大家一起交流,今天的分享有 10 個(gè)部分,分別是:
在正式開(kāi)始之前,我要跟大家先聲明一下,以上 10 個(gè)部分僅是我個(gè)人認(rèn)為優(yōu)先級(jí)比較高的問(wèn)題,單獨(dú)整理出來(lái)跟大家一起探討,大家如果想要深究到具體某一行代碼如何編寫(xiě)的更加優(yōu)雅的話,可以閱讀市面上更多關(guān)于編碼方法論的書(shū),另外,今天這些內(nèi)容僅適用于上層業(yè)務(wù)開(kāi)發(fā)的項(xiàng)目,也就是說(shuō)不適用于開(kāi)源項(xiàng)目/基礎(chǔ)庫(kù)項(xiàng)目。
另外因?yàn)槲覀兺瑫r(shí)使用了 Angular 和 React,所以今天舉例的時(shí)候可能會(huì)穿插兩個(gè)框架的代碼,不過(guò)不會(huì)涉及太多框架相關(guān)概念,不會(huì)影響大家理解。
1/10. 基本約定:
1. 目錄結(jié)構(gòu)
大部分情況下你的目錄結(jié)構(gòu)組織得好,你的可維護(hù)性就已經(jīng)及格了,而一個(gè)好的目錄結(jié)構(gòu)應(yīng)該是可以自解釋的。
這里需要特別解釋一下為什么在 Angular 項(xiàng)目里你最好還可以有一個(gè) modules 目錄,因?yàn)榇蠹視r(shí)間久了會(huì)發(fā)現(xiàn),簡(jiǎn)單一個(gè) shared 目錄是很難滿(mǎn)足需求的,有時(shí)候你僅僅依賴(lài) shared 里的一個(gè)組件,卻需要導(dǎo)入整個(gè) shared,這是因?yàn)橛袝r(shí)候一個(gè)路由模塊對(duì)應(yīng)的不僅僅是一個(gè)領(lǐng)域,在這種情況下,我非常建議你將 shared 目錄按照領(lǐng)域模型拆分得更細(xì),甚至移除 shared 改為 modules 目錄,可以實(shí)現(xiàn)一個(gè)路由模塊按需導(dǎo)入幾個(gè)領(lǐng)域模塊。
2. 命名風(fēng)格
需要說(shuō)明幾點(diǎn):
2/10. 類(lèi)型安全
在早期團(tuán)隊(duì)還只有我一個(gè)人的時(shí)候?yàn)榱丝焖匍_(kāi)發(fā)選擇使用 es6,后來(lái)產(chǎn)品被提升為公司戰(zhàn)略級(jí)產(chǎn)品,團(tuán)隊(duì)也在爆發(fā)式擴(kuò)大,工程師水平產(chǎn)生了梯度,類(lèi)型約束的需求越來(lái)越大,于是我們非常果斷決定把整個(gè)項(xiàng)目改造至 100% TS,當(dāng)時(shí)為了降低遷移成本,代碼仍然充斥著大量 'any',讓人感覺(jué)沮喪的是,如果不能用好 TS 的類(lèi)型系統(tǒng)的話,使用 TS 反倒抬高了心智負(fù)擔(dān)降低了編碼效率。 后來(lái)我們發(fā)起了一個(gè)學(xué)習(xí) TS 的熱潮,大家不斷閱讀文檔,高級(jí)教程,學(xué)習(xí)市面上設(shè)計(jì)優(yōu)雅的 TS 代碼,嘗試去建設(shè)一個(gè)比較標(biāo)準(zhǔn)TS體系。
很快項(xiàng)目里的 any 就在肉眼變少,我們目前已經(jīng)在全鏈路 lint 檢查中開(kāi)啟了兩個(gè)重要約束:
另外這里要提一點(diǎn),人的自覺(jué)性總是無(wú)法被完全信任的,即使你使用了 pre-commit 鉤子來(lái)跑 lint,也可以輕易被人繞過(guò),我強(qiáng)烈建議你在云端 ci 流中加入 lint 檢查,且強(qiáng)制約束未通過(guò) lint 檢查的分支無(wú)法被合并。
在早期開(kāi)啟全面禁用 any 之后,我們編碼的效率非常低下,很多時(shí)候我們不得不編寫(xiě)大量的類(lèi)型定義。
不過(guò)好在大部分情況下,我們使用的工具或者庫(kù)已經(jīng)導(dǎo)出了一些工具類(lèi)型,我們需要自行編寫(xiě)的工具類(lèi)型非常少,跟大家推薦幾個(gè)資源可以幫助你更快地編寫(xiě)更安全的 TS 代碼:
3/10. 注釋有罪
這種注釋看起來(lái)很搞笑吧?但是我相信你的項(xiàng)目里一定有,而且還在源源不斷產(chǎn)生這種注釋。如何編寫(xiě)注釋確實(shí)是一門(mén)藝術(shù),寫(xiě)太多寫(xiě)太少都要被人罵。我的建議非常簡(jiǎn)單:永遠(yuǎn)不要注釋,除非你有充分的理由。
大家想想什么情況下你會(huì)抱怨代碼沒(méi)有注釋?
那么這就很好理解我們什么時(shí)候需要注釋了:
4/10 配置分離
對(duì)于組件級(jí)別的差異,很多時(shí)候 Props 就夠了,如果是多個(gè)組件組成的一個(gè)模塊需要差異化的話,你可能會(huì)使用多級(jí)參數(shù)透?jìng)骰蛘呤庆o態(tài)變量的方式,不過(guò)我想告訴你大多數(shù)時(shí)候可能 Provider 更合適,尤其是你希望同時(shí)在一個(gè)應(yīng)用生命周期里使用幾種配置方案。
其實(shí)大家對(duì) Provider 并不陌生,不管是 Angular 或者是 React 實(shí)際上都可以輕松使用 Provider ,如下
這里要特別說(shuō)一下,大家在 Angular項(xiàng)目中編寫(xiě)復(fù)用模塊時(shí),最好養(yǎng)成暴露 InjectToken 的習(xí)慣,即使它看起來(lái)暫時(shí)還不需要配置。
5/10 狀態(tài)管理
市面上關(guān)于全局狀態(tài)管理方案已經(jīng)比較成熟了,比如 Rxjs,Mobx,Redux... 今天我們不會(huì)再過(guò)多探討全局狀態(tài)管理,更多想要跟大家探討一下局部狀態(tài)管理。
首先問(wèn)大家一個(gè)問(wèn)題:局部狀態(tài)管理可以被復(fù)用么?或者說(shuō)應(yīng)該復(fù)用局部狀態(tài)管理代碼么?
再問(wèn)一個(gè)問(wèn)題:如果可以被復(fù)用,那么我們應(yīng)該使用組合還是使用繼承呢?
我的建議是:可以復(fù)用,但最好不要使用繼承,可以考慮組合。
實(shí)際上在 Angular 里,你只需要簡(jiǎn)單的從組件級(jí)別注入服務(wù)就可以復(fù)用局部狀態(tài)管理代碼了。是不是好簡(jiǎn)單?實(shí)際上這就是 MVP 架構(gòu)的實(shí)現(xiàn),這里的組件級(jí)別 Providers 充當(dāng)?shù)木褪?Presenter 層。
而在 16.8 版本 React 開(kāi)始,你可以使用 Hooks 來(lái)組合局部狀態(tài)管理,相信大家應(yīng)該都有聽(tīng)說(shuō)過(guò),我們實(shí)際用下來(lái)的感受是:好用是真的好用,坑也是真的多。
我們?cè)谶^(guò)去使用過(guò)程中也沉淀了自己的 Hooks 庫(kù)(@bixi/hooks),大家有興趣可以拿去玩玩。
6/10 性能優(yōu)化
我對(duì)性能優(yōu)化的一貫看法是:最好的時(shí)機(jī)就是項(xiàng)目立項(xiàng)的時(shí)候,其次是現(xiàn)在。
大家最好還是能夠養(yǎng)成編寫(xiě)高性能代碼的習(xí)慣,以下是我們?cè)陂_(kāi)發(fā)過(guò)程中經(jīng)常會(huì)使用的一些性能優(yōu)化手段,不細(xì)說(shuō),大家可以自己挨個(gè)去研究。
7/10 版本管理
這里版本管理有兩個(gè)部分:
8/10 適度封裝
說(shuō)到封裝,我們先看下面這個(gè)代碼段變化過(guò)程
我在過(guò)去的代碼評(píng)審過(guò)程中發(fā)現(xiàn)很多同學(xué)會(huì)很喜歡編寫(xiě)上面這種代碼,他們都能給我一個(gè)很好的理由:函數(shù)拆得細(xì)好復(fù)用啊。
可是,你的代碼真的有被復(fù)用么?
實(shí)際上,在業(yè)務(wù)代碼開(kāi)發(fā)過(guò)程中,很多時(shí)候過(guò)度封裝反倒會(huì)提高復(fù)雜度降低可維護(hù)性,因此我經(jīng)常跟大家說(shuō)的一句話是“你把它搞復(fù)雜了”。
我們可以試試用最笨最簡(jiǎn)單的方式改造一下上面這個(gè)代碼如下
大家會(huì)發(fā)現(xiàn),這段代碼行數(shù)看起來(lái)變多了,但是維護(hù)起來(lái)變得特別簡(jiǎn)單,我在處理 a 邏輯時(shí)候我才不關(guān)心會(huì)不會(huì)影響到 b/c/d...
9/10 組件設(shè)計(jì)
我們來(lái)看下面這個(gè)組件設(shè)計(jì)需求,這是我們?cè)趹?yīng)用里的一個(gè)真實(shí)組件,它有以下四個(gè)主要特性:
有些同學(xué)的組件拆解原則是:管它三七二十一,先拆到它不能再拆為止。
那么在遇到上面這個(gè)組件設(shè)計(jì)需求的時(shí)候它會(huì)拆解成下面左邊這樣,甚至粒度更細(xì),那么拆成這樣有什么問(wèn)題呢?
很明顯,不是組件拆得越細(xì)就越好,因此們更推薦你像右圖這樣拆解成兩個(gè)組件就夠了(當(dāng)然如果你的代碼復(fù)雜的話,可以再適當(dāng)拆解)。
我們?cè)倏聪旅孢@個(gè)簡(jiǎn)易的 Input 組件,看看它有幾宗罪:
2. 組件不具備復(fù)用性,產(chǎn)生了一個(gè)searchTasks 事件
3. 組件暴露了實(shí)現(xiàn)細(xì)節(jié),需要依賴(lài)外部的 validate 方法
4. 組件在初始化時(shí)拷貝了 value 副本,之后并沒(méi)有繼續(xù)監(jiān)聽(tīng)外部 value 變化刷新副本,導(dǎo)致不是數(shù)據(jù)驅(qū)動(dòng)的,不是冪等的
10/10 防御式編程
編寫(xiě)更可靠的代碼當(dāng)然應(yīng)該是我們的長(zhǎng)期追求,但一些不好的編程習(xí)慣可能會(huì)讓你的代碼問(wèn)題變得難以追蹤,你的錯(cuò)誤監(jiān)控工具(我們使用 sentry 監(jiān)控代碼錯(cuò)誤)可能會(huì)變得形同虛設(shè),比如上圖中的代碼:
// bad if (this.editor) {this.editor.destory(); }// bad,等價(jià)于上面代碼 this.editor?.destory();之所以說(shuō)它不好,因?yàn)槲覀冎涝谶@個(gè)組件掛載之后,this.editor 是應(yīng)該必然存在的,那么我們?cè)谛遁d的時(shí)候如果不存在我們應(yīng)該及時(shí)拋出錯(cuò)誤,而不是悄無(wú)聲息吞噬掉,更好的處理方式應(yīng)該是下面這種方式:
// good,我們斷定它一定存在 (this.ediotor as Editor).destory();總得來(lái)說(shuō),你最好還是謹(jǐn)慎使用 lodash 或者 optional chaining,該拋錯(cuò)的地方就讓它及時(shí)拋出來(lái)。
當(dāng)然為了不讓你的應(yīng)用奔潰得太難看,你還是需要做好錯(cuò)誤收集以及 UI 降級(jí)。
謝謝大家,如有錯(cuò)誤,請(qǐng)不吝賜教,你可以在這里聯(lián)系到我。
PPT 可以在這里下載到:
http://cdn.liguo.run/share/clean_code.keyhttp://cdn.liguo.run/share/clean_code.md
總結(jié)
以上是生活随笔為你收集整理的如何编写无法维护的代码_编写可维护的前端代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python语法速成方法_30分钟学完P
- 下一篇: 谷歌浏览器禁止右滑返回历史_移动端h5禁