Java 或 C# 异常管理的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/409563/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 02:15:15  来源:igfitidea点击:

Best practices for exception management in Java or C#

c#javaexceptionerror-handling

提问by AtariPete

I'm stuck deciding how to handle exceptions in my application.

我被困在决定如何处理我的应用程序中的异常。

Much if my issues with exceptions comes from 1) accessing data via a remote service or 2) deserializing a JSON object. Unfortunately I can't guarantee success for either of these tasks (cut network connection, malformed JSON object that is out of my control).

如果我的异常问题来自 1) 通过远程服务访问数据或 2) 反序列化 JSON 对象。不幸的是,我不能保证这些任务中的任何一个都能成功(切断网络连接,我无法控制的格式错误的 JSON 对象)。

As a result, if I do encounter an exception I simply catch it within the function and return FALSE to the caller. My logic is that all the caller really cares about is if the task was successful, not why it is wasn't successful.

因此,如果我确实遇到异常,我只需在函数中捕获它并将 FALSE 返回给调用者。我的逻辑是,调用者真正关心的是任务是否成功,而不是为什么不成功。

Here's some sample code (in JAVA) of a typical method)

这是典型方法的一些示例代码(在 JAVA 中)

public boolean doSomething(Object p_somthingToDoOn)
{
    boolean result = false;

    try{
        // if dirty object then clean
        doactualStuffOnObject(p_jsonObject);

        //assume success (no exception thrown)
        result = true;
    }
    catch(Exception Ex)
    {
        //don't care about exceptions
        Ex.printStackTrace();
    }
    return result;
}

I think this approach is fine, but I'm really curious to know what the best practices are for managing exceptions (should I really bubble an exception all the way up a call stack?).

我认为这种方法很好,但我真的很想知道管理异常的最佳实践是什么(我真的应该在调用堆栈中一直冒泡吗?)。

In summary of key questions:

总结关键问题:

  1. Is it okay to just catch exceptions but not bubble them up or formally notifying the system (either via a log or a notification to the user)?
  2. What best practices are there for exceptions that don't result in everything requiring a try/catch block?
  1. 只捕获异常但不冒泡或正式通知系统(通过日志或通知用户)是否可以?
  2. 对于不会导致所有需要 try/catch 块的异常,有哪些最佳实践?

Follow Up/Edit

跟进/编辑

Thanks for all the feedback, found some excellent sources on exception management online:

感谢所有的反馈,在网上找到了一些关于异常管理的优秀资源:

It seems that exception management is one of those things that vary based on context. But most importantly, one should be consistent in how they manage exceptions within a system.

异常管理似乎是根据上下文而变化的事物之一。但最重要的是,人们应该在如何管理系统内的异常方面保持一致。

Additionally watch out for code-rot via excessive try/catches or not giving a exception its respect (an exception is warning the system, what else needs to be warned?).

另外要注意通过过多的尝试/捕获或不尊重异常来防止代码腐烂(异常是警告系统,还需要警告什么?)。

Also, this is a pretty choice comment from m3rLinEz.

此外,这是来自m3rLinEz 的一个不错的选择评论。

I tend to agree with Anders Hejlsberg and you that the most callers only care if operation is successful or not.

我倾向于同意 Anders Hejlsberg 和您的观点,即大多数来电者只关心手术是否成功。

From this comment it brings up some questions to think about when dealing with exceptions:

从这个评论中,它提出了一些在处理异常时需要考虑的问题:

  • What is the point this exception being thrown?
  • How does it make sense to handle it?
  • Does the caller really care about the exception or do they just care if the call was successful?
  • Is forcing a caller to manage a potential exception graceful?
  • Are you being respectful to the idoms of the language?
    • Do you really need to return a success flag like boolean? Returning boolean (or an int) is more of a C mindset than a Java (in Java you would just handle the exception) one.
    • Follow the error management constructs associated with the language :) !
  • 抛出这个异常有什么意义?
  • 处理它有什么意义?
  • 调用者真的关心异常还是只关心调用是否成功?
  • 强迫调用者管理潜在的异常是否优雅?
  • 你尊重语言的习语吗?
    • 你真的需要返回一个像布尔值这样的成功标志吗?返回布尔值(或 int)更像是一种 C 思维方式,而不是 Java(在 Java 中,您只需处理异常)。
    • 遵循与语言相关的错误管理结构 :) !

采纳答案by Brian Rasmussen

It seems odd to me that you want to catch exceptions and turn them into error codes. Why do you think the caller would prefer error codes over exceptions when the latter is the default in both Java and C#?

