【一年前端必知必会】如何写出简洁清晰的代码

5,089 阅读8分钟

版权声明:本人文章仅在掘金平台发布,请勿抄袭搬运,转载请注明作者及原文链接 🦉

阅读提示:网页版带有主题和代码高亮,阅读体验更佳 🍉

不管进入什么样的公司,项目工程里总有一些业务代码像是在厕所里滚了一圈。即使使用了 ESlintgit hook,也总是难以避免出现这种代码。本质上还是人的原因,一个 coder 对代码缺乏追求,不论使用什么工具都没法阻止他提交 shit code。所以本篇文章的主题很简单,我们可以不知道设计模式,我们可以不懂各种算法,但是不妨碍我们写出简洁、清晰的代码,就像写一篇论文,辞藻、文献、数据可以没那么充分,但是该有的格式要有。另外,代码风格因人而异,本篇文章仅仅是个人观点,欢迎探讨。

接下来,从变量命名、排版格式、简化代码三个维度来讲讲,如何写出更简洁、清晰的代码。另外,简洁、清晰不是说这个代码写得多牛,设计模式和算法多花,而是——简单清晰,易于理解。

变量命名

这真是一个老生长谈的问题了,每个人在刚工作时都被无数遍警告过变量命名的问题。但是,讲道理,纵观我司一些基础设施代码,封装得很好,但是一看代码风格,惨不忍睹,很难阅读理解,而且这些基础设施的代码还是当时前端大牛封装的,早已成为公司业务的基石,无法变动。

其中,问题之一就是变量命名,代码中很多 xpG 类似的别称,很难理解到底什么意思。但是 G 它设置为 window 的别名,这其实完全没必要的。

正反面案例:

一般变量小驼峰命名:

// bad
function get_list_data() {
  ...
}

// good
function getListData() {
  ...
}

组件、类、构造函数等大驼峰命名:

// bad
class mainTable extends React.Component {
  ...
}

// good
class MainTable extends React.Component {
  ...
}

特殊意义变量不要简写:

// bad
const G = window
const n = navigator
const l = location

// good
const patientInfo = {}
const paymentInfo = {}
const getListData = function() {}

全局变量顶层声明,可大写及拼接下划线:

// bad
function useRegExp() {
  const regExp = /^123/;
}

// good 避免在函数执行时重复声明
const NUMBER_REGEXP = /^1[3|4|5|7|8|9]\d{9}/;
const EN_CODE_REGEXP = /^abcdefg/

function useRegExp() {
  ...
}

不是越长语义化越好:

// bad
// 获取支付详情信息
function getPaymentDetailInfomation() {
  ...
}

// good 命名不要太长,否则会有阅读、书写的心智负担
function getPayDetail() {
  ...
}

善用语义词:

// 事件以 on 开头
function onGetListData() {
  ...
}

// ajax 请求以 fetch、get 开头
function fetchListData() {
  ...
}

// 删除、更新、创建
function deleteRecord() {
  ...
}

function updateRecord() {
  ...
}

function createRecord() {
  ...
}

// 表 boolean,is 开头
const isDialogShow = false

// 表可用时,can 开头
const canUseWechat = () => {}

// 表是否存在时,has 开头
const hasPropsInObject = () => {}

不要动不动使用 handle: 这里确实是我个人意见了,因为很多人起名困难时动不动就是 handle 开头,并不是什么情况都适用。

// 自动轮询
// bad
function handleQueryLoop() {
  ...
}
// good
function autoQueryLoop() {
  ...
}

关于变量命名,先暂且讲这么多吧。

格式与排版

很多前端对样式的关注,就像他的头发一样稀少。一些公司或者项目可能缺少产品和UI,所以很多业务页面,前端拥有很大自由,很多人根本不考虑屏幕尺寸,也不考虑间距、颜色。讲道理,作为前端,不能侮辱自己的职业,建议大家平时学下UI和页面设计,即使以后转行产品也是大有裨益。

