2010-09-02 24 views
6

我总是努力在哪里放置一个try catch块。例如,我有一个接受两个参数的方法的数据库类。 FindObject(string where,string order)。此方法使用指定的where和order字符串执行sql查询。在哪里实现try catch块?

在一个类中我有一个名为IsUsed属性,这个属性是这样的:

public bool IsUsed 
{ 
get 
{ 
    ClassA a = new ClassA(); 
    Collection<ClassA> myCollection = a.FindObject("Id = 1",""); 

    if(..) // etc 
} 
} 

,如果这种做法是聪明与否没关系,我只是想知道在何处放置试如果sql查询的执行出错,就赶上。

我应该在哪里放置try catch,以便我可以通知用户出了什么问题?

  1. 在FindObject方法中?
  2. 在IsUsed属性中?
  3. 我在哪里调用IsUsed属性?
  4. 其他地方?但是,在
+0

可能的重复,虽然不是特定于C#:http://stackoverflow.com/questions/2119780/try-catch-blocks-inside-or-outside-of-functions-and-error-handing – 2010-09-02 08:16:55

回答

6

只有在你能做些什么的情况下才能发现异常。否则,在您的应用程序中以“最高”级别捕获异常,并执行所需的任何操作来处理它,包括终止您的应用程序。

在具有UI的应用程序中,“最高”级别通常是事件处理程序,例如搜索按钮的点击处理程序。对于后台服务来说,“最高”级别通常是线程特效或定时器回调。

UI application       Background service 

| System Code |      | System Code | 
+----------------+      +----------------+ 
| Event Handler | <- try/catch here -> | Thread proc | 
|    |      |    | 
| ...   |      | ...   | 
|    |      |    | 
| Method   | <- throw here  -> | Method   | 

如果你让异常传播回系统代码,你将有一个未处理的异常,你的应用程序将崩溃。

在UI应用程序中处理异常通常涉及显示一个消息框。一些异常并不致命,并且操作可能会重试(例如,如果文件丢失或数据库查询失败),但其他异常是致命的,唯一的选择是终止应用程序。

后台服务将记录异常并可能重试该操作。如果多次重试失败,日志记录级别可能会增加,以引起操作员的注意。

当谈到异常处理的良好做法:

  • 如果你发现一个异常并重新抛出您自己的异常封装了原始异常作为新的异常的InnerException
  • 如果您发现异常,可能需要做一些清理工作,但重新抛出异常,因为您希望它冒泡,然后始终使用throw重新抛出它,而不指定任何异常。这将确保原始堆栈跟踪不被破坏。
  • 在大多数情况下,您只能在顶级处理程序中捕获基类Exception
  • 使用finally块或甚至更好的IDisposable模式来执行代码中的适当清理。
  • 将异常看作是开发人员的“用户界面”,并相应地格式化异常消息。如果你需要更技术性更好的用户界面,你应该隐藏更多技术性的东西。
  • 尝试仅在异常情况下使用异常,例如意外错误。不要为常见错误情况抛出异常。检查字典中键的存在不应引发异常,而是返回true/false值。

要回答你的具体问题,我不认为你应该捕捉你的财产的任何例外。

11

我尽量遵循一个相当简单的规则:我设置了一个try..catch块如果我能以一种合理的方式处理异常。如果我对异常没有任何意义,我会让它冒泡到调用代码中。

作为一个旁注,我会避免在属性获取器中执行(可能冗长的)数据库调用。我通常试图让属性刚刚设置或从后台字段获取值,让其他方法执行数据库查找等。这会使代码对于编写调用代码的人更具可预测性(对于属性访问来说这是一个便宜的操作,而方法调用通常可能更昂贵)。

+0

谢谢。我会用你的方法。我应该在IsUsed方法中引发一个消息框来通知用户发生了异常吗? – Martijn 2010-09-02 08:24:02

+1

@Martijn:在不了解代码的情况下,我不能说是否显示消息框是个好主意,但我尽量避免这样做,除非代码非常接近最终用户。如果代码位于类库中,则显示消息框有效使其无法在Web应用程序或类似应用程序中使用。 – 2010-09-02 08:42:37

