繁体   English   中英

在没有单独的 try-catch 块的情况下尝试捕获每一行代码

[英]Try-catch every line of code without individual try-catch blocks

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

忽略你的体系结构必须有的明显问题,甚至尝试这样做,让我们假设你有一些别人设计的可怕的代码,你需要在相同的代码中做一堆广泛和不同的操作块,例如:

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

乘以一百。 其中一些可能有效,另一些可能会出现严重错误。 您需要的是 C# 等效的“on error resume next”,否则您最终将围绕多行代码复制和粘贴 try-catch。

您将如何尝试解决这个问题?

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 );

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

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

每一个都得到适当的保护。

我会说什么都不做

是的,没错,什么都不做。

你已经清楚地向我确定了两件事:

  1. 你知道架构很无聊。
  2. 有很多这样的废话。

我说:

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

如果它真的那么糟糕,你很快就会把它清理干净。 是的,我知道这听起来很糟糕,你可能一开始就用错误修复来拉扯你的头发,但它会让你在(大量)可能实际工作代码之前修复有需要的/错误的代码,无论它多么糟糕看起来。

一旦你开始赢得战争,你就会更好地处理代码(由于你所有的重构)你会对它的成功设计有更好的想法。

尝试用气泡纸包裹所有这些可能需要很长时间才能完成,而且您仍然离解决问题还差得很远。

很明显,您会在 VB.NET 中编写代码,它实际上确实有On Error Resume Next ,并将其导出到 DLL 中到 C#。 其他任何东西都只是被惩罚的贪吃。

快速失败

详细说明,我想我是在质疑这个问题。 如果抛出异常,为什么您希望您的代码继续运行,就好像什么都没发生一样? 在某些情况下,您可能会期望异常,在这种情况下,您围绕该代码编写一个 try-catch 块并处理它们,或者出现意外错误,在这种情况下,您应该希望您的应用程序中止、重试或失败。 不要像受伤的僵尸一样继续呻吟“大脑”。

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

所以,如果这是 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() 块添加到代码中) . 确保它只修改了它应该修改的行可能很困难(必须跳过变量声明、循环构造等,以免破坏构建)。

但是,我认为这些都是可怕的想法,永远不应该这样做,但有人提出了这个问题。 :)

真的,你不应该这样做。 您需要找到导致错误的原因并修复它。 吞下/忽略错误是一件坏事,所以我认为这里的正确答案是“修复错误,不要忽略它。”: :)

On Error Resume Next在 C# 世界中是一个非常糟糕的主意。 添加相当于On Error Resume Next的等价物也不会真正帮助您。 它所做的只是让您处于糟糕的状态,这可能会导致更细微的错误、数据丢失和可能的数据损坏。

但是为了给提问者应有的待遇,您可以添加一个全局处理程序并检查 TargetSite 以查看哪个方法失败了。 那么你至少可以知道它在哪条线上停住了。 下一部分将尝试弄清楚如何像调试器那样设置“下一条语句”。 希望此时您的堆栈不会展开,或者您可以重新创建它,但这当然值得一试。 但是,如果使用这种方法,代码每次都必须在调试模式下运行,这样您才能包含调试符号。

正如有人提到的,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();
    }
}

重写代码。 尝试找到逻辑上相互依赖的语句集,这样如果一个语句失败,那么下一个语句就没有意义了,如果你想忽略然后继续。

这可能会帮助您确定问题最多的部分。

@JB King 谢谢提醒。 Logging 应用程序块具有可用于跟踪事件的 Instrumentation Event,您可以在 MS Enterprise 库文档中找到更多信息。

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

此使用中的所有步骤都将跟踪到一个日志文件,您可以将其解析出来以查看日志中断的位置(抛出 ex)并识别高违规者。

重构确实是你最好的选择,但如果你有很多,这可能会帮助你找出最严重的违规者。

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

这种方法在今天的 .NET 3.5 中是不可能的——如果没有其他原因,只是在 System.Linq.Expressions 中缺少“try”语句。 但是,一旦 DLR 和 LINQ 表达式树的合并完成,它可能在未来的 C# 版本中非常可行。

可以使用 goto,但它仍然很乱。

我实际上想要一种单语句 try-catch 有一段时间了。 这在某些情况下会很有帮助,例如添加日志记录代码或您不想在主程序失败时中断主程序流程的内容。

我怀疑可以使用与 linq 相关的一些功能来完成一些事情,但目前真的没有时间研究它。 如果你能找到一种方法将语句包装为匿名函数,然后使用另一个方法在 try-catch 块中调用它,它会起作用……但不确定这是否可能。

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

  1. 它稍微不那么难看,因为你不需要破坏你的源代码,你只能在调试模式下使用它。
  2. 在实施 C# 时,您会学到一些有趣的东西。

但是,我建议不要这样做,除非您当然要接管某些工作的维护并且您需要处理异常以便修复它们。 虽然写起来可能很有趣。

有趣的问题; 非常可怕。

要是能用个宏就好了但这是糟糕的 C#,因此您可以使用一些预处理器工作或一些外部工具来解决它,将您的代码行包装在单独的 try-catch 块中。 不确定您的意思是不想手动包装它们还是想完全避免 try-catch。

为了解决这个问题,我尝试标记每一行并从一次捕获中跳回,但运气不佳。 然而, 克里斯托弗发现了这样做的正确方法 在 Dot Net Thoughts 和Mike Stall 的 .NET 博客上有一些关于此的有趣的附加讨论

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

您可以使用编译器预处理步骤自动执行此操作,或者破解Mike Stall 的内联 IL 工具以注入一些错误忽略。

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

总而言之,这似乎是一个有趣且有启发性的练习。 当然,您必须决定在什么时候模拟 ON ERROR RESUME NEXT 的努力超过修复代码的努力。 :-)

在应用程序的UnhandledException 事件中捕获错误。 这样,甚至可以将未处理的异常记录到发件人以及开发人员认为合理的任何其他信息。

不幸的是,你可能不走运。 On Error Resume Next 是一个遗留选项,通常非常不鼓励,并且不具备与我在 C# 中的知识相当的选项。

我建议将代码保留在 VB 中(这听起来像是源代码,考虑到您对 OnError ResumeNext 的特定请求)并与实现您需要的任何新代码的 C# dll 或 exe 交互或从中交互。 然后进行重构以使代码安全,并在执行此操作时将此安全代码转换为 C#。

您可以查看集成企业库的异常处理组件,以了解如何处理未处理的异常的一种想法。

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

忽略你想避免这样做的所有原因......

如果只是需要减少行数,您可以尝试以下操作:

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
    }
}

但是,如果您尝试重用调用的任何结果,则必须注意变量范围。

Hilite 每一行,一次一个,'环绕' try/catch。 这样就避免了你提到的复制粘贴

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM