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

Posted

技术标签:

【中文标题】在无效函数输入时返回未定义或抛出错误?【英文标题】:Return undefined or throw error upon invalid function input? 【发布时间】:2018-04-28 01:30:26 【问题描述】:

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

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

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

首先,我想展示一个我个人宁愿抛出错误然后返回未定义的示例。

function sum(a, b)

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

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

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

这是对上述问题的评论,这几乎正是我要问的问题,但还没有人回答:

“但是由于我需要多花 1 分钟来抛出错误而不是默默地死去,对于那些没有花时间阅读文档的人来说,这不会节省数小时的调试时间吗?”

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

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

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

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

检查输入而不是检查输出不是更聪明吗? (确保输入能够正常工作,而不是运行函数并检查是否返回了 undefined)

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

【问题讨论】:

【参考方案1】:

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

函数式语言在 OO 出现之前就通过 Maybe 类型解决了这个问题。

在编写函数时,您可以使用包含 Success 或 Failure 的 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,它不会强制您提供 else。

所以在 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 不是反映可能失败的函数的好类型。您不知道为什么它未能返回预期的类型,现在必须对原因做出假设,在代码中做出假设会使您的代码更难维护。

【讨论】:

在诸如数学函数之类的承诺会严重过度杀伤的时候呢?此外,C# 中的可能看起来很棒。我只是希望他们能赶上 @ThatBrianDude 在 Java 和 C# 中,返回 null 可用于表示出现问题,抛出异常表示出现问题。我认为最好为 JavaScript 中的同步函数返回 Result 类型,它就像 Maybe 但不是 SomeNone 它有 SuccessFailure 其中 Success 是实际值Failure 是错误。在此处处理 js 示例:github.com/amsterdamharu/lib/blob/master/src/index.js#L36-L125 但必须明天或更晚才能完成。

以上是关于在无效函数输入时返回未定义或抛出错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在尝试读取记录集字段时未定义访问 vba 抛出子或函数?

未处理的拒绝(错误)reducer 返回未定义

注册无效,注册未定义错误 React Native

护照抛出“未定义不是函数”

从服务器抛出的自定义错误未在客户端的 HttpErrorResponse 中返回

网格存储配置抛出“未捕获的类型错误:无法读取未定义的属性‘缓冲’”