+1

我还想补充一点,这可能是一种很好的方法来让异常被抛出,但有时候你也必须使用'finally'块(特别是对于DB调用,确保关闭连接 - 如果不使用'使用“块”)。在这种情况下,您只需在'catch'部分重新抛出异常。 – 2010-09-02 08:44:24

3

你应该把try-cacth块放在你可以做一些有意义的事情的地方。就像你登录或显示给用户一样。不要仅仅因为它而捕捉异常。

+0

但我的观点是,向用户展示它的最佳位置是什么? – Martijn 2010-09-02 08:24:37

2

那么它应该在哪里try..catch块是需要的,并且它可以被处理。我假设这将在FindObject方法。

因此,在FindObject方法中,捕获并处理例如SqlException。如果需要将其移至更高级别以获得更好的处理,请抛出异常,或让它简单地冒泡。

+0

如果该方法失败,我想通知用户发生了数据库错误。我该如何以及在哪里做这件事? – Martijn 2010-09-02 08:25:45

+0

@Martijn - 最好的做法是将数据库代码从'IsUsed'属性移开,并且有一个方法调用它。然后,您可以从某种初始化代码的方法中处理异常。对属性的数据库调用可能会变得很难。而是使用初始化代码并设置属性值。 – 2010-09-02 08:44:07

2

我能想到的唯一规则就像是“在最低级别上,您可以实际上对信息做一些有用的事情,而且不会重复执行异常处理代码”。例如,如果您通常希望以相同的方式捕获所有与数据库访问相关的异常,我会创建一个数据抽象层(有很多很好的理由可以这么做),并将try-catch块放在那里。

另一个极端将是一个Web应用程序,您不希望出现任何此类异常,并且Elmah会捕获异常。在这种情况下,你根本不想使用try-catch块,因为它会搞砸日志。

2

它很容易回答:你可以在哪里适当地处理它。

I.e,在特定的地方,你能否根据这个问题做出有用的决定?你能回到别的东西吗?你能告诉某人什么吗?你能否将错误转化为对应用程序的其他层更有用的错误?

如果答案是肯定的,那么你可以在那里找到答案。如果不是所有这些,那么不要抓住它,让另一个区域这样做。

FWIW,恕我直言,你的Get执行也过于复杂。我认为通常情况下,人们不会期望一个财产做这种“工作”。但我的意见。

0

就我而言,我把尝试捕捉块:

  • 最后需要清理ressources
  • 时,需要在失败的情况下适用的操作流程(用户重试,日志记录...)

通常,我让异常冒泡到顶层,通常是一些winform控件事件处理程序。我用在这里试着抓住,使用应用程序级别的异常处理方法。

否则,当我特别懒惰,我钩Application.ThreadException事件到MessageBox.Show静态无效的主要()入口点方法。

+2

但是你不需要一个catch块来有一个finally块。 – Polyfun 2010-09-02 10:06:10

-1

请参阅,如果您遇到任何错误将IsUsed设置为默认值。这样的事情:

public bool IsUsed 
{ 
get 
{ 
    try{ 
    ClassA a = new ClassA(); 
    Collection<ClassA> myCollection = a.FindObject("Id = 1",""); 

    if(..) // etc 
    } 
    catch { return false;} 
} 
} 

所以,每当在FindObject中有任何错误,您将产生false。

+0

我想过这种方法,但我不喜欢,因为这样我永远不知道是否发生了数据库异常。所以这使得调试更加困难.. – Martijn 2010-09-02 08:33:58

+0

@Martijn:是的。永远不要吞噬异常,除非它真的是虚假的,或者是不需要成功的操作的一部分,例如调试日志记录(对于某些程序)。 – siride 2010-09-02 14:33:17

+0

@Martijn当然如果你需要的话,你可以抛出异常。但是通常情况下,如果属性发生异常,我们只返回属性的默认值。如果您的例外情况非常敏感,那么您可以记录例外消息并向用户显示一条好消息,而不是停止整个程序。 – abhishek 2010-09-04 15:22:28

1

一般来说,作为rule of thumb

的try块包含保护代码是可能导致异常

内部在你的情况,你可以处理它想:

FindObject() 
{ 
try{}catch{throw;//or throw new Exception("Some info"); 
} 

IsUser 
{ 
try{...a.FindObject("Id = 1",""); return true;} 
catch(Exception ex){Log(ex); return false;} 
} 

- EDIT--

这是响应@controlbreak评论:

MSDN说:

您可以显式地抛出使用throw语句异常。您还可以使用throw语句再次引发捕获的异常。 好的编码练习将信息添加到重新抛出的异常,以便在调试时提供更多信息。

+0

为什么要捕捉一个异常,什么也不做,然后再扔掉? – Martijn 2010-09-02 08:34:52

+0

catch {throw;}是unusefull。无论如何,很高兴提及catch(单独(不是catch(Exception ...))可以用来捕获非托管异常 – Larry 2010-09-02 08:41:44

+0

@Martinjn:你捕获异常并再次抛出,因为你目前没有处理程序它不知道你想要怎么处理这个特定的错误,但是你通过重新抛出它来让它“起泡”,这样某个“上层”可能知道该怎么处理这个错误。捕捉一个“服务器忙”的异常,你可以通过尝试再次连接来处理它,但你也会发现一个异常说“服务器关闭”,现在你不知道该怎么做......所以你把它泡了起来。调用方法可以很好地捕捉和处理错误 – ace 2010-09-02 08:43:48

1

这取决于您想要处理的地方继续处理。假设调用代码负责通知用户,那么一个好的地方就是调用代码。在通知用户之后,调用代码可能需要向用户提问。

还考虑重新构造引发的异常。如果IsUsed驻留在与数据库无关的类中并且FindObject可以抛出SqlServerTimedOutException,它将弹出整个调用堆栈。如果这schemantics是完全混乱捕获异常并重新抛出这样的:

 public bool IsUsed 
    { 
     get 
     {  
      ClassA a = new ClassA();  
      Collection<ClassA> myCollection; 

      try 
      { 
       myCollection = a.FindObject("Id = 1",""); 
      } 
      catch (SqlServerTimedOutException ex) 
      { 
       throw new MyApplicationException("Cannot evaluate if is in use.", ex); 
      } 

      if(..) // etc 
     } 
    } 

但不要滥用这一点,因为它使代码比较难看。慎重考虑。

-1

我不同意你们大多数人。

首先,我想乔尔推荐这个伟大的帖子:http://www.joelonsoftware.com/items/2003/10/13.html

牢记这一点,我使用try-catch块仅接近他们可以去错了地。
显然,用户界面想要了解它。让它具有一个错误值,但不要隐藏实际问题的原因(通过捕获其他意外错误)。

如果我正确理解你的代码,我想我会在实际查询中使用try-catch,并让IsUsed nullabe确保我明白它没有赋值。

+0

理论上听起来不错,但事实是,在抛出它的地方,你可能无法处理异常。我的意思是,也许你可以,并在这种情况下,通过一切手段抓住和处理。但要求每个级别“处理”每一个错误不仅会造成难看的代码,而且可能会产生不正确的代码。原因:生成异常的代码实际上可能不足以处理它,只有较高层才会有这些信息(例如,决定是否重试)。 – siride 2010-09-02 14:31:29

0

尽早捕获错误,并设计API调用,以便在不使用异常的情况下处理失败。未捕获的异常是稳定性的威胁,并且您可能不知道是否存在边缘情况中出现的异常,除非较低的API表示它们可能会失败。

通常你可以设计api来让结果对象保持执行状态,这会告诉消费开发者这种方法可能会失败以及失败。

这样的方法通常会给你有意义的方式来处理低级API部分中的异常,而不是让它们向上调用代码。

这就是说,有些语言可以声明一个方法会抛出异常,抛出哪些异常,但是你编写的C#没有这个功能。