代碼質(zhì)量第3層-可讀的代碼!

導(dǎo)語?|?騰訊云加社區(qū)精品內(nèi)容欄目《云薦大咖》,特邀行業(yè)佼者,聚焦前沿技術(shù)的落地與理論實(shí)踐,持續(xù)為您解讀云時(shí)代熱點(diǎn)技術(shù),探秘行業(yè)發(fā)展新機(jī)。

可讀的代碼能極大地提高開發(fā)效率。在開發(fā)的過程中,有很大一部分時(shí)間是在閱讀代碼。可讀的代碼,容易理解,也容易改。反之,不可讀性的代碼,讀起來心情很差,改起來也容易出錯(cuò)。
下面是一段不可讀的代碼:
const user = ...const foo = (cb) => ...const bar = (list, cb) => ...const other = (list1, list2) => ...if(user ? user.isAdmin : ((user.permission && user.permission.view) ? user.permission.view === true : false)) {foo((res) => {if(res && res.ok && res.list && res.list.length > 0) {bar(res.list, (res2) => {if(res2 && res2.ok && res2.list && res2.list.length > 0) {other(res.list, res2.list)}})}})}
以上代碼有這些問題:
函數(shù)的命名和功能不符。
if條件太復(fù)雜,而且嵌套深。
回調(diào)函數(shù)嵌套深。
將上面的代碼改成可讀的代碼:
const user = ...const fetchChannel = () => ...const fetchArticle = (list) => ...const render = (channelList, articleList) => ...const hasViewPermission = user.isAdmin || user.permission?.viewif(!hasViewPermission) {return}const { ok, list: channelList} = await fetchChannel()if (!(ok && channelList?.length > 0)) {return}const { ok: fetchArticleOk, articleList } = await fetchArticle(channelList)if (!(fetchArticleOk && articleList.length > 0)) {return}render(channelList, articleList)
總結(jié)來說,可讀的代碼主要有如下的特點(diǎn):
一致的代碼風(fēng)格。
合理的命名。
必要的注釋。
沒有大文件。
沒有嵌套很深的代碼。
如何寫出可讀代碼?寫出可讀代碼,要滿足上面提到的特點(diǎn)。
一、一致的代碼風(fēng)格
一致的代碼風(fēng)格指:空格,縮進(jìn),命名風(fēng)格(駝峰,中劃線等)等在整個(gè)項(xiàng)目里是一致的。一致的代碼風(fēng)格,看起來很整齊,也能減少理解成本。在項(xiàng)目中,用怎樣的代碼風(fēng)格不重要。重要的是,風(fēng)格要一致。
前端業(yè)界比較有名的代碼風(fēng)格有:Airbnb JavaScript Style Guide和JavaScript Standard Style。如若不想折騰,可以使用JavaScript Standard Style。
JavaScript Standard Style的特點(diǎn):
無須配置。史上最便捷的統(tǒng)一代碼風(fēng)格的方式,輕松擁有。
自動代碼格式化。只需運(yùn)行standard--fix從此和臟亂差的代碼說再見。
提前發(fā)現(xiàn)風(fēng)格及程序問題。減少代碼審查過程中反反復(fù)復(fù)的修改過程,節(jié)約時(shí)間。
確定了代碼風(fēng)格后,可以用檢查工具ESLint(https://eslint.org/)來保證代碼風(fēng)格的統(tǒng)一。每次代碼提交前,做檢查,可以用工具:husky(https://www.npmjs.com/package/husky)。對于大項(xiàng)目,檢查整個(gè)項(xiàng)目太慢。用lint-staged(https://www.npmjs.com/package/lint-staged)只檢查本次改動的文件。
二、合理的命名
There are only two hard things in Computer Science: cache invalidation and naming things.?(計(jì)算機(jī)科學(xué)中只有兩件事很難:緩存失效和命名。--Phil Karlton)
好的命名是“看其名而知其意”。
(一)好的命名
有如下特點(diǎn):
直白的,有意義的
好的命名是易于理解的,也就是直白的,有意義的。比如:fetchUserInfo。
推薦:故宮命名法(提取目標(biāo)對象的關(guān)鍵特征來命名)
推薦命名工具:CODELF。它幫你搜索Github、GitLab等網(wǎng)站中,你想查找的內(nèi)容的不同命名。
注意,命名中的單詞不要拼錯(cuò)。推薦單詞拼寫檢查工具:Code Spell Checker。
遵循行業(yè)慣例
好的命名也應(yīng)該遵循行業(yè)的習(xí)慣慣例。如:業(yè)界慣例id作為唯一標(biāo)識命名,不要用identifier。i、j、k用來做循環(huán)計(jì)數(shù)變量命名。
符合代碼風(fēng)格
好的命名應(yīng)該符合當(dāng)前項(xiàng)目的代碼風(fēng)格。如:駝峰風(fēng)格等。
(二)不好的命名
有如下特點(diǎn):
無意義的名字
無意義的名字,如:foo,bar,var1,fun1。
太過抽象的名字
太過抽象的名字,如:data,res,temp,tools。
會有歧義的簡稱
會有歧義的簡稱,如:mod。你可能無法確認(rèn)到底是mode或module。
不必要的上下文信息
// badfunction async getUserName (userId) {const userName = await fetchUser(userId)return userName}// goodfunction async getUserName (id) {const name = await fetchUser(id)return name}
太長的名字
太長的名字,不容易理解。如:fetchGraintYoungestBoyName。優(yōu)化方式:將不重要內(nèi)容省略掉。如改成:fetchBoyName。
三、必要的注釋
注釋是是對代碼的解釋和說明。好的代碼是自我解釋的。對于不復(fù)雜的代碼,不需要注釋。如果寫的注釋,只是解釋了代碼做了什么,不僅浪費(fèi)讀者的時(shí)間,還會誤導(dǎo)讀者(注釋和代碼功能不一致時(shí))。
需要寫注釋的場景:
當(dāng)代碼本身無法清晰地闡述作者的意圖。
邏輯比較復(fù)雜。
(一)沒有大文件
大文件指:代碼行數(shù)很多(超過1千行)的文件。大文件,意味代碼做了很多事,很難跟蹤到底發(fā)生了什么。
可以用ESLine中max-lines規(guī)則來找出大文件。
優(yōu)化方案:按功能,將大文件拆分成若干小文件。
(二)沒有嵌套很深的代碼
嵌套很深的代碼,可讀性很差,讓人產(chǎn)生“敬畏感”。比如:
fetchData1(data1 =>fetchData2(data2 =>fetchData3(data3 =>fetchData4(data4 =>fetchData5(data5 =>fetchData6(data6 =>fetchData7(data7 =>done(data1, data2, data3, dat4, data5, data6, data7))))))))
下面是幾種常見的嵌套很深的場景。
回調(diào)地獄
用回調(diào)函數(shù)的方式來處理多個(gè)串行的異步操作,會造成嵌套很深的情況。俗稱“回調(diào)地獄”。如:
fetchData1(data1 =>fetchData2(data2 =>fetchData3(data3 =>done(data1, data2, data3))))
if嵌套很深
在條件語句中,如果判斷條件很多,會出現(xiàn)嵌套很深或判斷條件很長的情況。比如,判斷一個(gè)值是否是: 1到100之間,能被3和5整除的偶數(shù)。這么寫:
const isEvenNum = num => Number.isInteger(num) && num % 2 === 0const isBetween = num => num > 1 && num < 100const isDivisible = num => num % 3 === 0 && num % 5 === 0if (isEvenNum(num)) { // 是偶數(shù)if(isBetween(num)) { // 1 到 100 之間if(isDivisible(num)) { // 能被 3 和 5 整除return true}return false}return false}return false
三元表達(dá)式也會出現(xiàn)嵌套很深的情況:
a > 0 ? (b < 5 > ? (c ? d : e) : (f ? g : h)) : (i ? j : k)函數(shù)調(diào)用嵌套
執(zhí)行多個(gè)函數(shù)調(diào)用,每個(gè)函數(shù)輸出是下個(gè)函數(shù)的輸入,會造成很深的嵌套。如:
// 模擬炒蛋的過程:買蛋 -> 打蛋 -> 炒蛋 -> 上桌。toTable( // 第四步: 上桌fry( // 第三步: 炒蛋handle( // 第二步: 打蛋buy(20) // 第一步: 買蛋)))
React高階組件嵌套
在React寫的應(yīng)用中,會出現(xiàn)一個(gè)組件被很多個(gè)高階組件(HOC)包裹,造成嵌套很深的情況。如:
class Comp extends React.Component {...}Wrapper5(Wrapper4(Wrapper3(Wrapper2(Wrapper1(Comp)))))
React Context嵌套
在React寫的應(yīng)用中,可以用Context來管理子組件間的數(shù)據(jù)共享。如果共享數(shù)據(jù)很多,而且類型不同,容易造成頂部組件Context嵌套很深。如:
<PageContext.Providervalue={...}><User.Providervalue={...}><Project.Providervalue={...}><ChildComp />Project.Provider>User.Provider>PageContext.Provider>
優(yōu)化方案見:
https://www.yuque.com/fegogogo/fe/rnr74g
四、總結(jié)
符合本文提到的可讀代碼特點(diǎn)的代碼,可讀性都不會差。當(dāng)然,還有很多能提升代碼的可讀性的技巧。比如:
限制函數(shù)的參數(shù)數(shù)量。
限制函數(shù)的圈復(fù)雜度。
禁用with語句。
要了解更多提升代碼可讀性的技巧,推薦擼一遍ESLint的規(guī)則(https://eslint.org/docs/rules/)。代碼質(zhì)量的下一層次就是:可復(fù)用的代碼。將會在下一篇文章中介紹。
?推薦閱讀
代碼質(zhì)量第5層-只是實(shí)現(xiàn)了功能
聊聊代碼質(zhì)量-《學(xué)得會,抄得走的提升前端代碼質(zhì)量方法》前言
公司的電腦為什么卡——因?yàn)槿鄙俟こ處熚幕?/span>

??戳「閱讀原文」一鍵訂閱《云薦大咖》專欄,看云端技術(shù)起落,聽大咖指點(diǎn)迷津!云薦官將在每周五抽取部分訂閱小伙伴,送出云加視頻禮盒!
