在边缘情况下,尝试catch而不是if



在以下用例(性能和可读性方面)中,用try-catch替换if语句是个好主意吗

示例1

public static void AddInitializable(GameObject initializable)
{
if(!HasInstance)
{ // this should only happen if I have forgotten to instantiate the GameManager manually
Debug.LogWarning("GameManager not found.");
return;
}
instance.initializables.Add(initializable);
initializable.SetActive(false);
}
public static void AddInitializable2(GameObject initializable)
{
try
{
instance.initializables.Add(initializable);
initializable.SetActive(false);
}
catch
{
Debug.LogWarning("GameManager not found.");
}
}

示例2

public static void Init(int v)
{
if(!HasInstance)
{// this should happen only once
instance = this;
}
instance.alj = v;
}
public static void Init2(int v)
{
try
{
instance.alj = v;
}
catch
{
instance = this;
Init(v);
}
}

编辑:

问题2:我能得到多少个例外才能保持积极的表现?

这取决于情况。

Try块通常很便宜,所以当异常是而不是抛出时,这将是一个可以接受的解决方案。但是:在您的情况下,如果条件不满足(意味着在调用该方法之前没有初始化),这是一个编程错误,而不是在完成的程序中应该发生的事情。这样的错误使程序崩溃是完全正确的。使发现错误和修复它们在开发中变得更加容易,并避免了您默默地隐藏它(在示例1中,您默默地什么都不做,这可能会导致以后出现令人困惑的行为)。

所以:如果这是一个编程错误,不要使用异常处理程序,也不要使用测试(除了Assert)。只需让程序崩溃(在本例中为NullReferenceException)。

我同意PMF:取决于

在您的特定用例中,特别是某个东西是您的错误还是您无法控制/预测的东西。

因此,总的来说,我认为有三种方法可以处理行为不符合预期的

  • A)让抛出一个异常来指示这真的很糟糕,并且没有办法恢复=>很可能会使你的应用程序崩溃

    这在开发时通常是完全有意义的,因为在调试时,您明确地希望您的应用程序崩溃,这样您就可以找到并解决问题。

    这种情况应该发生在所有原因基本上是搞砸了并且可以由你解决的事情上。(在您的情况下,instance未正确初始化)

  • B) 返回一些东西,例如false,以指示出现了问题,但允许代码处理,例如尝试其他东西。

    在我看来,这应该是处理你无法控制自己的事情的首选方式,例如用户输入和其他不可预测的条件,如互联网连接等。

  • C) 忽略它,什么也不做。

    当然,这取决于你到底在做什么,但实际上这种情况几乎永远不会发生。对于用户来说,这可能会非常令人沮丧,对于作为开发人员的您来说,这会使调试变得困难到不可能!

    当然,结合B,这是有效的,因为其他问题已经存在。

一般来说,除非你处理一些核心/重用库,否则我自己永远不会抛出异常,除非你重新抛出捕获的异常来添加额外的调试信息。这基本上属于";你无法控制";其他人将如何使用您的库->从您的角度来看,这基本上属于用户输入;)


现在,这三个选项都可以通过try - catchif内部检查来实现,当然,这取决于您想要走哪条路。

我对的一些想法

  • 可读性方面,我更喜欢单独使用if,因为它可以明确检查哪个条件。当我看到try - catch时,我不知道第一眼可能会抛出哪个确切的异常。

    因此,使用try - catch作为if的替代品只会掩盖到底是什么失败了,并使难以进行调试

  • 例外情况相当昂贵!因此,在性能方面,我认为尽可能使用if

  • 然而,在某些情况下——在我看来,这是唯一允许try - catch的情况——你使用库,根本无法防止异常。


示例:FileIO

  • 您想要访问的文件不存在

    ->你不需要try - catch(在我看来,这是一种懒惰的方式)。这是你可以而且应该首先检查if(!File.Exists(...))的事情,这样你的程序就可以正确地处理它并处理这种情况(例如,你可能想告诉用户,而不是简单地崩溃或什么都不做)。

  • 该文件当前已被另一个程序打开,因此您无法对其进行写入。

    ->没有办法事先发现这一点。你会得到一个异常,无法避免。在这里,你想要try - catch,以便仍然允许你的代码处理这种情况(就像以前一样,例如告诉用户,而不是简单地崩溃)。

但你如何再次真正处理它们取决于:

  • 如果你使用一些硬编码路径,这些文件肯定应该在那里->异常,因为这意味着作为开发人员搞砸了一些事情。

  • 如果路径来自用户输入->Catch,因为这是您作为开发人员无法控制的但不只是希望您的应用程序崩溃,而是向用户显示他搞砸了的提示。


现在在您的用例中,Example 1和您的两个解决方案对我来说都很糟糕。您使用最后一个选项C,然后忽略调用-用户不会看到警告,开发人员也可能不会注意到/忽略它。

如果这意味着您的应用程序将无法正常运行并且根本无法捕获异常,那么您肯定希望在此处获得异常!

一般来说,不需要特殊的bool标志。我宁愿选择

if(instance == null)
{
Debug.LogError(...);
return;
}

因为这很可能是一个更严重的错误,而不仅仅是一个警告,所以它至少可以获得可见性。

Example 2中,实际上有一种惰性初始化,所以无论哪种方式,调用本身基本上都是有效的。

在这种情况下,你可以很容易地检查这一点,我不会等待异常(尤其是任何),因为我已经知道肯定至少会有一次。

在我看来,这应该是

if(instance == null)
{
// I have put `???` because, well, in a "static" method there is no "this" so 
// I wonder where the instance should come from in that case ;)
instance = ???;
}
instance.alj = v;

所以这里的思路是正确的。

除非你迫切需要提高性能,否则不要尝试优化,如果你确实需要优化,请确保你做得正确(异常比if语句更昂贵,尤其是如果你知道它们会发生的话)

你举的第一个例子,我有点落后了。你假设某个东西已经初始化,如果结果不是,就抛出一个错误。你正在记录它,它是好的,你初始化它,你可能再也不用担心这个异常了。

你给出的第二个例子是一个很大的否定。你不应该使用异常来陷入应用程序中的其他逻辑。相反,在Init()方法中,总是有一行"instance=this",不要执行if语句。一旦您知道它已经初始化,就不应该有理由在使用时抛出异常。

当然,不要对此感到疯狂,例外情况应该只用于特殊情况。如果你写代码时想"嗯,所以它可能是A场景或B场景,而在B场景中,我希望发生这种情况,所以我会抛出一个异常",这完全是错误的想法。相反,它应该是"嗯,所以这一切都会发生,但以防万一有什么东西坏了,我会把它放在一个尝试捕获并记录下来,谁知道呢,我不是万无一失的">

你可以看到我是如何将上面的逻辑应用到你的两个例子中的,

在我看来,这不是一个好主意。

当我们知道上下文中会出现什么类型的异常时,我们通常会使用try-catch,因此无异常类型的catch不是一个好的做法。此外,只有当异常很少发生时,try-catch才不昂贵。

在您的场景中,由于您已经知道唯一的问题是属性HasInstance可能为false,因此可以直接使用if语句进行检查。在这里,使用try-catch似乎更像是一种成本,尽管它是有效的。这看起来像是你在期待一个错误,而你只是忽略了这个错误,因为它的消息无关紧要。

此外,我看到你正在使用Unity并创建一个单例GameManager,实际上我认为这里的单例模式可能不太正确。

例如,如果你使用这样的代码,实际上,如果你正确处理场景和游戏对象,它几乎不可能没有实例:)

当;不可能";state出现在您的接口中(不是关键字,只是单词),如果您必须在业务逻辑中尝试catch,则您的设计会受到影响。

在这种情况下,如果实现稍有遗漏的命名为null的对象模式,您可能会获得可扩展性和可读性。(应称为默认对象模式)

并且永远无法将null接口传递给该方法。

因此:

public interface IGameObject {
void Activate(instance initializable);
}
public class GameObjectDefault : IGameObject {
public void Activate(instance initializable){
--Does nothing on purpose
}
}
public class GameObjectReal : IGameObject {
private Instance _instance;
public GameObjectReal(Instance instance)
{
_instance = instance;
} 
public void Activate(IGameObject initializable) {
_instance.initializables.Add(initializable);
--Do whatever you need to do to the object
}
}

这是伪的,因为我看不到你的整个系统。但通过这种方式,如果您将所有游戏对象初始化为DefaultGameObjects,那么您的activate或任何其他方法都不会起任何作用。

现在,没有理由检查null。没有理由尝试接球。你不可能的状态,现在,从字面上讲,是不可能的。

最新更新