Language agnostic 您更喜欢例外还是返回码,以及为什么?
我的问题是,对于错误处理、异常或错误返回代码,大多数开发人员更喜欢什么。请注意语言(或语系)的具体情况,以及为什么您更喜欢其中一种语言 我问这个是出于好奇。就我个人而言,我更喜欢错误返回代码,因为它们的爆炸性较小,如果用户不愿意,也不会强制用户代码支付异常性能惩罚 更新:谢谢所有的答案!我必须说,尽管我不喜欢异常情况下代码流的不可预测性。关于返回码(以及它们的哥哥句柄)的答案确实给代码增加了很多噪音。我实际上使用了这两种方法 如果是已知的、可能的错误,我会使用返回码。如果这是一个我知道可以并且将要发生的场景,那么有一个代码会被发回Language agnostic 您更喜欢例外还是返回码,以及为什么?,language-agnostic,exception,Language Agnostic,Exception,我的问题是,对于错误处理、异常或错误返回代码,大多数开发人员更喜欢什么。请注意语言(或语系)的具体情况,以及为什么您更喜欢其中一种语言 我问这个是出于好奇。就我个人而言,我更喜欢错误返回代码,因为它们的爆炸性较小,如果用户不愿意,也不会强制用户代码支付异常性能惩罚 更新:谢谢所有的答案!我必须说,尽管我不喜欢异常情况下代码流的不可预测性。关于返回码(以及它们的哥哥句柄)的答案确实给代码增加了很多噪音。我实际上使用了这两种方法 如果是已知的、可能的错误,我会使用返回码。如果这是一个我知道可以并且将
异常只用于我不期望的事情。对于任何合适的编译器或运行时环境,异常都不会招致严重的惩罚。它或多或少类似于跳转到异常处理程序的GOTO语句。此外,运行时环境(如JVM)捕获异常有助于更轻松地隔离和修复bug。我将在java中使用任何C++中的一个Null PoExtExchange异常。< /P> < P>我的首选项(C++和Python)是使用异常。语言提供的工具使它成为一个定义良好的过程,可以引发、捕获和(如有必要)重新抛出异常,从而使模型易于查看和使用。从概念上讲,它比返回代码更简洁,因为特定的异常可以通过它们的名称来定义,并且附带有附加信息。对于返回代码,您仅限于错误值(除非您要定义ReturnStatus对象或其他内容)
除非您正在编写的代码是时间关键型的,否则与展开堆栈相关的开销不足以让您担心。只有在发生您不希望发生的事情时才应返回异常 从历史上看,另一个例外是返回代码本质上是专有的,有时可以从C函数返回0来表示成功,有时返回-1,如果失败则返回其中任何一个,如果失败则返回1表示成功。即使在枚举时,枚举也可能是不明确的 异常还可以提供更多的信息,并明确指出“出了什么问题,这里是什么,堆栈跟踪和上下文的一些支持信息”
也就是说,一个枚举良好的返回代码对于一组已知的结果非常有用,一个简单的“函数的结果如下,它就是这样运行的”我只使用异常,没有返回代码。我这里说的是Java
我遵循的一般规则是,如果我有一个名为
doFoo()
的方法,那么如果它没有“doFoo”,那么就会出现异常情况,应该抛出异常。我从实用程序员那里得到的一条很好的建议就是“您的程序应该能够在不使用任何异常的情况下执行其所有主要功能”。我担心异常的一件事是抛出异常会扰乱代码流。例如,如果您这样做
void foo()
{
MyPointer* p = NULL;
try{
p = new PointedStuff();
//I'm a module user and I'm doing stuff that might throw or not
}
catch(...)
{
//should I delete the pointer?
}
}
甚至更糟糕的是,如果我删除了一些不应该删除的内容,但在我进行其余清理之前被抛出捕获,会怎么样呢?抛出会给糟糕的用户IMHO带来很大的影响。我更喜欢使用异常来处理错误和返回值(或参数)作为函数的正常结果。这提供了一个简单且一致的错误处理方案,如果正确执行,将使代码看起来更干净。最大的区别之一是异常迫使您处理错误,而错误返回代码可以不检查 错误返回码,如果大量使用,也会导致非常难看的代码,有很多类似于此表单的if测试:
if(function(call) != ERROR_CODE) {
do_right_thing();
}
else {
handle_error();
}
就个人而言,我更喜欢对调用代码应该或必须处理的错误使用异常,并且只对“预期失败”使用错误代码,其中返回的内容实际上是有效和可能的。在Java中,我使用(按以下顺序):
我在exception vs.return code参数中的一般规则:
- 在需要本地化/国际化时使用错误代码——在.NET中,您可以使用这些错误代码引用资源文件,然后该文件将以适当的语言显示错误。否则,请使用异常
- 仅对真正异常的错误使用异常。如果是经常发生的错误,请使用布尔或枚举错误代码
引发异常的性能开销不应在您的决策中发挥任何作用。如果您做得正确,那么异常毕竟是异常的。有很多理由认为异常优于返回代码:
- 通常,为了可读性,人们会尽量减少方法中返回语句的数量。这样,异常就可以防止在不一致状态下做一些额外的工作,从而防止可能损坏更多数据
- 异常通常比返回值更详细,也更容易扩展。假设一个方法返回自然数,并且在发生错误时使用负数作为返回代码,如果方法的范围发生变化,现在返回整数,则必须修改所有方法调用,而不只是稍微调整exc感同身受
- 异常允许更容易地分离正常行为的错误处理
try: dataobj = datastore.fetch(obj_id) except LookupError: # could not find object, create it. dataobj = datastore.create(....)
# wrong way: if os.path.exists(directory_to_remove): # race condition is here. os.path.rmdir(directory_to_remove) # right way: try: os.path.rmdir(directory_to_remove) except OSError: # directory didn't exist, good. pass
try{ db.UpdateAll(somevalue); } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; }
try{ dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false } catch (ConnectionException ex) { logger.Exception(ex, "Connection failed"); dbHasBeenUpdated = false; }
try{ db.UpdateAll(somevalue); } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; } finally { db.Close(); }
try{ using(IDatabase db = DatabaseFactory.CreateDatabase()) { db.UpdateAll(somevalue); } } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; }
try{ try { IDatabase db = DatabaseFactory.CreateDatabase(); db.UpdateAll(somevalue); } finally{ db.Close(); } } catch (DatabaseAlreadyClosedException dbClosedEx) { logger.Exception(dbClosedEx, "Database connection was closed already."); } catch (Exception ex) { logger.Exception(ex, "UpdateAll method failed"); throw; }
if(doSomething()) { if(doSomethingElse()) { if(doSomethingElseAgain()) { // etc. } else { // react to failure of doSomethingElseAgain } } else { // react to failure of doSomethingElse } } else { // react to failure of doSomething }
try { doSomething() ; doSomethingElse() ; doSomethingElseAgain() ; } catch(const SomethingException & e) { // react to failure of doSomething } catch(const SomethingElseException & e) { // react to failure of doSomethingElse } catch(const SomethingElseAgainException & e) { // react to failure of doSomethingElseAgain }
CMyType o = add(a, multiply(b, c)) ;
void doSomething(CMyObject * p, int iRandomData) { // etc. }
void doSomething(CMyObject * p, int iRandomData) { if(iRandomData < 32) { MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ; return ; } if(p == NULL) { MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ; throw std::some_exception() ; } if(! p.is Ok()) { MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ; } // etc. }
# I care whether this succeeds. If it doesn't return :ok, raise an exception. :ok = File.write(path, content) # I don't care whether this succeeds. Don't check the return value. File.write(path, content) # This had better not succeed - the path should be read-only to me. # If I get anything other than this error, raise an exception. {:error, :erofs} = File.write(path, content) # I want this to succeed but I can handle its failure case File.write(path, content) do :ok => handle_success() error => handle_error(error) end
with {:ok, content} <- get_content(), :ok <- File.write(path, content) do IO.puts "everything worked, happy path code goes here" else # Here we can use a single catch-all failure clause # or match every kind of failure individually # or match subsets of them however we like _some_error => IO.puts "one of those steps failed" _other_error => IO.puts "one of those steps failed" end
# Raises a generic MatchError because the return value isn't :ok :ok = File.write(path, content) # Raises a File.Error with a descriptive error message - eg, saying # that the file is read-only File.write!(path, content)