codereview后的收获

1,762 阅读10分钟

Abstract

刚进入公司开发了两块代码,遇到了负责的老大,开始了规范的pr和codereview学到了一些东西,和大家分享总结一下。

命名规范和表达问题

这应该算是修改的比较多的问题了,举几个比较典型的栗子:

1. 表达问题

大概是如下的一段抛错,其实就是在数据库里查一个东西没有查到嘛

throw new Error('Did not find the package whose ID is 2.')

下面是和大佬的一段对话:(大佬:D,垃圾:L)

  • D: 你英语是体育老师教的吧。。。哪有你这样写的。。。
  • L: 哇!就是我的英语是体育老师教的,我才专门在网上翻译的。
  • D: 怕是假的翻译软件哟。
  • L: 谷歌翻译有图有真相!

image

  • D: 你这是语文没学好(一脸无语,默默贴出他的谷歌翻译)

image

不管说是英语不好还是语文不好,其实归结于自己没有多思考一下,没有做到那种精益求精的工匠精神。我相信没有人能说我那句话错了,但事实就是它不好。

2. API设计规范

// bad
get('/package/package_with_app_version?appid=xxx&version=xxx');
// good
get('/package?appid=xxx&version=xxx');

经过多方查阅,关于api的设计规范,都比较推崇RESTful的API风格。

通过HTTP Method来描述动作

Method 对应数据库行为 描述
GET SELECT 从服务器取出资源
POST CREATE 在服务器新建一个资源
PUT UPDATE 在服务器更新资源(客户端提供改变后的完整资源)
PATCH UPDATE 在服务器更新资源(客户端提供改变的属性)
DELETE DELETE 从服务器删除资源

example:

// 获取包列表
get('/package');
// 获取ID是12的包
get('/package/12');
// 严格遵守RESTFUL的话,应该是通过这两个参数筛选返回列表
// 但我觉得在业务里不太合适,于是根据业务改为,直接返回对应的包
get('/package?appid=xxx&version=xxx');
// 如果想要严格遵守规范的话应该写成这样
get('/app/:appid/package/:version')
// 指定第几页,以及每页的记录数。
get('/package?page=2&per_page=100');
// 更新包,客户端提供包的所有信息,类似于替换
put('/package/:id');
// 更新包,客户端提供需要更新的信息
patch('/package/:id');
// 删除包
delete('/package/:id');
// 创建包
post('/package');

更规范的设计API,可以一目了然的了解这个API将会发生的动作。

代码错误

1. 在读取文件拼接buffer的时候出现的问题

下面是错误的写法,大佬看了一眼说不对,让改。郁闷死我了,我花了好长时间去试验最终证明了,我的写法在解析的时候确实会出现乱码。贴代码

const fs = require('fs');
let rs = fs.createReadStream('./Zn-utf8.txt');
let body = '';
rs.on("data", (chunk) => {
  body += chunk;
});
rs.on("end", () => {
  console.log(body);
});

image

真的有乱码了!

错误: 在这里真的出现乱码了,下面来分析一下原因。

一个英文字符占用一个字节,而一个汉字占用三个字节。当文件较大需要进行切割的时候。

假设:每17个字符进行一次切割,在读取

'一个汉字占用三个字节的时候'
==>
'一(1,2,3)个(4,5,6)汉(7,8,9)字(10,11,12)三(13,14,15)个(16,17,18)字(19,20,21)节(22,23,24)'
==>
chunk1:'一个汉字三??'+chunk2:'?字节'

而在上面代码中拼接字符串的时候,出现了隐式的toString(),把不完整的chunk转成了字符串,导致不完整的汉字不能正确解析出现乱码。

这就是为什么会出现那三个乱码的原因

解决方法:

let body = [];
rs.on('data', (chunk) => {
  body.push(chunk);
});
rs.on('end', () => {
  body = Buffer.concat(body).toString();
  console.log(body);
});

2. 使用定时器检查状态

let status = await checkStatus();
if (status === 'success' || status === 'faild') {
  if (status === 'success') {
    console.log('成功!接下来该做啥做啥。')
  } else if (status === 'faild') {
    console.error('失败!想干啥都不行。');
  }
} else {
  this.statusTimer = setInterval(async () => {
    status = await checkStatus();
    if (status === 'success' || status === 'faild') {
      clearInterval(this.statusTimer);
      if (status === 'success') {
        console.log('成功!接下来该做啥做啥。')
      } else if (status === 'faild') {
        console.error('失败!想干啥都不行。');
      }
    }
  }, 200);
}

大概的逻辑就是,发一个异步请求去检查状态checkStatus(),可能会有三个状态:success、faild和pending;如果是pending状态,就会开启一个setInterval,每200毫秒检查一次,知道产生结果为止。

听起来好像没有什么问题,我最初也是这样考虑的,可是结果确出乎我的意料,因为在本地调试,请求时间短到没有发现这个问题,大佬一眼就看出来了,放一段代码和运行结果,大家就一目了然。

// 概念版的上面那段逻辑
var timer = setInterval(async () => {
	console.log('发送请求之前做的事')
    await new Promise(resolve => setTimeout(resolve, 5000));
    console.log('收到请求之后做的事');
 }, 1000)

image

每一秒执行一次函数,await会阻塞函数后面的内容五秒,用clearInterval清掉定时器,代表收到了想要的结果,不再执行函数。然而还有五次‘收到请求之后做的事’被输出出来。

也就是说当网络情况不好的时候,我之前的代码会造成,检查到状态是成功以后清调了定时器以后,很可能还有数次函数没有执行完,会多次检查到状态成功,触发成功以后干的事情,造成严重后果。

优化方案,借助递归+sleep函数解决问题:

用sleep也只是为了减少无效的请求,主要解决问题的还是递归。

