返回undefined或在无效函数输入时抛出错误?

时间:2017-11-14 23:27:20

标签: javascript node.js error-handling javascript-framework

我知道这是重复的,但是这里的anwser根本不满足我:

JS library best practice: Return undefined or throw error on bad function input?

我想谈谈指出的一些事情以及我不清楚的事情。

首先,我想提出一个例子,我个人宁愿抛出错误然后返回undefined。

function sum(a, b)

假设消费者传递一个字符串,因为他传递了输入框的直接值,而最终用户输入的数字不是数字。

如果我作为sum的作者在字符串输入时返回了undefined,那么即使开发者在某个时刻键入了一个字符串,也没有任何事情会发生,他也不会关心,因为那就是被期望。但在这种情况下,如果我抛出一个错误,开发人员会意识到这实际上是一个必须处理的边缘情况,因为毕竟没有人想要在他们的程序中出错。

所以基本上,为什么不通过实际抛出错误让开发者意识到边缘情况呢?

这是对上述问题的评论,这正是我所要求的,但没有人回复:

  

“但是因为我需要额外花费1分钟才能抛出错误而不是默默地死亡,对于那些没有花时间阅读文档的人来说,不会为调试时间节省数小时吗?”

上面接受的anwser中的另一点:

  

“捕获异常比测试返回值慢很多,所以如果错误是常见的,那么异常会慢得多。”

我能想到这个qoute适用的唯一情况是I / O或网络内容,其中输入始终采用正确的格式,例如用户不存在该ID。

在这种情况下,我理解为什么抛出错误会减慢这个过程。但同样,一个只包含纯同步函数的数学库呢?

检查输入而不是检查输出是不是更聪明? (确保输入有效而不是运行函数并检查是否返回了undefined)

我的很多困惑实际上源于类型检查,因为我来自C#世界并且认为库应该按照预期的方式使用,而不是无论如何都是仁慈和工作。

1 个答案:

答案 0 :(得分:2)

我认为对于OO语言来说,返回null是很常见的,即使这是不好的做法,而null的发明者称之为billion dollar mistake

功能语言已经解决了这个问题,甚至在OO存在之前就有了类型。

在编写函数时,你可以使用包含成功或失败的Maybe的变体,Scott Wlaschin称之为railway orientated programming,Promises是一种以铁路为导向的编程。

在OO中使用Maybe的问题是你没有union types。在F#中,您的代码如下所示:

let x = 
  // 9/0 throws so divideBy returns None the caller of divideBy can
  //decide what to do this this.
  match (divideBy 9 0) with
  | Some result -> //process result
  | None -> //handle this case

在F#中匹配某些内容时,如果您忘记了案例(不处理None),则会出现编译时错误。在OO中,您将不会遇到运行时错误或安静地失败。当您尝试访问可空类型时,编译器会警告您C#的改进,但只关注if not null,它不会强制您提供其他内容。

所以在JavaScript中我建议使用Promises或返回结果对象。 Promise是现代浏览器和nodejs的原生。在浏览器中,当您不处理失败的承诺时(控制台中的错误和源中的未捕获拒绝中断),它将在控制台中向您大喊大叫。在将来;对于nodejs;它将导致您的进程像未处理的异常一样停止。

//example with promises
const processNumber = compose([
  //assuming divideBy returns a promise that is rejected when dividing by zero
  divideBy(9)
  ,plus(1)
  ,minus(2)
  ,toString
])
// 9/0 returns rejected promise so plus,minus and toString are never executed
processNumber(0) 
.then(
  success => //do something with the success value
  ,fail => //do something with the failure
);

//example of result type:
const Success = {}
,Failure = {}
,result = (type) => (value) => 
  (type === Failure)
    //Failure type should throw when trying to get a value out of it
    ? Object.create({type:type,error:value}, {
      value: {
        configurable: false,
        get: function() { 
          throw "Cannot get value from Failure type"
        }
      }
    })
    : ({
      type:type
      ,value:value    
    })
;
//convert a function (T->T) to (result T->result T)
const lift = fn => arg => {
  //do not call funcion if argument is of type Failure
  if(arg.type === Failure){
    return arg;
  }
  try {
    const r = fn(arg.value);
    //return a success result
    return result(Success)(r);
  } catch (e) {
    //return a failure result
    return result(Failure)(e);
  }
};
//takes a result and returns a result
const processNumber = compose(
  [
    //assuming divideBy throws error when dividing by zero
    divideBy(9)
    ,plus(1)
    ,minus(2)
    ,toString
  ].map( //lift (T->T) to (result T -> result T)
    x => lift(x)
  )
);

const r = processNumber(result(Success)(0));//returns result of type Failure
if(r.type === Failure){
  //handle failure
} else {
  //handle r.value
}

你可以只返回null或throw但是OO中越来越多的人开始意识到处理事情的not the best way。抛出是副作用,因此使函数不纯(函数越多,维护代码越难)。

Null不是反映可能失败的函数的好类型。您不知道为什么它无法返回预期的类型,现在必须对原因进行假设,在代码中进行假设会使您的代码难以维护。