面向调试编程

编程的方式有很多种:面向工资编程、面向 bug 编程、面向性能编程、面向领导编程。但是有一种编程方式你们一定没有听说过:面向调试编程。下面我拿 javascript 举例来说一说怎么面向调试编程。

一道面试题

从一道面试题开始说起:请实现一个 flat 函数,将多维数组展开,这道题有很多种写法,但是肯定有一种写法它是最炫酷的:

function flat(arr=[]) {
  return arr.reduce((acc, val) => (Array.isArray(val) ? acc.concat(flat(val)) : acc.concat(val)), []);
}

看到没有,一行代码解决,是不是很牛,很多面试题网上不乏都有这样的答案,这里我不去讨论其实现的复杂度如何,单单看这种写法会给后面的维护带来什么问题。

假如业务中使用到了这个工具方法,但是计算出的结果不正确,需要调试解决 bug,这个时候会发现没办法打断点,这是其一:箭头函数一时爽,到了后面就哭天喊地了。

这时候肯定有人会说了:把箭头函数加上一个括号,然后 debug 不就行了,是的单单看这一个案例是没问题,但是如果面对的是复杂上千行的代码那又该当如何呢?为什么不提前加上大括号呢?

好,现在就加上大括号,reduce 函数可以 debug 了。

下面看一下改造之后的代码:

function flat(arr = []) {
  const result = arr.reduce((acc, val) => {
    const isArray = Array.isArray(val);
    return isArray ? acc.concat(flat(val)) : acc.concat(val);
  }, []);

  return result;
}

改造之后的代码看起来很Low但是到了实际出问题的时候能断点的位置比之前多了 5 处,解决问题更加方便了,是选择表面上的高大上,还是内心的健壮,这就看你自己的选择了。

面向调试编程

上面这种方式我愿意把它称为面向调试编程,作为程序员,我不希望程序有 bug,但是我阻止不了 bug 的产生。下面我来列举一下常见的场景:

对象与纯函数

对象与纯函数是调试的重灾区,这一点我深有体会,比如为了转换数据格式然后传给后端接口,我写了下面一段伪代码:

const arr = [
  {
    name: "Li Ming",
    age: 42,
    idNo: "1234567890123456789",
  },
];
const params = {
  id: 1,
  people: arr.map((item) => ({
    ...item,
    label: item.name,
    value: item.idNo,
  })),
  extra: {
    xxx: arr.map((item) => ({
      ...item,
      label: item.name,
      value: item.idNo,
    })),
    abc: arr.filter((item) => item.age > 40),
  },
};

对象上面充斥着大量的纯函数表达式,这样写确实一时爽,我以前也这么写。但是如果 arr 是个 null 导致程序报错了,然后又用 trycatch 包裹了这个请求,这个时候只要 catch 里面没有打印错误信息,那么报错就很难查找。在 debug 的时候你不得不把所有的表达式都改写一下,如下:

const arr = null;
const people = arr.map((item) => {
  return {
    ...item,
    label: item.name,
    value: item.idNo,
  };
});
const xxx = arr.map((item) => {
  return {
    ...item,
    label: item.name,
    value: item.idNo,
  };
});
const abc = arr.filter((item) => {
  return item.age > 40;
});
const params = {
  id: 1,
  people,
  extra: {
    xxx,
    abc,
  },
};

这个时候痛苦加倍!早知今日,何必当初要这么写呢?

函数参数

函数参数也很容易出现不容易调试的问题,下面来看一段非常熟悉的代码:

  try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
  } catch (err) {
    return Promise.reject(err)
  }

这是 Koa 洋葱模型的核心实现,Koa 这样写没问题,因为它有完备的单元测试,如果我业务中也写成这样那就会被所有同事嫌弃了。可以这样改写:

try {
  const result = dispatch.bind(null, i + 1);
  const promise = fn(context, result);
  return Promise.resolve(promise);
} catch (err) {
  return Promise.reject(err);
}

这样一来是不是好理解多了呢?

一句话总结:面向调试编程就是将表达式都拆分出来然后赋值给变量,这样调试的时候就能对每个过程的值进行测试,便于找出真正有问题的代码。目前我找到的有两个场景:一个是对象与纯函数,另外一个场景是函数参数,如果有其他场景,欢迎大家评论区留言分享。

原文链接:https://juejin.cn/post/7348712837848399908 作者:蚂小蚁

(0)
上一篇 2024年3月22日 上午10:37
下一篇 2024年3月22日 上午10:48

相关推荐

发表回复

登录后才能评论