我应该什么时候处理异常,什么时候抛出异常?

When should I handle exceptions and when should they be thrown?

在编程时,我发现自己很难思考我的程序应该如何工作,当 可能抛出异常时

举个例子:

    public void AddComponent(IEntityComponent component)
    {
        if (component == null)
        {
            // Should I throw an ArgumentNullException or should I just return?
        }

        if (ContainsComponentOfType(component.GetType()))
        {
            // Should I return here? Or should I throw an ArgumentException?
        }

        // Finally, we know we can add the component to the entity
        components.Add(component);
    }

    public bool ContainsComponentOfType(Type componentType)
    {
        if (componentType == null)
        {
            // Should I throw an exception here? Should I return false?
        }

        return components.Any(c => c.GetType() == componentType);
    }

请注意,上面的代码将被使用我的引擎创建游戏的人使用。

这取决于你开发什么以及它应该如何使用以及谁使用它...

在您开发游戏引擎的情况下,最佳做法是抛出异常并将主题转发给最终用户(使用您的引擎开发游戏的开发人员),让开发人员处理异常并做适当的工作he/she想要。

始终尝试检查所有可能的异常并提供有关异常的有用信息以及修复引发异常的错误的方法。一般异常处理是针对你对主题一无所知的未知和不需要的异常。

这取决于对您来说什么是错误以及什么是异常,只是因为您的代码检测到某些事情不正常并且无法继续,您应该抛出异常。

异常,顾名思义,发生在您的代码无法控制或无法控制的异常情况下(即网络连接中断,由于其他程序已锁定而无法保存文件,您拥有空引用)不受控制等)。

对于计算机来说,抛出异常往往代价高昂,因为它必须包括一堆发生的事情、调用堆栈的位置和状态信息,正因为如此,你希望尽可能多地避免异常尽可能。

由于预计您的代码可能无法 运行 在您考虑的一组受控条件下,控制流程和 return 值表示该过程没有达到预期的 运行,但在这种情况下,由于您控制的某些预期错误已经发生并立即通知该错误。

总结一下:

  • 避免为您的方法的流程控制抛出异常,因为它们代价高昂并且会在使用您的程序的系统上造成不必要的缓慢。
  • 仅抛出您的代码中未涵盖或您无法控制的异常(即用户正在使用的另一个程序 运行ning 抓取了您要使用的文件,而您没有考虑这种可能性).
  • 尽可能多地控制您认为必要的错误,并向用户提供有关发生的事情的合理信息,以便他能够自行解决(如果他有能力的话),以便他可以继续使用和消费你的申请。

政策可以变化,从零容忍(在任何可以抛出异常的地方抛出异常)到宽容(我们尽可能原谅调用者)。 通常的做法是DoAction(在你的情况下是AddComponent)严格而TryDoActionTryAddComponent)宽松。

零容忍版本:

// Strict: we are quite sure in the arguments; 
// that's why it's an exceptional case (error in the code!) if arguments are invalid
// and we fail to add 
public void AddComponent(IEntityComponent component) {
  // Contract: here we validate the input arguments
  // Since we sure in them we apply zero tolerance policy:
  // if contract is't met throw corresponding exception

  // we want not null component  
  if (component == null)
    throw new ArgumentException(nameof(component));

  // which contains at least one item of the required type
  if (ContainsComponentOfType(component.GetType()))
    throw new ArgumentException("Component must contain...",  nameof(component)); 

  // Finally, we know we can add the component to the entity
  components.Add(component);
}

宽松实施

// Lenient: we have arguments from the (unknown) source in which we are not sure
// we want just to try adding (and get true / false) if we succeed or not
public bool TryAddComponent(IEntityComponent component) {
  // Contract: we validate the input arguments from unknown source
  // if validation fails we should not throw any exception (it's not an
  // exceptional case to get incorrect data from unreliable source) but
  // let the caller know that we don't succeed

  // We can't add if component is null
  if (component == null)
    return false;

  // We have nothing to add if component doesn't contain required items 
  if (ContainsComponentOfType(component.GetType()))
    return false;

  // Finally, we know we can add the component to the entity
  components.Add(component);

  return true;
}

// Do we really want to expose it?
// ContainsComponentOfType is an implementation detail which we keep private
private bool ContainsComponentOfType(Type componentType) {
  // Since it's a private method we can omit the check
  return components.Any(c => c.GetType() == componentType);
}

用法:因为你正在构建一个引擎两种情况(严格和宽松)都可能出现

MyClass loader = new MyClass();

...

// Cloud Database must not be corrupted; if it is, we want to know it immediatly
loader.AddComponent(componentFromCloud);

...

// Local file can contain any data: it can be corrupted, user can try cheating etc.
if (!loader.TryAddComponent(componentFromLocalFile)) {
  // Let user know that saved data failed to be loaded 
}