你想捕获异常并将它们转换为错误代码对我来说似乎很奇怪。为什么你认为调用者更喜欢错误代码而不是异常,而后者是 Java 和 C# 中的默认值?

As for your questions:

至于你的问题:

  1. You should only catch exceptions that you can actually handle. Just catching exceptions is not the right thing to do in most cases. There are a few exceptions (e.g. logging and marshalling exceptions between threads) but even for those cases you should generally rethrow the exceptions.
  2. You should definitely not have a lot of try/catch statements in your code. Again, the idea is to only catch exceptions you can handle. You may include a topmost exception handler to turn any unhandled exceptions into something somewhat useful for the end user but otherwise you should not try to catch each and every exception in every possible place.
  1. 您应该只捕获您可以实际处理的异常。在大多数情况下,仅仅捕获异常并不是正确的做法。有一些异常(例如,线程之间的日志记录和编组异常),但即使在这些情况下,您通常也应该重新抛出异常。
  2. 你的代码中绝对不应该有很多 try/catch 语句。同样,我们的想法是只捕获您可以处理的异常。您可以包含一个最顶层的异常处理程序,将任何未处理的异常转化为对最终用户有用的东西,否则您不应该尝试在每个可能的位置捕获每个异常。

回答by JoshBerke

This depends on the application and the situation. If your building a library component, you should bubble up exceptions, although they should be wrapped to be contextual with your component. For example if your building an Xml Database and let's say you are using the file system to store your data, and you are using file system permissions to secure the data. You wouldn't want to bubble up a FileIOAccessDenied exception as that leaks your implementation. Instead you would wrap the exception and throw an AccessDenied error. This is especially true if you distribute the component to third parties.

这取决于应用程序和情况。如果您构建一个库组件,您应该冒泡异常,尽管它们应该被包装成与您的组件相关。例如,如果您构建了一个 Xml 数据库,并且假设您正在使用文件系统来存储您的数据,并且您正在使用文件系统权限来保护数据。您不想冒泡 FileIOAccessDenied 异常,因为它会泄漏您的实现。相反,您将包装异常并引发 AccessDenied 错误。如果您将组件分发给第三方,则尤其如此。

As for if it's okay to swallow exceptions. That depends on your system. If your application can handle the failure cases and there is no benefit from notifying the user why it failed then go ahead, although I highly recommend that your log the failure. I've always found it frustating being called to help troubleshoot an issue and find they were swallowing the exception (or replacing it and throwing a new one instead without setting the inner exception).

至于是否可以吞下异常。这取决于你的系统。如果您的应用程序可以处理失败情况并且通知用户失败的原因没有任何好处,那么继续,尽管我强烈建议您记录失败。我总是发现被调用来帮助解决问题并发现他们正在吞下异常(或替换它并抛出一个新异常而不设置内部异常)令人沮丧。

In general I use the following rules:

一般来说,我使用以下规则:

  1. In my components & libraries I only catch an exception if I intend to handle it or do something based on it. Or if I want to provide additional contextual information in an exception.
  2. I use a general try catch at the application entry point, or the highest level possible. If an exception gets here I just log it and let it fail. Ideally exceptions should never get here.
  1. 在我的组件和库中,如果我打算处理它或基于它做一些事情,我只会捕获异常。或者,如果我想在异常中提供额外的上下文信息。
  2. 我在应用程序入口点或可能的最高级别使用通用 try catch。如果出现异常,我只是记录它并让它失败。理想情况下,异常不应该出现在这里。

I find the following code to be a smell:

我发现以下代码是一种气味:

try
{
    //do something
}
catch(Exception)
{
   throw;
}

Code like this serves no point and should not be included.

像这样的代码毫无意义,不应包含在内。

回答by jcollum

If you're going to catch an Exception and return false, it should be a very specific exception. You're not doing that, you're catching all of them and returning false. If I get a MyCarIsOnFireException I want to know about it right away! The rest of the Exceptions I might not care about. So you should have a stack of Exception handlers that say "whoa whoa something is wrong here" for some exceptions (rethrow, or catch and rethrow a new exception that explains better what happened) and just return false for others.

如果您要捕获异常并返回 false,则它应该是一个非常具体的异常。你没有这样做,你正在捕捉所有这些并返回false。如果我收到 MyCarIsOnFireException,我想立即了解它!其余的例外我可能不关心。因此,您应该有一堆异常处理程序,对于某些异常(重新抛出,或捕获并重新抛出一个可以更好地解释发生的事情的新异常)说“哇哇哇这里出了什么问题”,而对其他人则返回 false。

