如何让代码的质量提升起来

525 阅读7分钟

写出优雅质量好的代码,应该统一好:规范命名注释代码优雅

如果把些执行起来,加上合理的code review,就不会让后面接手同事,或者过一段时间,突然大声质问这TM谁写的代码,这么烂,查一下log,不可能,怎么可能是我自己写的。

规范

要规范,则要如,Prettier、ESLint、stylelint,这些工具的辅助,在第一层就可以把习惯不统一的代码全部强制统一,eslint不通过,不让提交,在阅读代码层面上来看,就不会出现有的2个缩进,有的4个缩进,有的有分号,有的没有分号;

// 统一eslint
arrayUnique (array) {
  if (array && array.length) {
    return [...new Set(...array)]
  }
}

// 不要有多种风格,如这种
arrayUnique(array) 
  if(array && array.length) 
    return [...new Set(...array)]
  }
}
// 这种
arrayUnique(array) 
    if(array && array.length) 
    return [...new Set(...array)]
  }
}
// 这种
arrayUnique(array) {
  if(array&&array.length) 
    return [...new Set(...array)];
  }
}

统一风格,阅读代码就不会给人一种很凌乱的感觉。

统一自动格式化,遇到最坑的事,你的IDE会自动格式化,而同事又没有,或不按eslint来格,最后一提交全是冲突这种就没法进行下去了。人多的团队建议直接把eslint+githooks,不符合规范的代码直接不让提交。

附上几个大厂的前端规范

代码千万行,安全第一行;前端不规范,同事两行泪。

腾讯

tgideas.qq.com/doc/index.h… image

京东

guide.aotu.io/index.html image

百度

github.com/ecomfe/spec image

里面写了HTML、CSS、JavaScript的一些规范,有需要可以参考一下,给自己的团队定一份规范

命名

JavaScript

常规的命名规范有匈牙利命名法驼峰式命名法帕斯卡命名法

==匈牙利命名法==:变量名 = 类型 + 对象描述

  • array 数组 a
  • boolean 布尔值 b
  • float 浮点数 l
  • function 函数 fn
  • int 整型 i
  • object 对象 o
  • regular 正则 r
  • string 字符串 s
// 列表数据
let aListData = []
// 产品数据
let oProduct = {}

==驼峰式命名法==:变量名或函数名是由一个或多个单词连结在一起,其中第一个单词以小写字母开始,后面的所有单词的首字母都采用大写字母,这样的变量名看上去就像骆驼峰一样此起彼伏

const myName = 'haha'
const arrayUnique = (array) => {}

==帕斯卡命名法==:和驼峰式命名法类似,只不过第一个单词的首字母需要大写

const MyName = 'haha'
const ArrayUnique = (array) => {}

css

命名,100个人有100种命名,比如css,有用中划线-,下划线_,双下滑线的__,10个人写一个项目,css各种命名简直要不忍直视了,这算不算有强迫症。

常用的命名,像头部这种,命名就是header,像这种布局,我会来这样命名

<div class="header">
  <div class="h-left">
    <div class="h-l-back">返回</div>
    <div class="h-l-number">12</div>
  </div>
  <div class="h-center">标题</div>
  <div class="h-right">
    <div class="h-r-shopping">图标</div>
  </div>
</div>
.header{}
.h-left{}
.h-l-back{}

取父级元素第一个字母+当前内容,组成calss名,而取父级不超过三层,比如这样

<div class="main">
  <div class="m-left">
    <div class="m-l-nav">
      <ul class="l-n-list">
        <li class="n-l-item"></li>
      </ul>
    </div>
  </div>
  <div class="m-right"></div>
</div>

这种命名方式,让css名不容易起冲突,而每个人的命名风格都不一样,比如按 BEM 命名规范。

什么是 BEM 命名规范,Bem是块(block)、元素(element)、修饰符(modifier)的简写,由 Yandex 团队提出的一种前端 CSS 命名方法论。


注释

合理的注释,为什么要说合理的注释,有幸看到一个代码,200行的代码100行注释,好奇是写代码还是写文章的???注释应该帮助我们理解函数的目的,让我们不需要去看这个函数的具体逻辑和代码就可以快速的使用它。

能代码自解释,尽量不要写注释

// 获取产品数据缓存,用于当前页面渲染
Storge.get('productData')

// 设置产品列表数据,跳转到产品列表页需要用
Storge.get('productListData', productListData)

// 删除当前登录数据,清空退出
Storge.remove('loginState')

/**
 * 判断 两个值 是否相等 **注意: **这个方法支持比较 arrays, array buffers, booleans, date objects, error objects, maps,
 * numbers, Object objects, regexes, sets, strings, symbols, 以及 typed arrays. Object 对象值比较自身的属性,
 * 不包括继承的和可枚举的属性。 不支持函数和DOM节点比较。
 * @param value
 * @param other
 */
static isEqual(value, other) {
  return isEqual(value, other)
}

一些难解释的代码,还是要写上注释,不然可能过一段时间回来,你自己都看不懂了

/**
 * @name path 路径必须是iconUrlPrefix + 年份(2020)+ 时间搓 + 后缀名组成(不按这个规则组成,请求失败)
 */
var suffix = imageUrl.split('.');
var type = suffix ? suffix[suffix.length - 1] : '';
var list = [{
  token: res.token,
  key: res.key,
  path: "/" + res.iconUrlPrefix + new Date().toLocaleDateString().split("/")[0] + '/' + new Date().getTime() + '' + '.' + type,
  provider: 'FASTDFS',
}]

