我知道这是重复的,但是这里的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#世界并且认为库应该按照预期的方式使用,而不是无论如何都是仁慈和工作。
答案 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不是反映可能失败的函数的好类型。您不知道为什么它无法返回预期的类型,现在必须对原因进行假设,在代码中进行假设会使您的代码难以维护。