尝试捕获每一行代码,而无需单独的try-catch块

时间:2020-03-06 14:33:41  来源:igfitidea点击:

我目前没有这个问题,但是我们永远都不会知道,而且思想实验总是很有趣。

忽略尝试甚至要尝试执行此操作所必须面对的明显问题,让我们假设我们拥有别人设计的可怕代码,而我们需要在同一代码中进行一系列广泛而多样的操作阻止,例如:

WidgetMaker.SetAlignment(57);
contactForm["Title"] = txtTitle.Text;
Casserole.Season(true, false);
((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true;

乘以一百。其中一些可能会起作用,另一些可能会严重出错。我们需要的是"下一次错误恢复"的等效项,否则我们将最终在许多代码行中复制并粘贴try-catches。

我们将如何解决这个问题?

解决方案

在Cworld中,On Error Resume Next是一个非常糟糕的主意。添加与" On Error Resume Next"等效的内容也不会真正。它所要做的就是使我们处于不良状态,这可能会导致更多的细微错误,数据丢失甚至可能损坏数据。

但是要给提问者以应有的责任,我们可以添加一个全局处理程序并检查TargetSite以查看哪个方法失败了。然后,我们至少可以知道它中断了哪条线。下一部分将尝试找出与调试器相同的方式设置"下一条语句"。希望此时堆栈不会解开,或者我们可以重新创建它,但是肯定值得一试。但是,使用这种方法,代码每次都必须在Debug模式下运行,这样调试符号就会包括在内。

不幸的是,我们可能不走运。在错误恢复上,下一步是一个遗留选项,通常不鼓励使用,并且不具备我在C#中的知识。

我建议将代码保留在VB中(根据我们对OnError ResumeNext的特定要求,这听起来像是源代码),并与实现所需新代码的Cdll或者exe进行交互。然后执行重构以使代码安全,然后将此安全代码转换为Cas。

重写代码。尝试查找在逻辑上相互依赖的语句集,这样,如果一个语句失败,那么下一个语句就没有意义了,如果我们想忽略它们的结果,则将它们分配到它们自己的函数中并用try-batch包围它们那并继续。

每行一次高亮显示,一次"环绕"尝试/捕获。这样就避免了我们提到的复制粘贴

重构为单独的,命名良好的方法:

AdjustFormWidgets();
SetContactTitle(txtTitle.Text);
SeasonCasserole();

其中的每一个都受到适当的保护。

如果可以让编译器为该代码提供一个表达式树,则可以通过用包装原始语句的新try-catch块替换每个语句来修改该表达式树。这并不像听起来那样牵强。对于LINQ,获得了将lambda表达式捕获为表达式树的功能,可以在运行时在用户代码中对其进行操作。

在.NET 3.5中,今天这种方法是不可能的-if除了System.Linq.Expressions中缺少" try"语句外,没有其他原因。但是,在Conce的未来版本中,DLR和LINQ表达式树的合并已完成非常有可能。

忽略所有我们想要避免这样做的原因。

如果仅是需要减少行数,则可以尝试执行以下操作:

int totalMethodCount = xxx;
for(int counter = 0; counter < totalMethodCount; counter++) {
    try {
        if (counter == 0) WidgetMaker.SetAlignment(57);
        if (counter == 1) contactForm["Title"] = txtTitle.Text;
        if (counter == 2) Casserole.Season(true, false);
        if (counter == 3) ((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true;
    } catch (Exception ex) {
        // log here
    }
}

但是,如果我们尝试重用任何调用结果,则必须注意可变范围。

这是拥有预处理器有用的事情之一。我们可以定义一个吞入异常的宏,然后使用快速脚本将该宏添加到所有行。

因此,如果这是C ++,则可以执行以下操作:

#define ATTEMPT(x) try { x; } catch (...) { }
// ...
ATTEMPT(WidgetMaker.SetAlignment(57));
ATTEMPT(contactForm["Title"] = txtTitle.Text);
ATTEMPT(Casserole.Season(true, false));
ATTEMPT(((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true);

不幸的是,似乎没有多少语言像C / C ++那样包含预处理器。

我们可以创建自己的预处理器,并将其添加为预构建步骤。如果我们想完全自动化,则可以编写一个预处理器,该预处理器将接收实际的代码文件,并单独添加try / catch内容(因此我们不必手动将这些ATTEMPT()块添加到代码中) 。确保它仅修改了原本可能很困难的行(必须跳过变量声明,循环构造等,以免破坏构建)。

但是,我认为这些想法很可怕,绝不应该做,但有人提出了这个问题。 :)

确实,我们永远都不应该这样做。我们需要查找导致错误的原因并进行修复。吞咽/忽略错误是一件坏事,因此,我认为这里的正确答案是"修复错误,不要忽略它!"。 :)

我们可以查看如何集成企业库的异常处理组件,以获取有关如何处理未处理的异常的一种想法。

如果这是针对ASP.Net应用程序的,则Global.asax中有一个称为" Application_Error"的函数,在大多数情况下会被调用,而灾难性故障通常是另一种情况。

我们可以使用goto,但是它仍然很混乱。

我实际上一直想要一种单语句try-catch。在某些情况下会很有用,例如添加日志记录代码,或者我们不想在主程序流失败时中断它。

我怀疑可以使用与linq相关的某些功能来完成某些工作,但是目前还没有足够的时间来研究它。如果我们只是找到一种将语句包装为匿名函数的方法,然后使用另一种方法在try-catch块中调用它就可以了……但不确定是否可行。

快速失败

详细地说,我想我是在质疑这个问题。如果引发异常,为什么要让代码继续执行就好像什么都没发生一样?我们可能希望在某些情况下出现异常,在这种情况下,我们应在该代码周围编写一个try-catch块并进行处理,或者出现意外错误,在这种情况下,我们应该希望应用程序中止,重试或者失败。不要像受伤的僵尸mo吟的"大脑"那样继续前进。

这可以确定问题最多的部分。

@ JB金
谢谢你提醒我。日志记录应用程序块具有一个可用于跟踪事件的检测事件,我们可以在MS Enterprise库文档中找到更多信息。

Using (New InstEvent)
<series of statements> 
End Using

此使用过程中的所有步骤都将追溯到一个日志文件,我们可以解析该日志以查看日志中断的地方(抛出异常)并找出违规者。

重构确实是我们最好的选择,但是如果我们有很多事情,这可能会确定最严重的违规者。

我会说什么都不做。

是的,没事。

我们向我明确指出了两件事:

  • 我们知道架构令人厌烦。
  • 有很多这样的废话。

我说:

  • 没做什么。
  • 添加一个全局错误处理程序,以便每次发送电子邮件时向我们发送电子邮件。
  • 等到某物掉落(或者测试失败)
  • 更正该错误(在页面范围内根据需要进行重构)。
  • 每次出现问题时重复一次。

如果那样糟的话,我们会立即将其清除。是的,我知道这听起来很烂,我们可能一开始就需要修正一些错误,但是它可以让我们在大量的代码生效之前修复有需要的代码,无论它多么糟糕。看起来。

一旦我们赢得了战争,我们将对代码有更好的了解(由于所有重构),我们将有一个更好的构想。

尝试将所有内容包装在气泡包装纸上可能只需要很长的时间,而我们仍将无法更接近解决问题。

public delegate void VoidDelegate();

public static class Utils
{
  public static void Try(VoidDelegate v) {
    try {
      v();
    }
    catch {}
  }
}

Utils.Try( () => WidgetMaker.SetAlignment(57) );
Utils.Try( () => contactForm["Title"] = txtTitle.Text );
Utils.Try( () => Casserole.Season(true, false) );
Utils.Try( () => ((RecordKeeper)Session["CasseroleTracker"]).Seasoned = true );

很明显,我们将在VB.NET中编写代码,该代码实际上具有On Error Resume Next,并将其以DLL格式导出到C#。什么都只是being嘴
惩罚

就像有人提到的那样,VB允许这样做。在C#中以相同的方式进行操作怎么样?输入可信赖的反射器:

这:

Sub Main()
    On Error Resume Next

    Dim i As Integer = 0

    Dim y As Integer = CInt(5 / i)

End Sub

转换为:

public static void Main()
{
    // This item is obfuscated and can not be translated.
    int VB$ResumeTarget;
    try
    {
        int VB$CurrentStatement;
    Label_0001:
        ProjectData.ClearProjectError();
        int VB$ActiveHandler = -2;
    Label_0009:
        VB$CurrentStatement = 2;
        int i = 0;
    Label_000E:
        VB$CurrentStatement = 3;
        int y = (int) Math.Round((double) (5.0 / ((double) i)));
        goto Label_008F;
    Label_0029:
        VB$ResumeTarget = 0;
        switch ((VB$ResumeTarget + 1))
        {
            case 1:
                goto Label_0001;

            case 2:
                goto Label_0009;

            case 3:
                goto Label_000E;

            case 4:
                goto Label_008F;

            default:
                goto Label_0084;
        }
    Label_0049:
        VB$ResumeTarget = VB$CurrentStatement;
        switch (((VB$ActiveHandler > -2) ? VB$ActiveHandler : 1))
        {
            case 0:
                goto Label_0084;

            case 1:
                goto Label_0029;
        }
    }
    catch (object obj1) when (?)
    {
        ProjectData.SetProjectError((Exception) obj1);
        goto Label_0049;
    }
Label_0084:
    throw ProjectData.CreateProjectError(-2146828237);
Label_008F:
    if (VB$ResumeTarget != 0)
    {
        ProjectData.ClearProjectError();
    }
}

为什么不在C#中使用反射?我们可以创建一个反映代码的类,并使用行号作为在每个单独的try / catch块中放置内容的提示。这有一些优点:

  • 它稍微不那么丑陋,因为它实际上并不需要我们修改源代码,并且只能在调试模式下使用它。
  • 在实现它的同时,我们会学到一些有关c#的有趣知识。

但是,我建议不要采取任何此类措施,除非我们当然要接管保养工作,并且我们需要处理这些异常,以便可以对其进行修复。也许会很有趣。

有趣的问题;非常可怕

如果可以使用宏,那就太好了。但这是C#爆炸的代码,因此我们可以使用一些预处理器工作或者一些外部工具来解决它,以将行包装在单独的try-catch块中。不知道我们是要手动包装它们还是要完全避免尝试捕获。

为此,我试图给每条线贴上标签,并从一个接发处跳回去,但运气不佳。但是,克里斯托弗(Christopher)发现了正确的方法。 Dot Net Thoughts和Mike Stall的.NET Blog中对此进行了一些有趣的添加讨论。

编辑:当然。列出的try-catch/switch-goto解决方案实际上不会编译,因为catch中的try标签不在范围内。任何人都知道制作这样的东西缺少什么吗?

我们可以使用编译器的预处理步骤来自动执行此操作,也可以修改Mike Stall的Inline IL工具来注入一些无知的错误。

(Orion Adrian关于检查异常并尝试设置下一条指令的答案也很有趣。)

总而言之,这似乎是一个有趣而有启发性的练习。当然,我们必须决定在哪一点上模拟ON ERROR RESUME NEXT的努力胜于修复代码的努力。 :-)