如何在 Java 中管理复杂的流程控制?

发布于 2024-11-01 03:06:29 字数 642 浏览 1 评论 0原文

我在这里研究 Java 流控制和异常处理有一段时间了,并且有这些普遍接受的规则:

  1. 不要使用异常处理进行流控制,
  2. 避免检查异常,除非客户端期望恢复(这是一个很好的方式来表达你强制客户端处理异常,以便他们也可以尝试从中恢复?)

以及我尝试遵循的一般规则:

  1. 如果 doSomething() 无法“做某事”,则应该让调用者意识到这一点
  2. 方法应该关注做一件事,

这在某些情况下会导致糟糕的组合。我发现我在方法中捕获了已检查的异常,并在各处返回布尔值,并且不断地必须检查连续的调用,做出这样的事情:

if(doA()){
  if(doB()){
    if(doC()){ 
      // only here do I know it's good
    }else{
      // I know C failed
    }
  }else{
    // I know B failed
  }
}else{
  // I know A failed
}

我在某些部分得到了 5-6 个嵌套的 if-else,这非常难看。更不用说管理一堆布尔变量来跟踪哪些有效,哪些无效。

有什么建议吗?这是一个可以接受“goto”的地方吗?

I've been researching Java flow control and exception handling for a while here and have these generally accepted rules:

  1. don't use exception handling for flow control
  2. avoid checked exceptions unless the client expects to recover (which is a nice way of saying that you're forcing the client to handle the exception so they might as well try to recover from it?)

Along with general rules I try to follow:

  1. if doSomething() fails to "do some thing", the caller should be made aware of this
  2. methods should be concerned with doing one thing

This makes for a bad mix in some cases. I'm finding that I'm catching checked exceptions within methods and returning boolean everywhere and continually having to check successive calls, making something like this:

if(doA()){
  if(doB()){
    if(doC()){ 
      // only here do I know it's good
    }else{
      // I know C failed
    }
  }else{
    // I know B failed
  }
}else{
  // I know A failed
}

I'm getting 5-6 nested if-else's deep in some parts and it's pretty ugly. Not to mention managing a bunch of boolean variables to keep track of what worked and what didn't.

Any advice? Would this be a place where 'goto' is acceptable?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

素手挽清风 2024-11-08 03:06:29

您应该查看 doA()doB()doC()。如果它们不太可能失败,则在失败时抛出异常。

try {
  doA();
  doB();
  doC();
} catch (FailureException e) {
  // handle failure
}

不合理失败的例子比比皆是,IOExceptionIllegalParameterException等。

如果它们很可能失败,那么

if (!doA()) {
  // handle A's failure
  return;
}
if (!doB()) {
  // handle B's failure
  return;
}
if (!doC()) {
  // handle C's failure
  return;
}

合理失败的例子在Java中不太被强调。一些示例包括当没有更多内容可供读取时,read() 返回 -1。如果您的 doA() 的名称实际上更接近于 attemptA(),那么可能会返回一个 boolean 来指示尝试是否成功。想想 Collections 接口中的 add(...)addAll(...),它们返回 true 如果生成的 Collection 被修改。

在大多数语言中,传统的 goto 并不是一个好的选择,因为在检查代码时,几乎不可能知道代码“来自”哪里。由于缺乏对进入goto之前的状态的了解,因此无法保证进入goto块之前的一致环境。顺便说一句,这就是为什么传统的 goto 在 Java 中不可用,而只有有限的延续 goto 可用。

要从嵌套不良的结构转换为嵌套较少的结构,请使用一些重构技术:

if(doA()){
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
} else {
  // I know A failed
}
return;

相当于 相当于

if (doA()) {
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
  return;
} else {
  // I know A failed
  return;
}

如果

if (!doA()) {
  // I know A failed
  return;
} else {
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
  return;
}

“我知道 A 失败”中的代码包含返回,则您无需担心代码在条件 doA() 为 true 的情况下,落入下面的代码;这样你就可以提升较低的块,如下所示:

if (!doA()) {
  // I know A failed
  return;
}
if (doB()) {
  if (doC()) { 
    // only here do I know it's good
  } else {
    // I know C failed
  }
} else {
  // I know B failed
}
return;

You should look into the doA(), doB(), and doC(). If they are reasonably unlikely to fail, throw an exception when they fail.

try {
  doA();
  doB();
  doC();
} catch (FailureException e) {
  // handle failure
}

Examples of unreasonable failures abound, IOException, IllegalParameterException, etc.

If they are reasonably likely to fail

if (!doA()) {
  // handle A's failure
  return;
}
if (!doB()) {
  // handle B's failure
  return;
}
if (!doC()) {
  // handle C's failure
  return;
}

Examples of reasonable failures are a bit less emphasized in Java. Some examples include read() returning -1 when there's nothing more to read. If your doA() is actually named closer to attemptA() then perhaps returning a boolean indicating the success of the attempt is appropriate. Think of add(...) and addAll(...) in the Collections interface, they return true if the resulting Collection was modified.

A traditional goto is not a good choice in most languages, as when reviewing the code, it is practically impossible to know where the code "came from." This lack of knowledge of the state prior to entering the goto makes it impossible to guarantee a consistent environment before entry to the goto block. Incidentally, this is why a traditional goto is not available in Java, and only a limited continuation goto is.

To covert from a poorly nested structure to one that's less nested, use a few refactoring techniques:

if(doA()){
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
} else {
  // I know A failed
}
return;

is equivalent to

if (doA()) {
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
  return;
} else {
  // I know A failed
  return;
}

which is equivalent to

if (!doA()) {
  // I know A failed
  return;
} else {
  if (doB()) {
    if (doC()) { 
      // only here do I know it's good
    } else {
      // I know C failed
    }
  } else {
    // I know B failed
  }
  return;
}

If the code in "I know A failed" includes a return, then you don't need to worry about code under the condition doA() being true falling into the code below; so you can promote the lower block, like so:

if (!doA()) {
  // I know A failed
  return;
}
if (doB()) {
  if (doC()) { 
    // only here do I know it's good
  } else {
    // I know C failed
  }
} else {
  // I know B failed
}
return;
人海汹涌 2024-11-08 03:06:29

是的。使用异常而不是返回代码。如果您愿意,您可以将异常包装在自己的异常中。

在异常情况下使用异常进行流量控制是完全合理的。通常的建议是避免将它们用于普通流量控制。

Yes. Use exceptions rather than return codes. You can wrap exceptions in your own, if you like.

It is perfectly reasonable to use exceptions for flow control -- in exceptional conditions. The usual recommendation is to avoid their use for ordinary flow control.

素食主义者 2024-11-08 03:06:29

我认为你的规则“除非客户端希望恢复,否则不要使用检查异常”是错误的。让调用者根本不恢复,而是将异常传递给调用者,可能会将异常转换为其他内容,这是很常见的。

I think your rule "don't use checked exception unless the client expects to recover" is wrong. It is pretty common to let the caller not recover at all, but instead pass the exception along to its caller, possibly translating the exception to something else.

超可爱的懒熊 2024-11-08 03:06:29

我会使用状态模式。这样做可以使您的代码更易于阅读,因为您只能看到从当前状态可以进入哪些可能的状态。

I would use The State Pattern. Doing so can make your code much easier to read as you can only see what possible states you can go to from the current state that you're in.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文