编程的经典方法是try ... catch。什么时候使用不带trycatch

在Python中,以下内容似乎合法且有意义:类似地,在Java中人们可能会认为它如下:好的做法,什么时候是好的做法?或者,为什么这不是良好做法或不合法? (我没有编译源代码。我正在询问它,因为它可能是Java的语法错误。我检查了Python是否确实可以编译。)

我遇到了一个相关问题是这样的:我继续编写函数/方法,最后必须返回一些内容。但是,它可能位于不应该到达的地方,必须是返回点。因此,即使我处理了上述异常,我仍会在代码中某些不应该到达的位置(通常是方法/函数的末尾)返回catch或空字符串。我一直设法重新组织代码,以使其不必使用NULL,因为这看起来绝对不像是一种好习惯。

评论

在Java中,为什么不将return语句放在try块的末尾?

我很遗憾try..finally和try..catch都使用try关键字,除了都以try开头之外,它们都是2个完全不同的结构。

try / catch不是“经典的编程方式”。这是经典的C ++编程方式,因为C ++缺乏适当的try / finally结构,这意味着您必须使用涉及RAII的丑陋的骇客来实现有保证的可逆状态更改。但是体面的OO语言没有这个问题,因为它们提供了try / finally。它的用途与try / catch完全不同。

通过外部连接资源,我看到了很多。您想要例外,但需要确保您不留下打开的连接等。如果发现它,则在某些情况下,只要将它重新扔到下一层即可。

@MasonWheeler“丑陋的骇客”,请解释一下让对象处理自己的清理有什么不好吗?

#1 楼

这取决于您是否可以处理此时可能引发的异常。

如果可以在本地处理异常,则应该这样做,并且最好将错误处理到尽可能靠近的地方。

如果您不能在本地处理它们,那么仅仅拥有一个try / finally块是完全合理的-假设无论该方法是否成功,都需要执行一些代码。例如(从Neil的评论中),打开一个流然后将该流传递给要加载的内部方法是一个很好的示例,说明何时需要try { } finally { },使用finally子句来确保无论成功如何都关闭流或读取失败。

但是,您仍然需要在代码中的某个地方使用异常处理程序-除非您当然希望应用程序完全崩溃。这取决于处理程序所在位置的应用程序体系结构。

评论


“而且最好将错误处理到尽可能接近错误发生的地方。”嗯,这取决于。如果您可以恢复并仍然完成任务(可以这么说),那么当然可以。如果无法做到,最好让异常一直蔓延到顶部,在这种情况下(可能)需要用户干预来处理发生的事情。

–扯开了
2012年5月2日,12:59

@will-这就是为什么我使用短语“尽可能”的原因。

–ChrisF♦
2012年5月2日13:00

好的答案,但是我想举一个例子:打开一个流并将该流传递给要加载的内部方法是一个很好的示例,说明何时需要尝试{} finally {},利用finally子句以确保流是无论成功与否,最终都将关闭。

–尼尔
2012年10月24日9:35

因为有时最顶端的路尽人所能

– Newtopian
15年1月27日在16:46

“仅仅尝试一下/最终阻止是完全合理的”,正是在寻找这个答案。谢谢@ChrisF

–八角茴香
2015年6月24日12:07



#2 楼

finally块用于必须始终运行的代码,而无论是否发生错误条件(异常)。相应的finally块完成后发生。即使trycatch块中发生未捕获的异常,它也始终运行。

try块通常用于关闭在catch块中打开的文件,网络连接等。原因是文件或网络连接必须关闭,无论使用该文件或网络连接的操作成功还是失败。

应注意finally块以确保不会关闭文件或网络连接。本身抛出异常。例如,请务必检查try等的所有变量。

评论


+1:“必须清理”很常见。 try-finally的大多数用法都可以用with语句替换。

– S.Lott
2012年1月23日在18:07

各种语言对try / finally结构具有非常有用的特定于语言的增强功能。 C#使用过,Python使用过,等等。

– yfeldblum
2012年1月23日在18:16



@yfeldblum-using和try-finally之间有细微的差别,因为如果IDisposable对象的构造函数中发生异常,则using块不会调用Dispose方法。 try-finally允许您执行代码,即使对象的构造函数抛出异常。

–斯科特·惠特洛克
2012年2月15日在16:27

@ScottWhitlock:那是一件好事吗?您要做什么,在未构造的对象上调用方法?那是十亿种坏事。

