thumbnail
在软件开发过程中,Code Review(代码审查)是一项至关重要的实践,它不仅有助于提高代码质量,还能促进团队合作和知识共享。然而,即使是经验丰富的开发人员,有时也会遇到一些常见的问题和挑战。本文旨在探讨这些问题,并提供实用的建议和解决方案,帮助开发团队更有效地进行代码审查。
来自AI

前言

在本文中,我们将深入探讨如何识别和解决常见的Code Review问题,包括但不限于:

  • 代码风格和一致性:如何确保代码遵循团队约定的风格指南?
  • 代码逻辑和架构:如何评估代码的可读性和可维护性?
  • 错误和潜在的Bug:如何发现并修复潜在的问题和错误?
  • 性能和优化:如何优化代码以提高性能和效率?
  • 安全性和漏洞:如何确保代码不会引入安全风险?

示例

下面我们将通过几个例子,来解决一些在日常开发中经常见到的不那么友好的代码,甚至是 屎山

回调地狱

getUserInfo().then((userInfo) => {
  getArticles(userInfo).then((articles) => {
    Promise.all(articles.map(article => getArticleDetail(article)))
      .then((articleDetails) => {
        console.log(articleDetails)
      }
    )
  })
})
getUserInfo()
.then(getArticles)
.then((articles) => Promise.all(articles.map(getArticleDetail)))
.then(console.log)

将回调地狱巧妙地改成了 链式调用 ,即使不用 async await,代码看起来也十分优雅。

滥用try catch

const getUserInfo = async () => {
  try {
    const userInfo = await fetch('/api/getUserInfo')
  } catch (error) {
    // 为了避免报错的空白catch
  }
}

我们都知道 trycatch 是用来捕获错误然后处理异常的,但经常有的同学为了让程序不报错,而去做空的catch。

实际上 报错并不可怕 ,它能让你及时的发现问题,从而解决问题。很多时候我们要做各种各样的处理,让程序更加及时的把错误暴露出来,我们才能解决问题,而不是等程序上线之后才发现问题的存在。

泛滥的函数入参

const getUserInfo = (name,age,weight,gender,mobile,hobby,address){ // ... }
// 将所有的参数合并成一个对象,再去函数体内进行解构
const getUserInfo = (options){ 
  const {name,age,weight,gender,mobile,hobby,address} = options
  // ...
}

getUserInfo({
  name:'123',
  address:'abc',
  ...
})

这种情况相信大家也都看到过,它一般是刚开始写函数的时候没几个参数,但是随着业务的拓展,参数越加越多。

这种做法的弊端是他会给调用函数的人造成非常大的 心智负担 ,他不仅要知道有多少个参数,还需要知道每个参数的顺序。

将所有的参数合并成一个对象之后,使用它的人就没有了心智负担,只需要传一个参数,且不在乎顺序,同时代码也很好阅读。

魔法数字

// component1.js
if(state === 1 || state === 2){
  // ...
} else if (state === 3) {
  // ...
}

// component2.js
if(state === 1 || state === 2){
  // ...
}
const NORMAL_USER = 1
const VIP_USER = 2
const SUPER_VIP_USER = 3

// component1.js
if(state === NORMAL_USER || state === VIP_USER){
  // ...
} else if (state === SUPER_VIP_USER) {
  // ...
}

// component2.js
if(state === NORMAL_USER || state === VIP_USER){
  // ...
}

魔法数字 就是在你的代码里面出现的一些莫名其妙的数字,你也搞不清这个数字到底是啥意思,同时代码也很难阅读。

所以我们应该通过定义 常量 去让代码的可读性可维护性变得更高。

无意义的注释

let id = 1; // 给id赋值为1
let id = 1; // 定义要查询的文章id

注释的意义在于解释这行代码没有提供的信息,上面的例子中已经给我们提供的信息就是定义了一个变量id且赋值为1,所以再写一遍这个信息到注释里面,是毫无意义的。

我们需要在注释里提供的信息应该是为什么要把id赋值为1?以及你后续的代码要怎么来使用这个变量,写清楚 for whatwhy

合理利用命名空间缩减变量属性名前缀

class User {
  userName;
  userAge;
  userPwd;

  userLogin(){},
  getUserProfile(){}
}
class User {
  ame;
  age;
  pwd;

  login(){},
  getProfile(){}
}

这个问题实际上是命名的问题,我们已经定义了一个类名为User,再去定义内部的变量时,又将类名作为前缀,这是复杂且啰嗦的。

使用映射替换分支逻辑的使用映射

function couter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}
function couter(state = 0, action) {
  const ins = {
    INCREMENT: 1,
    DECREMENT: -1,
  }
  return state + (ins[action.type] || 0)
}