最后总结一下,就是能代码自解释的,尽量通过上下文,函数命名,变量命名来解释代码的意图,逻辑复杂在通过注释来解释 ==命名的重要性要强于去写注释==

上面规范统一,按同一种格式命名,就剩下如何书写简洁,阅读性好,质量高的的代码了


提升代码的质量

函数SRP(功能单一)

==场景==:点击加入购物车,没登录需要跳到登录去,登录则添加到购物车

// 是否登录状态
export async function isLogin () {
  const token = Storage.get('token')
  const v = token.match(/##.+(##)?/g)[0];
  if (v.startsWith('##ANONYMOUS')) {
    return Promise.resolve(true)
  }
  return Promise.reject(new Error('当前状态未登录'))
}

// 添加到购物车
function addCart() {
  isLogin().then(() => {
    // 添加购物车逻辑操作
    ....
  })
}

此时又有个需求,需要拿当前用户会员等级,一般上面已经有判断当前用户身份的代码,加个传参,改装一下

/**
 *
 * @export
 * @param {*} isStatus 是否为返回身份状态(true为返回)(false)不返回
 * @returns
 */
export async function isLogin (isStatus) {
  const token = Storage.get('token')
  const v = token.match(/##.+(##)?/g)[0];
  if(isStatus){
    if (v.startsWith('##ANONYMOUS')) {
      return Promise.resolve('VIP0')
    }
    if (v.startsWith('##VIP1')) {
      return Promise.resolve('VIP1')
    }
    if (v.startsWith('##VIP2')) {
      return Promise.resolve('VIP2')
    }
  } else {
    if (v.startsWith('##ANONYMOUS')) {
      return Promise.reject(new Error('当前状态未登录'))
    }
    return Promise.resolve(true)
  }
}

// 获取当前用户等级
const level = await isLogin(true)

此时应该遵循单一原则,不要把多个功能混在一个函数里面,不然后期此函数只会越来越大,越来越臃肿,命名已经没法很好的解释这个函数功能了

// 获取当前用户等级
export function getLevel() {
  const token = Storage.get('token')
  const v = token.match(/##.+(##)?/g)[0];
  if (v.startsWith('##VIP1')) {
    return 'VIP1'
  }
  if (v.startsWith('##VIP2')) {
    return 'VIP2'
  }
  return 'VIP0'
}

// 获取当前用户等级
const level =  getLevel()

让函数始终只用做一件事情,一个功能,不必考虑一个函数有多少个功能。

字典

==场景==:接口返回数据类型为integer、decimal、string、date、dateTime,我们需要显示为整数、小数、字符串、日期、日期和时间,拿起键盘马上就敲

let typeStatus = ''
switch (type) {
  case 'integer':
    typeStatus = '整数'
    break;
  case 'decimal':
    typeStatus = '小数'
    break;
  case 'string':
    typeStatus = '字符串'
    break;
  case 'date':
    typeStatus = '日期'
    break;
  case 'dateTime':
    typeStatus = '日期和时间'
    break;
  default:
    typeStatus = ''
}

此时,用上字典,让代码优雅起来,写的不这么冗余

const typeDictionary =  {
  'integer': '整数',
  'decimal': '小数',
  'string': '字符串',
  'date': '日期',
  'dateTime': '日期和时间'
}
let typeStatus = typeDictionary[type] || '';

代码看起来,会不会显的整洁,明了呢。

主动中止

==场景==:验证表单,用户名和密码是否填写

formSubmit(data) {
  if (!data.name) {
    Toast('请填写姓名')
  } else if (!data.password) {
    Toast('请填写密码')
  } else {
    // 表单提交逻辑
    ...
  }
}

以上写法也没有问题,但是一层层的ifelse看上去,让逻辑显的不是非常的清晰,业务的核心是在最底下,我们优化一下,让代码质量好一点

formSubmit(data) {
  if (!data.name) {
    Toast('请填写姓名')
    return
  }
  if (!data.password) {
    Toast('请填写密码')
    return
  }
  // 表单提交逻辑
  ...
}

这种写法,不管是中间需要在加条件判断还是可读性来说,都比上面一层层的ifelse要好很多。

使用默认值

createADirectory(name) {
  const breweryName = name || 'directoryName';
  // ...
}

解构赋值,可读性一目了然

createADirectory(name = 'directoryName') {
  // ...
}

传参控制

==场景==:封装一个ajax函数

function request(url, type, data, header) {
  const sType = type || 'get';
  const oData = data || {};
  const oheader = type || {
    'Content-Type': 'application/json;charset=utf8'
  };

  // 执行封装axios请求操作
  ...
}

函数参数应该控制在2个以内,如果参数超过两个,使用解构语法,不用考虑参数的顺序,这样可读性还是性能方面都比上面写法要好

function request({
  url = '',
  type = 'get',
  data = {},
  header = {
    'Content-Type': 'application/json;charset=utf8'
  }
} = {}) {
  // 执行封装axios请求操作
  ...
}

至于类似应该使用let还是const 归纳一下

  • 可读性
  • 一致性
  • 可扩性

写完自省,可能因为时间因素或赶进度原因,写出的代码功能是没问题了,回头代码质量臃肿不堪,不忍直视,后面需要改进,这样编程之路,就一是优化的过程中,习惯就越来越好了。