– DeadMG
2012年2月15日在16:55

@DeadMG:另请参阅stackoverflow.com/q/14830534/18192

–布赖恩
13年4月25日在3:21

#3 楼

在Java中尝试...最终没有catch子句的情况是合适的(甚至更多,是惯用的),这是在并发实用程序锁包中使用Lock的情况。
如何在API文档中对其进行解释和说明(引号中的粗体字是我的):
...缺少块结构化的锁,可以消除同步方法和语句发生的锁的自动释放。在大多数情况下,应使用以下惯用法:

 Lock l = ...;
 l.lock();
 try {
     // access the resource protected by this lock
 } finally {
     l.unlock();
 }


当在不同范围内发生锁定和解锁时,必须小心确保尝试保持锁定期间执行的所有代码
受到try-finally或try-catch的保护,以确保在必要时释放该锁





评论


我可以将l.lock()放入尝试吗?试试{l.lock(); }最后{l.unlock();}

–RMachnik
2015年6月25日18:55



从技术上讲,您可以。我没有把它放在那里,因为从语义上讲,它意义不大。尝试在此代码段中尝试包装资源访问,为什么用与此无关的内容来污染它

– gna
15年6月25日在18:58

您可以,但是如果l.lock()失败,则如果l.lock()在try块中,则finally块仍将运行。如果您按照gnat的建议进行操作,则只有当我们知道已获取锁时,finally块才会运行。

– Wtrmute
16 Mar 4 '16 at 16:32

#4 楼

从基本的角度来说,catchfinally解决了两个相关但不同的问题: /> catch用于清除当前代码创建/修改的数据/资源,无论是否发生问题

所以两者都以某种方式与问题(异常)相关,但这差不多它们的所有共同点。调用堆栈。

finally是另一回事:正确的位置取决于您实际可以处理异常的位置。在对它无能为力的地方捕获异常是没有用的,因此,有时最好还是让它通过。

评论


Nitpick:“ ... finally块必须采用与创建资源相同的方法...”。这样做绝对是个好主意,因为更容易看到没有资源泄漏。但是,这不是必要的先决条件。也就是说,您不必那样做。您可以在(静态或动态)封闭的try语句的最后释放资源...并且仍然是100%防泄漏的。

– Stephen C
13-4-25在4:32



#5 楼

@yfeldblum给出了正确的答案:最后,没有catch语句的try-finally通常应替换为适当的语言构造。在Python中,这是一个with语句;在C#中,它是一个using语句。

这些几乎总是更优雅,因为初始化和完成代码位于一个位置(抽象对象),而不是两个位置。

评论


在Java中是ARM块

– MarkJ
2012年2月15日15:33

