【轉】如何避免同事踩到自己的坑?
元件化開發最佳實踐
轉自微信公眾號:超級全棧架構師
現在前端的元件化開發基本上已經成為主流了,既然已經是元件化開發了,那麼自然就要遵守一些元件化開發的最佳實踐。
每個元件檔案程式碼總行數不要超過400行
至於為什麼是400
行,這個數字是我當初在某篇技術文章上看到的,至於是哪個我忘記,但是不知為何印象很深刻,一直記著,並且根據我多年具備的經驗來看,這個數字還是蠻準確的,一般要麼不會超過這個數,或者在這個數字左右徘徊,要麼就是比這個數字大很多的。
這些都很好理解,既然是元件化開發,那麼如果一個元件檔案體積太大,存在幾十個方法、幾十個data
資料(如果用的是vue
框架),那就說明這個元件大概率包含的功能點太多,是可以被繼續細化出多個單一功能的子元件的。
太多的方法與data
資料,對於開發者來說根本就是一種折磨,別的不說,單是給這些方法起名字以及查詢都是一件麻煩事,至於日後的維護,那更是噩夢一般的存在,哪怕是當初親手寫下這些程式碼的程式猿,過一段時間再讓他來維護這些程式碼,他十有八九都要如履薄冰,儘管確認了好多遍,但還是會擔心自己修改某個資料或者某個方法,會不會對以前某個較為隱蔽的邏輯造成什麼不可預知的破壞,於是一遍又一遍地在幾十個方法與資料間測試、檢查。
我曾經看到過包含七十多個data
的元件
這種情況還算是好的,碰到不負責任的程式猿,可能直接就嫌麻煩,隨便在一大堆程式碼中找個地方寫下自己的新程式碼,檢查都不檢查就立馬提測上線了,因為反正以前上了那麼多需求,不小心搞亂了其中某個需求的邏輯,誰特麼知道?
這還僅僅是往舊程式碼中加新程式碼罷了,如果是下線之前過期的活動程式碼,或者刪除無用的邏輯,那就更讓人掉頭髮了,幾十個方法、幾十個資料纏在一起,誰知道哪些方法與方法、方法與資料、資料與資料間有著怎樣的聯絡?刪掉了這段看起來應該是無用的程式碼,會不會導致其他有用程式碼出什麼故障?算了算了,不刪了,反正後端介面已經關掉,這段程式碼也顯示不出來,就暫時放在上面吧。 殊不知,這所謂的暫時,就是海枯石爛的永遠。
爺爺爺爺,這段程式碼真的是你寫的嗎?
於是,頁面上無用程式碼越來越多,舊程式碼與新程式碼交相輝映,檔案體積迅速增大,隨著時間的推移,本來輕裝上陣的新鮮程式碼庫,逐漸背上了一個又一個歷史包袱,然後被後面接手的人大罵,這寫的到底是個什麼東西?
世上本沒有歷史包袱,丟包袱的人多了,也就有了歷史包袱。
每個函式不要超過 100行
這是接上面的,想想也能明白,一個元件最好不超過 400行,只是一個方法就超過了100
行,那還怎麼寫? 當然,這裡的數字100根據我的經驗,也是蠻準確的,超過這個行數,好好想下,這個方法是不是包含了太多邏輯,遵循函式功能單一原則,不要讓一個方法函式包含過多的邏輯功能,是用於拉取資料,那就讓它只拉取資料,是用來整合欄位的那就讓它只整合欄位,別幹其他的事情。
多麼性感的臀部啊,答應我,別用它來拉shi好嗎?
這樣一來,方法函式不僅功能明確,維護起來不必畏手畏腳,同時也能夠增加方法的複用性,例如,A方法中包含了拉取頁面基本資料的功能,後來需求迭代,另外一個後來新增的 B方法也需要拉取頁面基礎的功能,剛想複用之前 A方法,發現 A方法中除了拉取資料的程式碼,還有判斷是否顯示彈窗、修改資料欄位、埋點等多個用if...else...
連起來的程式碼,於是寫 B方法的人為了避免麻煩,或者發現根本無法複用,只好又把拉取資料的功能重寫了一遍。
當然,這只是一個原則,實際需求中,如果某兩個功能邏輯就是緊密聯絡不可分割的,你也可以寫在一個方法中,總之,在參考這個原則的前提下,靈活變通。
定義方法與資料要物以類聚
指具備相似能力或者共同作用於某個功能的方法和資料,最好定義在靠近的位置,方便查詢,某個功能受到那些方法和資料所控制,某個資料體包含哪些欄位全都一目瞭然,無論是以後要修改還是刪除,都能做到胸有成竹,不會遺漏。
像我這麼 diao的還有 107個
例如,生命週期方法寫在一起,並且按照執行的順序,不與自定義方法混插;自定義方法也要按照各自的功能進行歸類書寫,例如請求介面的方法寫到一起,切換頁面彈窗的方法寫到一起,頁面上與業務無關的工具方法要放到一起,使用者資訊的資料都定義在靠近的位置,如果你用的是 vue,那麼與模板渲染無關的變數,不要放到 data中,既提升了渲染效能,也易於區分變數的功能。
// 工具方法都放到一起
// 金額 分 轉 元
fen2yuan(fenNum) {
return fenNum / 100
}
// 修改連結協議
changeUrl(url) {
return url.replace('http', 'https')
}
// 使用者資訊的資料都定義在靠近的位置
// 當然,你甚至可以把下面這些資料,都定義在一個大的物件中
let avatar = 'https://avarat.com/a.png'
let userName = '小明'
let age = 19
順帶一提,CSS
的書寫最好也按照這種規則,CSS
書寫順序的判斷依據是css
程式碼影響的方面,例如,影響元素位置的屬性left top
,影響元素長相的color background
,字型font-size font-weight
,這些有同類功能的最好都寫在一起。
另外,我習慣於把能引起頁面迴流的放在能引起頁面的重繪屬性的前面,對元素影響程度越高的屬性越放在最前面,至於什麼叫影響程度我也說不清楚,下面是我一般寫 css的屬性的順序,大家可以自行領悟一下。
<!-- position 放在最前面 -->
position: relative;
<!-- 然後是參照 position 進行定位的屬性 -->
top: 100px;
left: 10px;
<!-- 然後是寬高 -->
width: 100px;
height: 100px;
line-height: 20px;
<!-- 然後是 margin padding -->
margin: 10px;
padding: 20px 30px;
<!-- font -->
font-size: 20px;
font-weight: 700;
<!-- border -->
border: 1px solid red;
border-radius: 4px;
<!-- background -->
background-color: pink;
<!-- z-index -->
z-index: 10;
一開始我也不習慣這種略帶有束縛的寫法,但是時間長了就習慣了,這些屬性的書寫順序,完全不用思考就迅速依次寫下,甚至有時候看到別人寫的亂七八糟不符合自己習慣的寫法,還會順手改一改。
這樣做的好處是易於查詢和維護,想改width
,那就肯定是在這個元素css
屬性序列的最前面,想改background
,那肯定就從後面掃,也避免了在元素屬性過多的情況下,可能導致的某個屬性出現多次的情況,我曾經不止一次在程式碼庫中看到某個屬性,例如background
寫了兩次的事情,一個background
寫在屬性序列的上半部分,然後可能由於這個元素的屬性太多,後來維護的人沒有看到那個backgound
,或者太亂了也不想看,於是就直接在屬性序列的最後面又寫了一個。
拋開上述的好處不說,最起碼這種有條理、有順序的書寫次序,對於某些星座的人來說,看著也賞心悅目,無形之中也能讓自己與外面那些妖豔賤貨區別開來
努力學習卻不裝逼,那將毫無意義
必要而準確的註釋
需要長期維護的專案,必要而準確的註釋必不可少(但不是讓你寫小說)
一行程式碼寫下去,或許你三天之內還能知道自己當初為什麼這麼寫,裡面的變數代表什麼意思,有什麼作用,但是一個月後呢,半年後呢,一年後呢?就算你記得,其他人呢?
我不知道這段程式碼的作用是什麼,但是把它刪掉程式就不正常了
如果讓你維護一段你早就不記得是誰寫的,也不知道到底代表什麼意思程式碼,你要做的第一件事肯定就是先看程式碼,弄明白到底什麼意思,然後才能進行後續維護,而對於某些較為複雜的邏輯程式碼,例如網站首頁,裡面嵌入了數十個彈窗,這數十個彈窗分別對應數十個功能點,讓你修改其中某個 A彈窗的彈出邏輯,並且還要與另外某個 B彈窗進行優先順序配合,光是弄明白A、B彈窗的邏輯你都要花費不少時間吧?
而如果在這段程式碼上面就有一行對這行程式碼的準確註釋,就算你稍後還是需要讀一遍程式碼,也肯定比沒有程式碼時,你理解的更快更準確。
一段程式碼節約一點時間,那麼十段呢?一個檔案的程式碼呢?整個程式碼庫的程式碼量呢?
所以千萬不要覺得註釋可有可無,另外,你寫的程式碼是給人讀的,而且是技術人員,所以要用人能聽懂的語句進行描述,如果你的描述能力不足以讓你在一行內描述清楚,那麼就再加一行,總之一定描述清楚,一知半解的註釋還不如不寫,現在基本上都接自動化打包編譯工具(例如 webpack),註釋這些東西最後在專案釋出階段都會自動刪掉,也不會佔用程式碼體積。
變數和方法的命名最好有一定的規律
同樣是為了更好的閱讀體驗,也是為了避免過渡註釋,最好的註釋就是讓程式碼自己“說出”自己的作用,即命名要有規律性。
例如,用於儲存陣列的變數以List
作為名字字尾,用於某種資訊的物件變數以Info
作為名字字尾,用於判斷某種邏輯的變數以is
最為字首,這樣一眼看上去就知道變數的大致功能,避免出現讓object
型別的物件呼叫map
的蠢事。
const namesList = ['xiaoming', 'xiaohong']
const userInfo = {
name: 'John',
age: 20,
gender: 'male'
}
const isEven = 10 % 2 === 0
方法的命名同上,另外,為了更容易體現出方法的功能點,方法的名稱最好不要太“寬泛”,例如拉取資料的方法,最好不要命名為類似getData
這種,因為如果這個元件中還存在其他的拉取資料方法,就容易讓人迷惑,不會那麼一下子就知道這個getData到底是針對哪個資料介面的,應該進一步精確到相應的介面,例如命名為getUserData
。
關於如何給變數以及方法命名這件事,存在很多細緻的解決方案,能寫兩篇文章出來,就不一一展開了。
通用的功能要封裝成元件
通用的元件,最好確定好功能點後,封裝成統一的通用元件,通用元件一定要覆蓋大部分的通用功能點,否則還不如不要。
大一點的,例如彈窗,可能包括標題、關閉按鈕、內容、遮罩層這些結構,小一點的,例如頁面的遮罩層,主體包括一層遮罩樣式,那在封裝這個元件的時候,就要把這些結構考慮進去。
我曾經看到過某個元件中,包含了多個彈窗,但是沒有封裝通用的彈窗元件,於是相同的元素以及樣式被寫了好幾遍,印象最深刻的是遮罩層元素也出現了好幾遍,每個彈窗都專門寫一個遮罩層樣式,寫過的人都知道,單是這一個遮罩層的樣式都有好幾行了,何苦來哉。
DRY原則
Don't repeat yourself
這一條可以與上條結合使用,無論你是寫什麼程式碼,需求是長期還是短期的,作為一個有素質的程式猿,你最好都要遵守這條規則
這裡的DRY
,不僅是指完全一模一樣的程式碼字母,還在於同樣的邏輯,這裡舉個例子,表單驗證是很常見的場景,一般的驗證方法都是不假思索的數個乃至是數十個if
語句依次排列,整整齊齊氣勢驚人,但問題是幾個if
語句連在一起或許不太明顯,難以觸碰到你的G點
,但是十幾個乃至是幾十個if
語句堆在一起,你難道還能不覺得彆扭嗎?
if (age > 19) {
// ...
}
if (name.indexOf('zhao')) {
// ...
}
if (gender === 1) {
// ...
}
if (weight > 75) {
// ...
}
// 無窮無盡
// ...
只要我複製貼上得足夠快,bug就追不上我
關於表單驗證這個東西,有篇文章寫得很好,大家可以參考一下
單獨的業務程式碼之間、業務程式碼與非業務程式碼之間進行必要的隔離
業務程式碼,以if...else
琳琅滿目為主要標誌之一,多個需求的業務程式碼混合在一起那就意味著數倍琳琅滿目的if...else
,如果再在這些if...else
中躲貓貓般穿插進非業務程式碼,那麼恭喜你,如今你得到的這份程式碼,其實有個流傳於江湖已久的響噹噹名號:義大利麵條式程式碼。
隔離單獨的業務程式碼,能夠直接減少頭髮掉落數,能間接降低 bug出現率,這很好理解。
一般的專案都是長期迭代而來,一個頁面上可能堆積了數十個需求的功能點,每個功能點都對應幾大段的方法和資料,並且這些需求還可能從誕生到現在被修改了數次,搞不好有的業務間還存在相互依賴與重疊
如果把這些業務包含的方法和資料全都放在一起,那酸爽……誒,這個方法是屬於A需求的嗎?如果是那為什麼這裡面還包含了B需求的資料?怎麼這裡還呼叫了C需求的方法?C需求的這個方法修改的這個資料是C需求的嗎?看起來不太像啊?算了算了,應該是,先這樣寫吧,等測試提 bug了再說……
下班晚不是因為你需求多,而是你自己寫得程式碼給你找的事多
與位置無關的元素聚集書寫
如果頁面上元素少的話,或許沒有區別,但是當頁面上存在大量元素,例如網站首頁、商品詳情頁這些比較重要的頁面,就很容易感覺出來了
像modal
彈窗、toast
等輔助性元素,頁面上可能存在好多個,這種元素一般與位置無關,樣式設定的都是position: asbolute
或者position: fixed
;,無論放在哪個位置基本上都ok
,那麼建議找好一個固定的位置聚集存放這些元素,例如頁面的頂部或者底部,並寫好註釋,方便尋找與修改,也方便統計,任意穿插在各種 DOM間,維護起來都是一件麻煩事。
<!-- 新手好禮的引導彈窗 -->
<ModalA />
<!-- 領獎彈窗 -->
<ModalB />
<!-- 新人提示 -->
<ToastA />
<!-- 資質不夠提示 -->
<ToastB />
<!-- 風險使用者提示 -->
<ToastC />
刪掉不必要的程式碼
包括無效程式碼、註釋的程式碼、不必要的除錯程式碼
做過活動頁的應該都知道,這種活動運營頁壽命一般都很短,但是所要付出的精力卻不少,活動下線後代碼可能就直接無效了,大部分情況下這些失效的活動程式碼不會對頁面有效邏輯產生什麼影響,所以趕著進入下一個需求的程式猿們,本著多一事不如少一事的原則,很可能就任由無效程式碼一直存在於頁面,直到地老天荒,這種事情最起碼從我的經歷來看,很常見
有的需求在開發階段頻繁變動,辛辛苦苦寫的邏輯還沒來得及被上傳到線上伺服器就夭折了,惱羞成怒的程式猿不甘心掉落的頭髮連一點成果都沒有留下,於是機智地按下了 Ctrl + /快捷鍵,幻想著這段被封印的程式碼總有重見天日的那天,殊不知,又是直到地老天荒,這種事情最起碼從我的經歷來看,很常見
雖然某些自動化打包編譯工具支援刪除類似於console.log
、debugger
之類的除錯程式碼,但問題是在 dev階段這些程式碼都是存在的,每個人在每個需求中都加入 5個console.log
,那麼只需要十個人次,控制檯上就可以出現50
行console.log
,特別是這些console.log
基本上都是秉持著哪裡有位置就寫在哪裡的原則,就算是想刪也需要耗費大量時間,苦逼的程式猿什麼都沒幹,先在控制檯看到幾十行別人寫的console.log
,然後在一堆console.log
中找到自己需要的那個,並在需求完成時,順便又在原先幾十個console.log
的基礎上,又貢獻了自己的一份力量,這種事情最起碼從我的經歷來看,很常見。
編輯器每多顯示一行無效程式碼、控制檯每多輸出一行console.log
,都將耗費一定的電量,全球幾千萬的程式設計師,積少成多,足以加重全球溫室效應,加速兩級冰川融化,可憐的北極熊寶寶和企鵝寶寶找不到爸爸媽媽,人間有真情人間有真愛,請堅定地按下backspace
或delete
吧。
良好的溝通,避免無效的產出
永遠不要相信 PM說的這個需求肯定不會再變了的話
PM改變需求我們無法勸阻,但是我們可以明確現有的需求,不寫無效的程式碼,從而進一步避免了大段註釋,保住了更多的頭髮,不要等到快上線的時候,才發現自己好像弄錯了某段邏輯,或者少寫了某段邏輯,最可恨的是白寫了某段邏輯。
程式碼容錯 這個世界上不存在沒有 Bug的程式碼,只要你寫的不是 demo,那麼一定要做好程式碼容錯處理,因為你永遠不知道介面給你返回的是 | 還是 丨(親身經歷,此梗參見有哪些讓你目瞪口呆的 bug?)
還有一些可能不叫錯誤,例如頁面資料的初始化,在獲取到資料之前,頁面上可能會渲染出undefined
這種鬼東西,為了更好的使用者體驗,最好還是給個體驗更好的初始值吧
遵守制定好的規範
多人協作規範很重要,無論是jshint
還是eslint
大行其道就是明證
對於一個專案來說,規範可能包括從es版本、縮排型別到頁面結構、元件拆分等,你既可以直接照抄成熟的規範,也可以自定義規範,但無論是什麼樣的規範,只要制定下來,那就要遵守,不要因個人原因,與團隊貌合神離。
有種效應叫破窗效應,這個專案中出現了不一樣的風格,後來進入的人第一印象就是規範也不是那麼嚴謹,偶爾打破一下也是可以的,然後又有後來的人看到,第一印象就是……於是不同的風格越積越多,規範也就無從談起了,從一開始就穩不住,難道還想著半路突然硬一下?
你可以不喜歡專案現在的頁面結構劃分方式,也可以認為路由的劃分方式很SB,但既然你在當初制定這個規範時沒有反對沒有提出異議(無論是忘記了還是被無視還是沒有機會提,總之當初你沒反對),那麼現在就要遵守,或者你也可以嘗試著改變這個規範,但請做好善後事宜,例如確保在新規範制定後,專案中之前存在的老規範要被全部修改過來,堅決保證程式碼風格統一化,至於是誰來做這種吃力不討好的事情,你懂得。
自己挑的shi,跪著也要吃完
定期的程式碼 review
旁觀者清,自己一般很難發現自己的錯誤,否則的話也不會那麼容易觸犯了,無論是程式碼的規範還是邏輯的偏差,有些錯誤如果其他人不及時指出來,我們可能永遠無法意識到,定期的程式碼 review就能夠很好地解決這個問題。
不過,程式碼雖好,也沒必要 review得太頻繁了,不然只會變成一種負擔,大家都這麼忙,誰沒事幫你天天 review程式碼。
必要的踩坑與經驗文件
這個很重要
,無論你用的是什麼框架,寫的是小程式還是webview
,肯定都存在著各種各樣的坑,如果老舊程式碼有歷史包袱不想搞的話,那麼新啟專案就要好好對待了,文件改寫的就要寫,不要怕麻煩,不過等到新人加入,或者乾脆是移交他人的時候,你就知道什麼叫 敲碼一時爽,交接火葬場了。
小結
曾經看到過某個帖子,大意是 題主認為大部分前端做的事情,其實無論是五年經驗還是外包或者實習生都能做,憑什麼五年經驗人拿得工資就高?
我當時也是閒得蛋疼,回答:這個問題不僅僅是在前端,後端、客戶端等各種領域都會給你這種錯覺,我以前也是這麼認為的,後來某天當我看到一個相同的 bug,組長看了一眼就準確定位並順手解決,而實習生抓耳撓腮了大半天,不僅沒能明白到底是怎麼回事,甚至還多引入了幾個 bug後,我就知道人家那麼多錢不是白拿的。
當然,大部分工作經驗高拿錢多的人並不是僅僅是靠改 bug快這一個原因,另外也不排除有些人本來就天賦異稟,只工作了一年但能力抵得上別人工作三年的,而有些人工作三年只有一年的能力。