我决定如何在我的应用程序中处理异常。
如果我的异常问题来自1)通过远程服务访问数据或2)反序列化JSON对象,那么很多。不幸的是,我不能保证这些任务中的任何一个都成功(切断网络连接,不正确的JSON对象,这是我无法控制的)。
结果,如果我遇到异常,我只是在函数中捕获它并返回FALSE给调用者。我的逻辑是,所有来电者真正关心的是任务是否成功,而不是为什么它没有成功。
以下是典型方法的一些示例代码(在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;
}
我认为这种方法很好,但我真的很想知道管理异常的最佳实践是什么(我应该在调用堆栈中一直冒出异常吗?)。
关键问题摘要:
跟进/修改
感谢所有反馈,在网上找到了一些关于异常管理的优秀来源:
异常管理似乎是根据上下文而变化的事情之一。但最重要的是,人们应该如何管理系统中的异常。
另外注意通过过度尝试/捕获代码腐烂或不尊重它的例外(例外是警告系统,还有什么需要警告?)。
此外,这是来自m3rLinEz的非常好的选择。
我倾向于同意安德斯·海尔斯伯格和你们认为最多的来电者 如果操作成功与否,请小心。
从这个评论中,它提出了一些在处理异常时要考虑的问题:
答案 0 :(得分:62)
我想要捕获异常并将其转换为错误代码似乎很奇怪。为什么你认为调用者更喜欢错误代码而不是异常,而后者是Java和C#中的默认代码?
关于你的问题:
答案 1 :(得分:25)
这取决于应用和情况。如果你构建一个库组件,你应该冒出异常,尽管它们应该被包装成与你的组件一起上下文。例如,如果您构建一个Xml数据库并假设您正在使用文件系统来存储数据,并且您正在使用文件系统权限来保护数据。您不希望冒出一个FileIOAccessDenied异常,因为它会泄漏您的实现。相反,您将包装异常并抛出AccessDenied错误。如果您将组件分发给第三方,则尤其如此。
至于是否可以吞下异常。这取决于你的系统。如果您的应用程序可以处理故障情况,并且通知用户失败的原因没有任何好处,那么请继续,尽管我强烈建议您记录失败。我总是觉得很难被叫来帮助解决问题,发现他们正在吞下异常(或者替换它并在不设置内部异常的情况下更换新的异常)。
一般来说,我使用以下规则:
我发现以下代码是一种气味:
try
{
//do something
}
catch(Exception)
{
throw;
}
这样的代码毫无意义,不应包括在内。
答案 2 :(得分:9)
我想就此主题推荐另一个好的来源。这是对C#和Java,Anders Hejlsberg和James Gosling的发明者的访谈,分别是关于Java的Checked Exception的主题。
页面底部还有很多资源。
我倾向于同意Anders Hejlsberg和你的看法,大多数来电者只关心操作是否成功。
Bill Venners :你提到过 可伸缩性和版本控制问题 关于已检查的例外情况。 你能说清楚你的意思吗? 那两个问题?
Anders Hejlsberg :让我们开始吧 版本化,因为问题是 很容易看到那里。我们说我 创建一个声明它的方法foo 抛出异常A,B和C. foo的第二版,我想添加一个 一堆功能,现在foo可能 抛出异常D.这是一个破坏 改变我添加D到投掷 该方法的条款,因为 该方法的现有调用者将 几乎肯定不会处理 异常。
向投掷添加新例外 新版本中的子句打破了客户端 码。这就像添加方法一样 接口。发布后 界面,它是实用的 目的不变,因为任何 实施它可能有 要添加的方法 下一个版本。所以你必须创造 而是一个新的界面。同样 除了例外,你会有 创建一个名为的全新方法 抛出更多异常的foo2,或 你必须抓住异常D in 新的foo,并将D转换为 A,B或C.
Bill Venners :但你不是在打破 无论如何,他们在这种情况下的代码甚至 用一种语言没有检查 异常?如果是新版的foo 将要抛出一个新的例外 客户应该考虑处理, 不是他们的代码只是被破坏了 事实上,他们并没有想到这一点 他们写代码的例外吗?
Anders Hejlsberg :不,因为很多 案件,人们不在乎。他们是 不会处理任何这些 例外。有一个底层 围绕其消息的异常处理程序 环。那个处理程序就是这样 提出一个对话框,说明发生了什么 错了并继续。程序员 通过编写try来保护他们的代码 终于到处都是,所以他们会回来的 如果发生异常,请正确输出 但他们实际上并不感兴趣 处理异常。
投掷条款,至少方式 它是用Java实现的,不是 必然会迫使你处理 例外,但如果你不处理 他们,它迫使你承认 究竟哪些例外可能会通过 通过。它需要你 捕获声明的异常或放置它们 在你自己的投掷条款。上班 人们这样做,围绕这个要求 荒谬的事情。例如,他们 装饰每一种方法,“抛出 例外。“这完全是 击败了这个功能,而你刚才做到了 程序员写的更多狼吞虎咽 泥流。这对任何人都没有帮助。
编辑:添加了有关转换的更多详细信息
答案 3 :(得分:8)
检查异常一般是一个有争议的问题,特别是在Java中(稍后我将尝试为那些赞成并反对它们的人找到一些例子)。
根据经验,异常处理应该围绕这些指导原则,而不是特定的顺序:
printStackTrace()
或类似的东西,您的用户最终可能会获得其中一个堆栈跟踪,并且完全零知识关于如何处理它。Exception
,您很可能会吞下其他重要的异常。Error
s !! ,意思是:从不抓住Throwable
s ,因为Error
是子类后者Error
是你很可能永远无法处理的问题(例如OutOfMemory
或其他JVM问题)关于您的具体情况,请确保调用您的方法的任何客户端都将收到正确的返回值。如果某些内容失败,布尔返回方法可能会返回false,但请确保您调用该方法的位置能够处理该错误。
答案 4 :(得分:5)
您应该只捕获可以处理的例外情况。例如,如果您正在处理通过网络阅读并且连接超时而您获得异常,则可以再次尝试。但是,如果您正在通过网络阅读并获得IndexOutOfBounds异常,那么您实际上无法处理它,因为您没有(好吧,在这种情况下您不会)知道是什么导致它。如果您要返回false或-1或null,请确保它是针对特定异常的。我不想要一个我正在使用的库在网络读取时返回false,当抛出的异常是堆内存不足时。
答案 5 :(得分:3)
异常是不属于正常程序执行的错误。根据您的程序及其用途(即文字处理器与心脏监视器)的不同,您可能希望在遇到异常时执行不同的操作。我使用的代码使用异常作为正常执行的一部分,它肯定是代码味道。
实施例
try
{
sendMessage();
if(message == success)
{
doStuff();
}
else if(message == failed)
{
throw;
}
}
catch(Exception)
{
logAndRecover();
}
这段代码让我成了barf。 IMO你不应该从例外中恢复,除非它是一个关键程序。如果你抛出异常,那么就会发生不好的事情。
答案 6 :(得分:2)
以上所有内容似乎都是合理的,通常您的工作场所可能有政策。在我们的位置,我们已经定义了异常类型:SystemException
(未选中)和ApplicationException
(已选中)。
我们已经同意SystemException
s不太可能被恢复,并且会在顶部处理一次。为了提供进一步的上下文,我们的SystemException
被扩展以指示它们发生的位置,例如RepositoryException
,ServiceEception
等
ApplicationException
可能具有InsufficientFundsException
等商业含义,应由客户代码处理。
Witohut是一个具体的例子,很难评论你的实现,但我永远不会使用返回码,它们是一个维护问题。您可能会吞下Exception,但您需要确定原因,并始终记录事件和堆栈跟踪。最后,由于你的方法没有其他处理,它是相当冗余的(除了封装?),所以doactualStuffOnObject(p_jsonObject);
可以返回一个布尔值!
答案 7 :(得分:1)
经过一番考虑并查看您的代码后,在我看来,您只是将异常重新抛出为布尔值。您可以让方法通过此异常(您甚至不必捕获它)并在调用者中处理它,因为这是重要的地方。如果异常将导致调用者重试此函数,则调用者应该是捕获异常的那个。
有时会遇到您遇到的异常对调用者没有意义(即它是网络异常),在这种情况下,您应该将其包装在特定于域的异常中。
另一方面,异常表示程序中存在不可恢复的错误(即此异常的最终结果将是程序终止)我个人喜欢通过捕获它并抛出运行时异常来使其显式化。
答案 8 :(得分:1)
如果您要在示例中使用代码模式,请将其命名为TryDoSomething,并仅捕获特定的异常。
在为诊断目的记录异常时,请考虑使用Exception Filter 。 VB具有Exception过滤器的语言支持。 Greggm博客的链接有一个可以在C#中使用的实现。异常过滤器具有比catch和rethrow更好的可调试性。具体来说,您可以在过滤器中记录问题并让异常继续传播。该方法允许附加JIT(即时)调试器以具有完整的原始堆栈。重新抛出时,重新抛出的堆栈将被关闭。
TryXXXX有意义的情况是当你包装第三方函数时,如果没有真正例外,或者在没有调用函数的情况下很难测试。一个例子是:
// throws NumberNotHexidecimalException
int ParseHexidecimal(string numberToParse);
bool TryParseHexidecimal(string numberToParse, out int parsedInt)
{
try
{
parsedInt = ParseHexidecimal(numberToParse);
return true;
}
catch(NumberNotHexidecimalException ex)
{
parsedInt = 0;
return false;
}
catch(Exception ex)
{
// Implement the error policy for unexpected exceptions:
// log a callstack, assert if a debugger is attached etc.
LogRetailAssert(ex);
// rethrow the exception
// The downside is that a JIT debugger will have the next
// line as the place that threw the exception, rather than
// the original location further down the stack.
throw;
// A better practice is to use an exception filter here.
// see the link to Exception Filter Inject above
// http://code.msdn.microsoft.com/ExceptionFilterInjct
}
}
你是否使用像TryXXX这样的模式更像是一个风格问题。捕捉所有异常并吞下它们的问题不是风格问题。确保允许传播意外的异常!
答案 9 :(得分:1)
我建议您从标准库中获取您正在使用的语言的提示。我不能代表C#,但让我们看看Java。
例如java.lang.reflect.Array有一个静态set
方法:
static void set(Object array, int index, Object value);
C方式将是
static int set(Object array, int index, Object value);
...返回值是成功指标。但是你不再在C世界了。
一旦你接受了异常,你就会发现它通过将错误处理代码从核心逻辑中移开来使代码更简单,更清晰。目标是在一个try
块中包含大量语句。
正如其他人所指出的那样 - 你应该尽可能具体地捕捉你遇到的异常。
答案 10 :(得分:0)
如果您要捕获异常并返回false,则应该是一个非常特殊的异常。你没有这样做,你抓住所有这些并返回虚假。如果我得到一个MyCarIsOnFireException,我想马上知道它!我可能不关心的其他例外情况。因此,对于某些异常(重新抛出,或者捕获并重新抛出一个更好地解释发生了什么的新异常)并且只为其他人返回false,你应该有一堆Exception处理程序,这些异常处理程序会说“whoa whoa some wrong”。
如果这是您要发布的产品,那么您应该在某处记录这些例外情况,这将有助于您将来调整。
编辑:关于在try / catch中包装所有内容的问题,我认为答案是肯定的。代码中的异常应该是如此罕见,以至于catch块中的代码很少执行,根本不会达到性能。例外应该是您的状态机损坏并且不知道该怎么做的状态。至少重新抛出一个异常,解释当时发生的事情,并在其中发现捕获的异常。 “方法doSomeStuff()中的异常”对于那些在度假(或新工作)时必须弄清楚它为何会破坏的人来说并不是很有帮助。
答案 11 :(得分:0)
我的策略:
如果原始函数返回 void ,我将其更改为返回 bool 。如果发生异常/错误,则返回 false ,如果一切正常,则返回 true 。
如果函数应该返回一些内容,那么当发生异常/错误时返回 null ,否则返回可返回项。
而不是 bool 可以返回包含错误描述的字符串。
在任何情况下,在返回之前都会记录错误。
答案 12 :(得分:0)
这里有一些很好的答案。我想补充一点,如果你最终得到像你发布的东西,至少打印超过堆栈跟踪。说出你当时在做什么,以及Ex.getMessage(),给开发者一个战斗机会。
答案 13 :(得分:0)
try / catch块形成嵌入在第一(主)集上的第二组逻辑,因此它们是一种很好的方法来敲出难以理解的,难以调试的意大利面条代码。
尽管如此,他们合理地使用了可读性的奇迹,但你应该遵循两个简单的规则:
在低级别使用它们(谨慎地)来捕获库处理问题,并将它们流回主逻辑流程。我们想要的大多数错误处理应该来自代码本身,作为数据本身的一部分。如果返回的数据不特殊,为什么要制定特殊条件?
在较高级别使用一个大型处理程序来管理代码中出现的任何或所有奇怪的条件,这些条件不会在低级别捕获。做一些有用的错误(日志,重启,恢复等)。
除了这两种类型的错误处理之外,中间的所有其余代码都应该是免费的,并且没有try / catch代码和错误对象。这样,无论您在何处使用它,或者您使用它,它都可以简单地按预期工作。
保罗。
答案 14 :(得分:0)
我的答案可能有点晚,但错误处理是我们可以随时改变和发展的事情。如果你想阅读更多关于这个主题的内容,我在我的新博客中写了一篇关于它的文章。 http://taoofdevelopment.wordpress.com
快乐的编码。