假设您有一个设计不良的对象(例如,一个在C#中未适当实现IDisposable的对象),但这并不是一个可行的选择。

–代用药
2012年2月15日的19:00

@mootinator:您不能从设计不良的对象继承并修复它吗?

–尼尔G
2012年2月15日在22:40

还是封装?呸。我的意思是,当然。

–代用药
2012-02-16 15:26



#6 楼

在许多语言中,finally语句也会在return语句之后运行。这意味着您可以执行以下操作:

try {
  // Do processing
  return result;
} finally {
  // Release resources
}


释放资源,而不管该方法以异常或常规return语句结束的方式。 br />这是好事还是坏事尚待争论,但是try {} finally {}并不总是仅限于异常处理。

#7 楼

我可能会用这个答案引起Pythonistas的愤怒(不知道,因为我使用的不是Python)或其他语言的程序员,但是我认为大多数函数最好没有catch块。为了说明为什么,让我将它与在80年代末和90年代初使用Turbo C时必须进行的手动错误代码传播进行对比。

所以我们说我们有一个要加载的函数响应用户选择要加载的图像文件而生成的图像或类似图像,它是用C语言和汇编语言编写的:



我省略了一些底层的函数,但我们可以看到,根据它们对错误处理所承担的责任,我用不同的颜色标识了不同的函数类别。 br />现在,我不难写出我称之为“可能的故障点”(即throw的那些)和“错误恢复和报告”功能(即catch的那些)的功能类别。 br />
那些功能总是很容易在异常处理可用之前正确编写,因为该功能可能会遇到外部故障,例如分配失败内存,可以只返回一个NULL0-1或为此设置一个全局错误代码或其他内容。错误恢复/报告总是很容易的,因为一旦您沿着调用堆栈向下移动到可以恢复和报告故障的地步,您只需获取错误代码和/或消息并将其报告给用户。很自然,此层次结构中的某个函数无论将来如何更改都永远不会失败(Convert Pixel)仍然很容易正确编写(至少就错误处理而言)。

错误传播

但是,容易发生人为错误的繁琐功能是错误传播程序,它们不会直接陷入失败,而是称为可能在层次结构中更深处失败的函数。到那时,Allocate Scanline可能必须处理来自malloc的故障,然后将错误返回到Convert Scanlines,然后Convert Scanlines将必须检查该错误并将其传递给Decompress ImageDecompress Image->Parse ImageParse Image->Load Image以及Load Image给用户-最终命令将最终报告错误。

很多人在这里犯错,因为只需要一个错误传播器就可以检查并把整个功能层次的错误传递给

此外,如果错误代码是由函数返回的,那么,例如90%的代码库,我们几乎失去了返回值的能力。对成功感兴趣,因为许多函数必须保留其返回值以在失败时返回错误代码。

减少人为错误:全局错误代码

那么我们如何减少人为错误的可能性?在这里,我什至可能会引起一些C程序员的愤怒,但我认为直接的改进是使用全局错误代码,例如带有glGetError的OpenGL。这至少释放了函数在成功时返回有意义的兴趣值的能力。有一些方法可以使错误代码本地化到线程,从而使该线程安全且高效。由于发现先前的错误,导致它过早返回之前过了一段时间。这样就可以发生这种情况,而不必在每个函数中检查90%的函数调用是否有错误,因此它仍然可以进行适当的错误处理,而不必太细致。

减少人为错误:异常处理

但是,上述解决方案即使需要减少手动if error happened, return error类型代码的行数,仍然需要许多功能来处理手动错误传播的控制流程方面。不能完全消除它,因为仍然经常需要至少一个位置检查错误并为几乎每个错误传播函数返回。因此,这是当异常处理出现在屏幕上以节省一天的时间(sorta)的方法。传播。这意味着它的价值与避免在整个代码库中编写大量catch块的能力有关。在上图中,唯一必须具有catch块的位置是报告错误的Load Image User Command。理想情况下,没有别的东西,因为它开始变得像错误代码处理一样乏味且容易出错。 -以一种优雅的方式进行处理,它应具有最少数量的catch块(至少我不是指零,但对于可能失败的每个独特的高端用户操作来说,它就更像一个,如果全部高,最终用户的操作是通过中央命令系统调用的。)

资源清除

但是,异常处理仅解决了避免手动处理错误的控制流方面的需要。在正常执行流程之外的特殊路径中传播。通常,即使现在使用EH自动执行此功能,也可以充当错误传播者,但该功能可能仍会获取一些需要销毁的资源。例如,这样的函数可能会打开一个临时文件,无论如何都需要先关闭该临时文件,然后再从该函数返回,或者锁定一个互斥量无论如何都需要解锁。

为此,我可能会从各种语言中引起很多程序员的愤怒,但我认为采用C ++的方法是理想的选择。该语言引入了析构函数,这些析构函数在对象超出范围时即以确定性方式被调用。因此,C ++代码可以通过带有析构函数的作用域互斥对象锁定互斥对象,而无需手动对其进行解锁,因为一旦对象超出范围,无论发生什么情况(即使发生异常,它都会自动解锁)遇到)。因此,实际上完全不需要编写良好的C ++代码来处理本地资源清理。

在缺少析构函数的语言中,他们可能需要使用catch块手动清理本地资源。就是说,如果您不必在整个怪异的地方都没有finally异常,那么仍然需要手动进行错误传播来解决代码。

扭转外部副作用

这是最难解决的概念问题。如果有任何功能(无论是错误传播者还是故障点)引起外部副作用,那么它需要回滚或“撤消”这些副作用,以使系统恢复为从未发生过的状态,而不是“半有效”状态表示操作成功。我知道没有一种语言可以使这个概念问题变得更加容易,除了那些可以简单地减少大多数功能首先导致外部副作用的语言(例如围绕不变性和持久数据结构的功能性语言)。