async function checkStatus() {
  const status = await checkStatus();
  if (status === 'success' || status === 'faild') {
    if (status === 'success') {
      console.log('成功!接下来该做啥做啥。')
    } else if (status === 'faild') {
      console.error('失败!想干啥都不行。');
    }
  } else {
    console.log('pending...');
    await new Promise(resolve => setTimeout(resolve, 500));
    await checkStatus();
  }
}

完美解决问题

异常捕获

关于异常捕获被老大点了两次名:

  1. 你的请求都不try...catch一下的,抛错了怎么捕获?
  2. 我改了以后>>>>>>你怎么到处都在try...catch写着好玩么?

于是自己默默研究了一下,什么地方能捕获到异常,什么时候捕获不到,应该在哪些地方捕获异常,是不是每一个请求都应该try...catch?

下面与大家简单分享一下非常low的捕获经验,我知道有很多更好的捕获模式,例如我就特别喜欢egg的异常处理,在service层controller层的错误都经过包装,把错误抛出来,通过中间件进行统一处理。

  • Q:什么时候捕获不到?
  • A:在JS中,对于异步回调函数,trycatch就无能为力。你捕获不到,就只有被原生的uncaughtException捕获到所有没有被捕获的错误,在这时就不能做合适的错误处理了。
  • Q:应该如何捕获异步函数?
  • A:说白了只要不跳出了异步调用链就能捕获。解决方法有很多例如借用promise将异步的错误reject捕获。或者借助async await,也可以轻易的捕获到异常。
  • Q:是不是所有地方都应该捕获异常呢?
  • A:答案当然是不是的。这个尺度自己把我,异常会向上冒,在里层throw出的异常,在往上冒的过程中就会上层的catch捕获,所以肯定不需要到处都写。将异常抛出,到你觉得应该输出错误的时候就输出错误,需要对错误进行处理的时候再捕获,这个尺度我可能还需要更多的经验去感受。

关于这一块,整理学习了一些,但肯定还远远不够,慢慢积累吧这一块的经验。但现在起码可以用最少的try...catch,把错误都处理了。

学会站在用户的角度思考问题

关于这一点和代码无关和程序无关和逻辑无关,就是一个视角问题。

下面是开发过程中对CLI命令的设计

第一版

# 在本地生成
seeder-package [packageName] -o ./dist/package.tgz
# 部署测试环境(自动上线)
seeder-package [packageName] -d ./dist/package.tgz
# 部署生产环境(需要手动上线)
seeder-package [packageName] -d ./dist/package.tgz -e production
# 更新版本号
seeder-package [packageName] --pv major
seeder-package [packageName] --pv minor
seeder-package [packageName] --pv patch
seeder-package [packageName] --pv 3.2.5
# 查看当前版本号
seeder-package [packageName] --pv current

第二版

# 在本地生成
seeder [packageName] -o ./dist/package.tgz
# 把本地指定路径的包部署到测试环境
seeder [packageName] -d ./dist/package.tgz
# 打包并部署到测试环境
seeder [packageName] -d -o
# 在测试环境拉最新版本的包 部署到生产环境
seeder [packageName] -d --prod
# 更新版本 打包 部署测试 上线
seeder [packageName] -o -d --pv [xxx]
# 更新版本 打包 部署测试 上线 的简写
seeder [packageName]

第三版

# 输出本地文件
seeder [packageName] -o xxx/xxx.tgz
# 打包部署并更新 patch 版本号
seeder [packageName]
# 打包部署并更新指定版本号
seeder [packageName] --pv [xxx]
# 同步到生产
seeder [packageName] -p

三个版本可以说变化非常的大

第一版 ==> 第二版

这一次变化是我自己就想做的,因为在开发过程中我就感受到,可用性太差,每次都需要输入太多东西。总的来说这一次变化就是为了***让用户减少输入的内容***。

  • 将seeder-package指令 缩短为 seeder
  • 将需要主动申明部署环境,-e 环境的默认值为测试环境,只有生产环境的时候才需要设置。
  • 将打包部署两个命令合并,并且不需要传后面长长的参数。
  • 类似于 -o -d --pv [xxx]这样的指令可以同时完成几个工作。
  • 做出了简写

第二版 ==> 第三版

矛盾和收获都发生在这一次。


我的观点: -o 输出本地文件 -d部署 --prod 描述环境 --pv版本相关操作,一个指令一个动作,非常清晰。要干什么事儿就用什么指令,不应该再简化了,逻辑非常清晰。 能够省去的输入项我都已经去掉了。


大佬的观点: 再简化,简化到极致(以致于我认为会对命令动作描述不清楚),大佬认为我说的我那样的清晰的逻辑是针对开发者的,不能站在一个技术的角度,觉得这样实现起来逻辑清晰。注意了划重点了 我一直欺骗自己的逻辑清晰原来是自己实现起来逻辑清晰。(恍然大悟)


于是第三次进行了大改造,将命令简化到了缺一不可。这两次是对代码的改动是最大的,但也是我这一次收获最大的,学会了什么叫人性化,什么叫站在用户的角度思考问题,忘掉自己是一个开发者。

感谢

在过去一直觉得codereview很没有用,这一次收获真的挺大的,还有很多偏业务的问题不方便总结出来,还有很多没有特点的问题也没有列出来,这次开发还学会了写测试用例。

敲代码有时候不是为了实现需求。更像是打磨工艺品,秉承工匠精神,你的态度决定了你代码的质量。感谢大佬教给自己的,相信以后也会学到很多,自己的代码质量也会蹭蹭蹭往上提。


来自一个踏上职场两周的孩子的感悟

把这些分享给你希望与你一起进步 还是相当的爽这个代码敲着就非常的舒服了