好的代码,靠变量命名是带不来的,在业务越来越复杂的时候,一个组件 3000 行,一个函数 500 行是常有的事,但是抽象、封装外,还有更容易被大家优化时忽略的点:那就是代码格式和排版。

写代码就像我们写论文,摘要、标题、脚注、参考文献等等,有它的格式,好的排版能带来愉悦的阅读体验。

优化判断条件

贴两段错误示例,完全符合 ESlint 的校验,但是真的好吗?

  1. 横向过长
if (number !== 1 || number !== 2 || number !== 3 || number !== 4 || number !== 5) {
  ...
}
  1. 纵向太碎
if (
  string === 'a' &&
  string2 === 'b' && (
    string3 === 'c' ||
    string4 === 'd'
  )
) {

}

上述两种判断方式,没有人看了会高兴,其实,优化起来很简单。

横向过长:

if ([1, 2, 3, 4, 5].includes(number)) {
  ...
}

纵向太碎:

const isStringTrue = () => {
  return (string === 'a' && string  === 'b' && (string3 === 'c' || string4 === 'd'))
}
// 不要判断条件里写又臭又长的判断
if (isStringTrue()) {

}

善用空格

合理使用空格,是你代码风格进步的最有效手段之一:

// 数组 , 后空格
const array = [1, 2, 3, 4, 5]

// 对象两侧 , 后空格
const object = { a: '1', b: '2' }

// 部分()两侧、右侧留空格
if () {}
while () {}
for () {}
function() {}

// 运算符两侧空格
const a = b + c
const isTrue = typeof a === 'string' ? a : 1
const func = () => {}

善用缩进

缩进可以说是垃圾代码最恶心的槽点之二,某些人写 jsx 或者 HTML 常常不注重缩进,导致代码看起来七歪八扭:

return (
    <div>
      <Radio.Group
          onChange={({ target: { value } }) => {
              setSelectionType(value);
          }}
          value={selectionType}
      >
                <Radio
        value="checkbox">Checkbox</Radio>
            <Radio value="radio">radio</Radio> 
      </Radio.Group>

  <Divider />

            <Table
      rowSelection={{
          type: selectionType,
          ...rowSelection,
          }}
          columns={columns}
  dataSource={data}
      />
    </div>
  );

善用换行

还是拿 jsx 代码举例:

// bad
return (
  <Button style={{}} className="button-class" type="primary" onClick={start} disabled={!hasSelected} loading={loading}>
    Reload
  </Button>
);

// good
return (
  <Button
    style={{}}
    className="button-class"
    type="primary"
    onClick={start}
    disabled={!hasSelected}
    loading={loading}
  >
    按钮
  </Button>
);

------------------------------------

// bad
import React from 'react'
import A from 'aaa'
import B from 'bbb'
const { a } = A;
const { b } = B;
class Comp extends React.Component {

}

// good
import React from 'react'
import A from 'aaa'
import B from 'bbb'

const { a } = A;
const { b } = B;

class Child extends React.Component {
  
}

排版其实更依赖于个人的审美能力和代码习惯。好的排版应该是横向不长(不超过100个字符),纵向不细碎,该空格空格,该换行换行。其实只要做到这几点,80%的代码不会出现排版差的情况。

代码示例

接下来就是本文的重点了,是笔者总结的一些实用的代码片段或者代码书写方式,配合前面的变量命名和排版相信大家一定可以写出简洁、清晰的代码。

使用 map 简化枚举值映射

在日常写表格的需求中,经常会自定义列字段的渲染,特别是一些枚举值的映射,很多人写一堆 if else,但其实只需要一个 map 就可以简化我们的代码:

const tableColumns = [
  {
    title:'状态',
    dataIndex: 'status',
    render: (value, index, record) => {
      const map = new Map([[1, '已完成'], [0, '未完成'], [3, '完成中'], [4, '中途放弃']]);
      return map.get(value);
    }
  }
]

共性代码合并

还是以 table 表格为例,我们经常会和分页器打交道。分页器有两个方法:onPageChangeonPageSizeChange,这两个函数可以合为一个:

<Pagination
  onChange={current => onPageChange(current, 'current')}
  onPageSizeChange={current => onPageChange(current, 'pageSize')}