在这里,catch可以说是解决围绕可变性和副作用的语言中最优雅的解决方案之一,因为这种逻辑通常是特定于特定功能的,并且与“资源”的概念映射得不太好清理”。并且我建议在这些情况下自由使用finally,以确保您的函数以支持它的语言来消除副作用,而不管您是否需要finally块(同样,如果您问我,编写良好的代码应具有最小的catch块的数量,并且所有catch块都应放在catch上图所示的最有意义的位置。)对于副作用逆转而言,接近理想值,但效果并不理想。我们需要引入一个Load Image User Command变量,以在发生过早退出(从抛出异常或其他异常退出)的情况​​下有效回滚副作用,例如:
我曾经可以设计一种语言,解决这个问题的理想方式就是将上述代码自动化:

bool finished = false;
try
{
    // Cause external side effects.
    ...

    // Indicate that all the external side effects were
    // made successfully.
    finished = true; 
}
finally
{
    // If the function prematurely exited before finishing
    // causing all of its side effects, whether as a result of
    // an early 'return' statement or an exception, undo the
    // side effects.
    if (!finished)
    {
        // Undo side effects.
        ...
    }
}
本地资源,因此我们只需要finallybooleantransaction(尽管我可能仍想添加rollback来处理不清理自身的C资源)。但是,带有catch变量的finally是使这一点变得最直接的方法,因为到目前为止我发现我缺少梦想的语言。我发现的第二个最直接的解决方案是C ++和D等语言中的范围保护,但是从概念上讲,我总是发现范围保护有点尴尬,因为它模糊了“资源清理”和“副作用反转”的思想。我认为这些是非常不同的想法,需要用不同的方式来解决。

我对语言的小梦想也将极大地围绕不变性和持久性数据结构,以使其(尽管不是必需的)更加容易地编写高效的函数,即使该函数导致了不必要深度复制整个数据结构没有任何副作用。相当于C ++的析构函数,我个人认为应该将它自由地用于需要消除副作用的地方,并尽量减少需要的地方。

评论


太糟糕了,该用户消失了。在大多数答案都符合该标准的情况下,SO会更好。

–寡核苷酸
20-11-21在14:07

#8 楼

即使不是强制性的,也强烈建议捕获错误/异常并以整洁的方式处理它们。

之所以这么说,是因为我相信每个开发人员都应该了解并解决其应用程序的行为,否则他不会适当地完成工作。在任何情况下,try-finally块都不会取代try-catch-finally块。

我将给您一个简单的示例:假设您已编写了用于在服务器上上传文件而没有捕获异常的代码。现在,如果由于某种原因上传失败,客户端将永远不会知道出了什么问题。但是,如果您捕获了异常,则可以显示一条整洁的错误消息,说明发生了什么错误以及用户如何对其进行补救。 >

评论


-1:在Java中,可能需要finally子句才能释放资源(例如,关闭文件或释放数据库连接)。这与处理异常的能力无关。

–kevin cline
2012年1月23日在18:47



@kevincline,他不是在问是否要使用finally,...他所要问的只是是否需要捕获异常...。他知道try,catch和finally会做什么.....最后是最重要的部分,我们都知道它以及为什么使用它。

–潘卡吉·阿帕德(Pankaj Upadhyay)
2012年1月23日在18:52



@Pankaj:您的答案表明,每次尝试时都应始终存在catch子句。包括我在内的更多经验丰富的撰稿人都认为这是不明智的建议。您的推理有缺陷。包含try的方法不是捕获异常的唯一可能的地方。通常最简单,最好的做法是允许从最高级别捕获和报告异常,而不是在整个代码中复制catch子句。

–kevin cline
2012年1月23日19:25

@kevincline,我相信其他人对问题的看法有所不同。问题恰好是关于我们是否应该捕获例外……我在这方面回答了。可以通过多种方式而不是最终尝试来处理欺骗。但是,这不是OP的问题。如果提出例外对您和其他人足够好,那么我必须说好运。

–潘卡吉·阿帕德(Pankaj Upadhyay)
2012年1月24日下午5:48

除例外之外,您希望中​​断语句的正常执行(并且无需在每个步骤中手动检查是否成功)。在深层嵌套的方法调用中尤其如此-在某个库中的4层方法不能仅仅“吞噬”异常;而是将方法嵌套在其中。它需要从所有层次上扔掉。当然,每一层都可以包装异常并添加其他信息。通常由于各种原因而无法做到这一点,因为要花更多的时间发展和不必要的冗长是两个最大的问题。

–丹尼尔B
2012年9月18日上午11:42