If this is a product that you'll be launching you should be logging those exceptions somewhere, it will help you tune things up in the future.

如果这是您将要推出的产品,您应该在某处记录这些异常,这将有助于您在未来进行调整。

Edit: As to the question of wrapping everything in a try/catch, I think the answer is yes. Exceptions should be so rare in your code that the code in the catch block executes so rarely that it doesn't hit performance at all. An exception should be a state where your state machine broke and doesn't know what to do. At least rethrow an exception that explains what was happening at the time and has the caught exception inside of it. "Exception in method doSomeStuff()" isn't very helpful for anyone who has to figure out why it broke while you're on vacation (or at a new job).

编辑:至于将所有内容都包含在 try/catch 中的问题,我认为答案是肯定的。异常在您的代码中应该非常罕见,以至于 catch 块中的代码很少执行,以至于根本不会影响性能。异常应该是您的状态机损坏并且不知道该怎么做的状态。至少重新抛出一个异常,解释当时发生的事情,并在其中包含捕获的异常。“doSomeStuff() 方法中的异常”对于那些必须在您休假(或新工作)时弄清楚它为什么会崩溃的人来说并不是很有帮助。

回答by wds

After some thought and looking at your code it seems to me that you are simply rethrowing the exception as a boolean. You could just let the method pass this exception through (you don't even have to catch it) and deal with it in the caller, since that's the place where it matters. If the exception will cause the caller to retry this function, the caller should be the one catching the exception.

经过一番思考并查看您的代码后,在我看来,您只是将异常作为布尔值重新抛出。你可以让方法传递这个异常(你甚至不必捕捉它)并在调用者中处理它,因为那是重要的地方。如果异常会导致调用者重试这个函数,那么调用者应该是捕获异常的人。

It can at times happen that the exception you are encountering will not make sense to the caller (i.e. it's a network exception), in which case you should wrap it in a domain specific exception.

有时可能会发生您遇到的异常对调用者来说没有意义(即它是网络异常),在这种情况下,您应该将其包装在特定于域的异常中。

If on the other hand, the exception signals an unrecoverable error in your program (i.e. the eventual result of this exception will be program termination) I personally like to make that explicit by catching it and throwing a runtime exception.

另一方面,如果异常表示您的程序中存在不可恢复的错误(即此异常的最终结果将是程序终止),我个人喜欢通过捕获它并抛出运行时异常来明确说明这一点。

回答by Malfist

You should only catch the exceptions you can deal with. For example, if you're dealing with reading over a network and the connection times out and you get an exception you can try again. However if you're reading over a network and get a IndexOutOfBounds exception, you really can't handle that because you don't (well, in this case you wont) know what caused it. If you're going to return false or -1 or null, make sure it's for specific exceptions. I don't want a library I'm using returning a false on a network read when the exception thrown is the heap is out of memory.

您应该只捕获可以处理的异常。例如,如果您正在处理通过网络读取并且连接超时并且出现异常,您可以重试。但是,如果您正在通过网络阅读并获得 IndexOutOfBounds 异常,您真的无法处理它,因为您不知道(好吧,在这种情况下,您不会)知道是什么导致了它。如果您要返回 false 或 -1 或 null,请确保它是针对特定异常的。当抛出的异常是堆内存不足时,我不希望我正在使用的库在网络读取时返回 false。

回答by Yuval Adam

Checked exceptions are a controversial issue in general, and in Java in particular (later on I'll try to find some examples for those in favor and opposed to them).

检查异常通常是一个有争议的问题,特别是在 Java 中(稍后我将尝试为那些支持和反对它们的人找到一些例子)。

As rules of thumb, exception handling should be something around these guidelines, in no particular order:

作为经验法则,异常处理应该围绕这些准则进行,没有特定的顺序:

  • For the sake of maintainability, always log exceptions so that when you start seeing bugs, the log will assist in pointing you to the place your bug has likely started. Never leave printStackTrace()or the likes of it, chances are one of your users will get one of those stack traces eventually, and have exactly zero knowledgeas to what to do with it.
  • Catch exceptions you can handle, and only those, and handle them, don't just throw them up the stack.
  • Always catch a specific exception class, and generally you should never catch type Exception, you are very likely to swallow otherwise important exceptions.
  • Never (ever) catch Errors!!, meaning: Never catch Throwablesas Errors are subclasses of the latter. Errors are problems you will most likely never be able to handle (e.g. OutOfMemory, or other JVM issues)
  • 为了可维护性,始终记录异常,以便当您开始看到错误时,日志将帮助您指出错误可能开始的位置。永远不要离开printStackTrace()或喜欢它,您的用户之一最终可能会获得这些堆栈跟踪之一,并且对如何处理它的知识完全为零
  • 捕获您可以处理的异常,并且只有那些并处理它们,不要只是将它们扔到堆栈中。
  • 总是捕捉一个特定的异常类,通常你不应该捕捉 type Exception,你很可能会吞下其他重要的异常。
  • 永远(永远)赶不上Error,意思是:永远不要捕捉Throwables,因为Errors 是后者的子类。Errors 是您很可能永远无法处理的问题(例如OutOfMemory,或其他 JVM 问题)

Regarding your specific case, make sure that any client calling your method will receive the proper return value. If something fails, a boolean-returning method might return false, but make sure the places you call that method are able to handle that.

关于您的具体情况,请确保调用您的方法的任何客户端都将收到正确的返回值。如果某事失败,返回布尔值的方法可能会返回 false,但请确保您调用该方法的地方能够处理该问题。

回答by Holograham

Exceptions are errors that are not part of normal program execution. Depending on what your program does and its uses (i.e. a word processor vs. a heart monitor) you will want to do different things when you encounter an exception. I have worked with code that uses exceptions as part of normal execution and it is definitely a code smell.

异常是不属于正常程序执行的错误。根据您的程序的用途及其用途(即文字处理器与心脏监视器),当您遇到异常时,您将需要做不同的事情。我曾经使用过将异常作为正常执行的一部分的代码,这绝对是一种代码异味。

Ex.

前任。

try
{
   sendMessage();

   if(message == success)
   {
       doStuff();
   }
   else if(message == failed)
   {
       throw;
   }
}
catch(Exception)
{
    logAndRecover();
}

This code makes me barf. IMO you should not recover from exceptions unless its a critical program. If your throwing exceptions then bad things are happening.

这段代码让我恶心。IMO 你不应该从异常中恢复,除非它是一个关键程序。如果您抛出异常,那么就会发生不好的事情。

回答by Holograham

All of the above seems reasonable, and often your workplace may have a policy. At our place we have defined to types of Exception: SystemException(unchecked) and ApplicationException(checked).

以上所有似乎都是合理的,通常您的工作场所可能会有政策。在我们这里,我们已经定义了 Exception 的类型:SystemException(unchecked) 和ApplicationException(checked)。

We have agreed that SystemExceptions are unlikely to be recoverable and will bve handled once at the top. To provide further context, our SystemExceptions are exteneded to indicate where they occurred, e.g. RepositoryException, ServiceEception, etc.

我们已经同意SystemExceptions 不太可能恢复,并且将在顶部处理一次。为了提供进一步的情况下,我们的SystemExceptions的exteneded说明他们发生地,例如RepositoryExceptionServiceEception等等。

ApplicationExceptions could have business meaning like InsufficientFundsExceptionand should be handled by client code.

ApplicationExceptions 可能具有商业意义,InsufficientFundsException并且应该由客户端代码处理。

Witohut a concrete example, it's difficult to comment on your implementation, but I would never use return codes, they're a maintenance issue. You might swallow an Exception, but you need to decide why, and always log the event and stacktrace. Lastly, as your method has no other processing it's fairly redundant (except for encapsulation?), so doactualStuffOnObject(p_jsonObject);could return a boolean!

举个具体的例子,很难评论你的实现,但我永远不会使用返回码,它们是一个维护问题。您可能会吞下异常,但您需要确定原因,并始终记录事件和堆栈跟踪。最后,由于您的方法没有其他处理,因此相当多余(除了封装?),因此doactualStuffOnObject(p_jsonObject);可以返回一个布尔值!

回答by Germstorm

My strategy:

我的策略:

If the original function returned voidI change it to return bool. If exception/error occurred return false, if everything was fine return true.

如果原始函数返回void我将其更改为返回bool。如果发生异常/错误返回false,如果一切正常返回true

If the function should return something then when exception/error occurred return null, otherwise the returnable item.

如果函数应该返回一些东西,那么当异常/错误发生时返回null,否则返回可返回的项目。

Instead of boola stringcould be returned containing the description of the error.

取而代之的布尔一个字符串可能包含错误的描述被退回。

In every case before returning anything log the error.

在任何情况下,在返回任何东西之前记录错误。

回答by Gant

I would like to recommend another good source on the topic. It's an interview with inventors of C# and Java, Anders Hejlsberg and James Gosling respectively, on the topic of Java's Checked Exception.

我想推荐有关该主题的另一个好的来源。这是对 C# 和 Java 的发明者 Anders Hejlsberg 和 James Gosling 分别以 Java 的检查异常为主题的采访。

Failure and Exceptions

失败和异常

There are also great resources at the bottom of the page.

页面底部也有很棒的资源。

I tend to agree with Anders Hejlsberg and you that the most callers only care if operation is successful or not.

我倾向于同意 Anders Hejlsberg 和您的观点,即大多数来电者只关心手术是否成功。

Bill Venners: You mentioned scalability and versioning concerns with respect to checked exceptions. Could you clarify what you mean by those two issues?

Anders Hejlsberg: Let's start with versioning, because the issues are pretty easy to see there. Let's say I create a method foo that declares it throws exceptions A, B, and C. In version two of foo, I want to add a bunch of features, and now foo might throw exception D. It is a breaking change for me to add D to the throws clause of that method, because existing caller of that method will almost certainly not handle that exception.

Adding a new exception to a throws clause in a new version breaks client code. It's like adding a method to an interface. After you publish an interface, it is for all practical purposes immutable, because any implementation of it might have the methods that you want to add in the next version. So you've got to create a new interface instead. Similarly with exceptions, you would either have to create a whole new method called foo2 that throws more exceptions, or you would have to catch exception D in the new foo, and transform the D into an A, B, or C.

Bill Venners: But aren't you breaking their code in that case anyway, even in a language without checked exceptions? If the new version of foo is going to throw a new exception that clients should think about handling, isn't their code broken just by the fact that they didn't expect that exception when they wrote the code?

Anders Hejlsberg: No, because in a lot of cases, people don't care. They're not going to handle any of these exceptions. There's a bottom level exception handler around their message loop. That handler is just going to bring up a dialog that says what went wrong and continue. The programmers protect their code by writing try finally's everywhere, so they'll back out correctly if an exception occurs, but they're not actually interested in handling the exceptions.

The throws clause, at least the way it's implemented in Java, doesn't necessarily force you to handle the exceptions, but if you don't handle them, it forces you to acknowledge precisely which exceptions might pass through. It requires you to either catch declared exceptions or put them in your own throws clause. To work around this requirement, people do ridiculous things. For example, they decorate every method with, "throws Exception." That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn't help anybody.

Bill Venners:您提到了与检查异常有关的可扩展性和版本控制问题。你能澄清一下你说的这两个问题是什么意思吗?

Anders Hejlsberg:让我们从版本控制开始,因为在那里很容易看到问题。假设我创建了一个方法 foo,声明它抛出异常 A、B 和 C。在 foo 的第二版中,我想添加一堆功能,现在 foo 可能会抛出异常 D。这对我来说是一个重大变化将 D 添加到该方法的 throws 子句中,因为该方法的现有调用者几乎肯定不会处理该异常。

在新版本中向 throws 子句添加新异常会破坏客户端代码。这就像向接口添加方法一样。在您发布接口之后,它对于所有实际目的都是不可变的,因为它的任何实现都可能具有您想要在下一个版本中添加的方法。所以你必须创建一个新的界面。与异常类似,您要么必须创建一个名为 foo2 的全新方法来抛出更多异常,要么必须在新的 foo 中捕获异常 D,并将 D 转换为 A、B 或 C。

Bill Venners:但是在那种情况下,即使是在没有检查异常的语言中,你也不会破坏他们的代码吗?如果新版本的 foo 将抛出一个客户应该考虑处理的新异常,那么他们的代码不是因为他们在编写代码时没有预料到该异常而被破坏吗?

Anders Hejlsberg:不,因为在很多情况下,人们并不关心。他们不会处理任何这些异常。他们的消息循环周围有一个底层异常处理程序。该处理程序将调出一个对话框,说明发生了什么问题并继续。程序员通过在任何地方编写 try finally 来保护他们的代码,因此如果发生异常,他们将正确退出,但他们实际上对处理异常并不感兴趣。

throws 子句,至少它在 Java 中的实现方式,并不一定会强制您处理异常,但是如果您不处理它们,它会迫使您准确地确认哪些异常可能会通过。它要求您要么捕获声明的异常,要么将它们放在您自己的 throws 子句中。为了解决这个要求,人们做了一些荒谬的事情。例如,他们用“抛出异常”来装饰每个方法。这完全破坏了该功能,并且您只是让程序员编写了更多狼吞虎咽的垃圾。这对任何人都没有帮助。

EDIT: Added more details on the converstaion

编辑:添加了有关转换的更多详细信息