/>

const onPageChange = (value, type) => {
  const { searchValues } = this.state
  this.setState({
    searchValues: {
      ...searchValues,
      [type]: value,
    }
  })
}

这里这么写其实有点小问题,但是意思很简单,就是类似的方法可以合并,没必要定义好几个函数,执行相似功能。

使用新的语法

其实前面的案例中我们已经介绍过 includes,这里我们额外啰嗦一下:

includes 替代 || 运算符

// bad
if (a === 1 || a === 2 || a === 3) {
  ...
}
// good
if ([1, 2, 3].includes(a)) {
  ...
}

Set 给数组去重

const array = [1, 2, 3, 3, 4, 2, 1, 5, 6, 7, 7]
console.log([...new Set(array)]) // [1, 2, 3, 4, 5, 6, 7]

简易策略模式优化 if else

现在有一个场景,需要将人民币转换为各个外国客户所在国的货币结算,客户有英国、美国、澳大利亚、泰国等,如果使用 if else 来进行封装,每次客户扩展你只能改代码改到死。写一个策略模式就行了:

// 客户列表
const currencyList = ['美元', '澳元', '泰铢', '欧元']
// 汇率
const rateList = { 
  '美元': 0.144881,
  '澳元': 0.216258,
  '泰铢': 5.3383,
  '欧元': 0.143649
}

// 实际开发中,上述两个数据都是动态从后端获得的
const rateKeys = Object.keys(rateList)
// 策略容器
const stratagy = {}

// 为每种汇率添加一个key,并赋值为一个计算对应价格的方法
rateKeys.forEach((item) => {
  // 传入 RMB 价格作为基数
  stratagy[item] = (RMPPrice) => {
    return RMPPrice * rateList[item]
  }
})

function getAbroadPrice(RMBPrice = 100, target = '美元') {
  return stratagy[target](RMBPrice)
}

getAbroadPrice(100, '美元') // 14.481...

阻断提升

其实很简单,把空值、错值提到前面来判断,并阻断代码往下执行:

// bad 
import { Message } from 'xxx-ui';
if (value) {
  // 100 行逻辑
} else {
  Message.error('value不能为空')
}

// good
import { Message } from 'xxx-ui';
if (!value) {
  Message.error('value不能为空')
  return
}
// 100 行逻辑

函数式编程替代for循环

filter;
map;
some;
every;
find;
findIndex;
Object.keys;
Object.values;
Object.entries;
...

善用 ?. 操作符

对象取值防止代码错误影响正常业务,造成生产事故。同时也能简化各种判空、判非。

if (res && res?.length) {
  res.map()
  ...
} 

抽离公共部分或静态变量到单独文件

以 Table 为例,一般我们要写很多 columns 配置,如果列特别多,这一个数组就有上百行,不利于组件阅读,可以将其中不必要留在组件的列定义放在单独的js文件里维护,还可以再进行一层封装:

import moment from 'moment'
import { toolTip } from 'xxx-ui'

// 鼠标浮在文字上显示完整信息
export const toolTip = (
  value,
  position,
  isTime = false,
  format = 'YYYY-MM-DD HH:mm:ss'
) => {
  const finalValue = isTime && value ? moment(value).format(format) : value;
  const tip = <span>{finalValue}</span>;
  return (
    <Tooltip
      trigger={tip}
      align={position}
    >
      {finalValue}
    </Tooltip>
  );
};

export const tableColumns = [
  {
    title: '序号',
    width: 120,
    dataIndex: 'serialNumber',
    render: value => toolTip(value, 'r'),
  },
  {
    title: '时间',
    width: 120,
    dataIndex: 'time',
    render: value => toolTip(value, 'r', true),
  }
]

未完待续。。。

写在最后

代码能力非一朝一夕之功,比起各种封装抽象,各种设计模式、算法,日常业务中多以增删改查为主。把基础的变量命名、排版、简洁写法贯彻后,代码就能简洁清晰很多。

与其远望高山,不如脚踏实地。与君共勉。