在软件开发中,使用 映射 替换分支逻辑是一种优化代码结构和提升可读性的有效策略。通过将条件判断和分支逻辑替换为映射(例如字典或对象),我们能够更简洁地管理不同情况下的处理方式。这种方法不仅减少了复杂的条件判断,还使代码更易于理解和维护,同时提高了代码的灵活性和可扩展性。

无法阅读的条件判断

function checkGameStatus() {
  if (remaining === 0
    || (remaining === 1 && remainingPlayers === 1)
    || remainingPlayers === 0
  ) {
    quitGame()
  }
}
function isGameOver() {
  return (remaining === 0
    || (remaining === 1 && remainingPlayers === 1)
    || remainingPlayers === 0
  )
}

function checkGameStatus() {
  if (isGameOver()){
    quitGame()
  }
}

这是最让人头疼的一类代码,写了一大堆的判断,根本没办法阅读,你无从知晓这个判断是用来干啥的。

我们可以将复杂的逻辑判断 抽离 成一个函数,这样代码就很好阅读,整体的逻辑也变得更加顺畅。

逻辑混乱

function publishPost(post) {
  if (isLoggedIn()) {
    if (post) {
      if (isPostDoubleChecked()) {
        doPost(post)
      } else {
        throw new Error('不要重复发布文章')
      }
    } else {
      throw new Error('文章内容不能为空')
    }
  } else {
    throw new Error('请先登录')
  }
}
function publishPost(post) {
  if(!isLoggedIn()){
    throw new Error('请先登录') 
  }
  if(!post){
    throw new Error('文章内容不能为空') 
  }
  if(!isPostDoubleChecked()){
    throw new Error('不要重复发布文章')
  }
  doPost(post)
}

经常有同学会将一些正常的业务处理和错误的处理放在一起,这样会让代码变得不是很好阅读。一般来说我们可以先处理错误,再去处理正常的业务,这是一种非常常见的开发技巧。

硬编码(hard code)

function responseInterceptor(resp) {
  const token = resp.headers.get('authorization') // hard code
  if (token) {
    localStorage.setItem('token', token)
  }
}

// ajax请求拦截
function requestInterceptor(config) {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
}
const AUTH_HEADER_KEY = 'authorization'
const AUTH_KEY = 'token'

function responseInterceptor(resp) {
  const token = resp.headers.get(AUTH_HEADER_KEY)
  if (token) {
    localStorage.setItem(AUTH_KEY, token)
  }
}

// ajax请求拦截
function requestInterceptor(config) {
  const token = localStorage.getItem(AUTH_KEY)
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
}

硬编码就是在代码的某些地方直接写一些字面量,如果它不会重复的话还好,一旦发生重复的话,维护起来就会变得特别困难。而且在上面的例子中,两个函数在产生一个隐式的耦合,就是他们之间没有相互调用,但是仍然耦合了。耦合的地方就是 字面量

所以我们应该将这些字面量提取成常量,这样才能真正意义上的将两个函数完全解耦,也更加方便之后的维护。

总结

这是十个比较常见的在codereview中发现的问题,当然问题肯定也不止这些。代码审查在软件开发中的重要性可想而知,不仅可以改善代码质量,还能促进团队合作和知识共享。希望同学们能够在日常工作中应用这些方法,从而提升代码审查实践,进而提高团队的整体效率和开发质量。