代码质量第3层-可读的代码!

共 4785字,需浏览 10分钟

 ·

2022-01-13 12:48


导语 | 腾讯云加社区精品内容栏目《云荐大咖》,特邀行业佼者,聚焦前沿技术的落地与理论实践,持续为您解读云时代热点技术,探秘行业发展新机。



可读的代码能极大地提高开发效率。在开发的过程中,有很大一部分时间是在阅读代码。可读的代码,容易理解,也容易改。反之,不可读性的代码,读起来心情很差,改起来也容易出错。


下面是一段不可读的代码:


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) } }) } })}


以上代码有这些问题:


  • 函数的命名和功能不符。


  • if条件太复杂,而且嵌套深。


  • 回调函数嵌套深。


将上面的代码改成可读的代码:


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)


总结来说,可读的代码主要有如下的特点:


  • 一致的代码风格。


  • 合理的命名。


  • 必要的注释。


  • 没有大文件。


  • 没有嵌套很深的代码。


如何写出可读代码?写出可读代码,要满足上面提到的特点。


一、一致的代码风格


一致的代码风格指:空格缩进命名风格(驼峰,中划线等)等在整个项目里是一致的。一致的代码风格,看起来很整齐,也能减少理解成本。在项目中,用怎样的代码风格不重要。重要的是,风格要一致。


前端业界比较有名的代码风格有:Airbnb JavaScript Style GuideJavaScript Standard Style。如若不想折腾,可以使用JavaScript Standard Style


JavaScript Standard Style的特点:


  • 无须配置。史上最便捷的统一代码风格的方式,轻松拥有。


  • 自动代码格式化。只需运行standard--fix从此和脏乱差的代码说再见。


  • 提前发现风格及程序问题。减少代码审查过程中反反复复的修改过程,节约时间。


确定了代码风格后,可以用检查工具ESLint(https://eslint.org/)来保证代码风格的统一。每次代码提交前,做检查,可以用工具:husky(https://www.npmjs.com/package/husky)。对于大项目,检查整个项目太慢。用lint-staged(https://www.npmjs.com/package/lint-staged)只检查本次改动的文件。



二、合理的命名


There are only two hard things in Computer Science: cache invalidation and naming things. (计算机科学中只有两件事很难:缓存失效和命名。--Phil Karlton)


好的命名是“看其名而知其意”。


(一)好的命名


有如下特点:


  • 直白的,有意义的


好的命名是易于理解的,也就是直白的,有意义的。比如:fetchUserInfo。


推荐:故宫命名法(提取目标对象的关键特征来命名)


推荐命名工具:CODELF。它帮你搜索Github、GitLab等网站中,你想查找的内容的不同命名。


注意,命名中的单词不要拼错。推荐单词拼写检查工具:Code Spell Checker


  • 遵循行业惯例


好的命名也应该遵循行业的习惯惯例。如:业界惯例id作为唯一标识命名,不要用identifier。i、j、k用来做循环计数变量命名。


  • 符合代码风格


好的命名应该符合当前项目的代码风格。如:驼峰风格等。



(二)不好的命名


有如下特点:


  • 无意义的名字


无意义的名字,如:foo,bar,var1,fun1。


  • 太过抽象的名字


太过抽象的名字,如:data,res,temp,tools。


  • 会有歧义的简称


会有歧义的简称,如:mod。你可能无法确认到底是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。优化方式:将不重要内容省略掉。如改成:fetchBoyName。



三、必要的注释


注释是是对代码的解释和说明。好的代码是自我解释的。对于不复杂的代码,不需要注释。如果写的注释,只是解释了代码做了什么,不仅浪费读者的时间,还会误导读者(注释和代码功能不一致时)。


需要写注释的场景:


  • 当代码本身无法清晰地阐述作者的意图。


  • 逻辑比较复杂。


(一)没有大文件


大文件指:代码行数很多(超过1千行)的文件。大文件,意味代码做了很多事,很难跟踪到底发生了什么。


可以用ESLine中max-lines规则来找出大文件。


优化方案:按功能,将大文件拆分成若干小文件。



(二)没有嵌套很深的代码


嵌套很深的代码,可读性很差,让人产生“敬畏感”。比如:


fetchData1(data1 =>  fetchData2(data2 =>    fetchData3(data3 =>      fetchData4(data4 =>        fetchData5(data5 =>          fetchData6(data6 =>            fetchData7(data7 =>              done(data1, data2, data3, dat4, data5, data6, data7)            )          )        )      )    )  ))


下面是几种常见的嵌套很深的场景。


  • 回调地狱


用回调函数的方式来处理多个串行的异步操作,会造成嵌套很深的情况。俗称“回调地狱”。如:


fetchData1(data1 =>  fetchData2(data2 =>    fetchData3(data3 =>      done(data1, data2, data3)    )  ))


  • if嵌套很深


在条件语句中,如果判断条件很多,会出现嵌套很深或判断条件很长的情况。比如,判断一个值是否是: 1到100之间,能被3和5整除的偶数。这么写:


const isEvenNum = num => Number.isInteger(num) && num % 2 === 0const isBetween = num => num > 1 && num < 100const isDivisible = num => num % 3 === 0 && num % 5 ===  0
if (isEvenNum(num)) { // 是偶数 if(isBetween(num)) { // 1 到 100 之间 if(isDivisible(num)) { // 能被 3 和 5 整除 return true } return false } return false}return false


三元表达式也会出现嵌套很深的情况:


a > 0 ? (b < 5 > ? (c ? d : e) : (f ? g : h)) : (i ? j : k)


  • 函数调用嵌套


执行多个函数调用,每个函数输出是下个函数的输入,会造成很深的嵌套。如:


// 模拟炒蛋的过程:买蛋 -> 打蛋 -> 炒蛋 -> 上桌。toTable( // 第四步: 上桌  fry( // 第三步: 炒蛋    handle( // 第二步: 打蛋      buy(20) // 第一步: 买蛋    )  ))


  • React高阶组件嵌套


在React写的应用中,会出现一个组件被很多个高阶组件(HOC)包裹,造成嵌套很深的情况。如:


class Comp extends React.Component {...}
Wrapper5( Wrapper4( Wrapper3( Wrapper2( Wrapper1(Comp) ) ) ))


  • React Context嵌套


在React写的应用中,可以用Context来管理子组件间的数据共享。如果共享数据很多,而且类型不同,容易造成顶部组件Context嵌套很深。如:


<PageContext.Provider  value={...}>  <User.Provider    value={...}  >    <Project.Provider      value={...}    >      <ChildComp />    Project.Provider>  User.Provider>PageContext.Provider>


优化方案见:
https://www.yuque.com/fegogogo/fe/rnr74g



四、总结


符合本文提到的可读代码特点的代码,可读性都不会差。当然,还有很多能提升代码的可读性的技巧。比如:


  • 限制函数的参数数量。


  • 限制函数的圈复杂度。


  • 禁用with语句。


要了解更多提升代码可读性的技巧,推荐撸一遍ESLint的规则(https://eslint.org/docs/rules/)代码质量的下一层次就是:可复用的代码将会在下一篇文章中介绍。



 推荐阅读


代码质量第4层——健壮的代码!

代码质量第5层-只是实现了功能

聊聊代码质量-《学得会,抄得走的提升前端代码质量方法》前言

公司的电脑为什么卡——因为缺少工程师文化!



👇「阅读原文」一键订阅《云荐大咖》专栏,看云端技术起落,听大咖指点迷津!云荐官将在每周五抽取部分订阅小伙伴,送出云加视频礼盒

浏览 36
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报