异常处理
我不会担心这样的代码中的异常“有多大”。稍后我会解释原因。
例外是针对特殊情况。拥有异常处理程序意味着您希望这些属性永远不会为空。
何时编写保护条件
这些属性通常是null吗?
如果是这样,这不是一个例外情况。您应该编写适当的空测试,并将您的异常处理程序代码更改为空条件代码。
这些属性为null 是否不常见,也就是说,您是否可以合理地期望它们永远不会是null?
如果是这样,您可以简单地避免编写 null 检查,而让底层代码抛出。但是,您不会获得很多关于哪个属性引发异常的上下文。
您也可以进行null 检查并抛出更具体的上下文异常。
何时编写异常处理程序
如果这些属性不常见为null,那么这是一种例外情况。但这并不意味着你必须有一个处理程序。
您是否有一种简单的方法可以在异常情况发生之前对其进行测试?
如果是这样,那么您应该在允许您使用的底层代码引发异常之前对此进行测试。由于您只需检查null,我会说这很容易。
你有合理的逻辑来处理这个级别的这个案例吗?
如果你有一个合理的方法来处理那个级别的异常情况,并且仍然保证你的方法正确执行,那么继续添加处理程序代码。如果您依赖于返回null 之类的机制,那么请确保从消费者的角度来看,他们不会总是得到结果是有道理的。例如。将方法命名为 FindInterestingString 而不是 GetInterestingString。
如果您没有合理的方法来处理这种情况,请不要将异常处理程序置于该级别。让您的异常冒泡并在代码的较高位置处理它。
如果你根本没有合理的方法来处理异常,那就让程序崩溃吧。这总是比吞下异常并继续好。这隐藏了错误。
这些规则的例外情况
有时您无法在不引发异常的情况下轻松测试条件。
文件系统等外部依赖项将在您的程序下发生变化。即使您进行了预测试,并且即使预测试通过了,一旦您尝试使用该对象,也可能会引发异常。在这种情况下,你无能为力,必须依赖异常处理。
电子邮件地址和 URI 等复杂验证可能需要您使用引发异常的构造。再说一次,它可能不会。您应该始终寻找最合适的方法来进行与您的意图相匹配的错误处理。仅在必要时才使用异常处理。
性能
性能不太可能成为错误检测代码的问题。
在高使用率的代码(当您编写框架时)、应用程序的瓶颈以及众所周知的 CPU/内存密集型算法中,性能很重要。您应该了解何时需要担心性能,但它应该始终是代码的可读性、可维护性和正确性的次要问题。
您会发现不可能完美地预测整个应用程序中的性能问题。获得准确图片的唯一方法是在真实条件下使用真实场景运行代码并对其进行分析。在您开发应用程序达到这一点之前,您不应该担心性能,除非您知道这将是一个问题。
使用异常的性能成本并不像许多人认为的那样高。在 .Net 中,它们被设计为在不引发异常时表现得非常好。这与例外是针对例外情况的事实相吻合。
您的代码示例
您提供的代码示例存在一些其他问题。希望我能在你被他们困住之前指出其中的一些。如果没有,希望您在遇到问题时可以回顾这些以获得指导。
编写异常处理程序
您为异常处理程序编写的代码根本不可接受。以下是编写更好的异常处理程序代码的一些指导:
不好:
try
{
}
catch // Note: Doesn't catch `Exception e`
{
// ... eats the exeption
}
这是一种不好的形式,永远不应该使用。您绝对无法正确处理所有异常类型。最常用的例子是OutOfMemoryException。
可能接受:
try
{
}
catch(Exception e)
{
logger.Log(e.ToString());
// ... eats the exeption
}
如果您捕获异常并将其记录或显示,则可以吃掉异常。只有在您积极监控/报告这些异常并且有办法保证这些异常会被诊断出来时,这才可以。
好的:
try
{
}
catch(Exception e)
{
logger.Log(e.ToString()); // Make sure your logger never throws...
throw; // Note: *not* `throw e;`
}
// Or:
try
{
}
catch
{
// Todo: Do something here, but be very careful...
throw;
}
如果你非常小心不要创建新的异常,并且如果你重新抛出异常,你可以在异常处理程序中做任何你想做的事情。这将保证错误被注意到。如果重新抛出异常,请确保使用throw; 而不是throw e;,否则您的原始堆栈跟踪将被破坏。
好:
try
{
}
catch(NullReferenceException e)
{
// ... Do whatever you want here ...
}
这是安全的,因为您只捕获已知由try 块中的代码抛出的某些异常类型。很容易理解代码的意图,也很容易进行代码审查。异常处理代码好不好,很容易理解。
避免重复代码
在可以避免的情况下,切勿重新访问属性。而不是像这样编写访问您的属性的代码:
rootObject ...
rootObject.FirstProperty ...
rootObject.FirstProperty.SecondProperty ...
rootObject.FirstProperty.SecondProperty.InterestingString ...
...只调用一次 getter:
var firstProperty = rootObject.FirstProperty;
var secondProperty = firstProperty.SecondProperty;
var interestingString = secondProperty.InterestingString;
您的代码示例看起来更像这样:
if (rootObject != null)
{
var firstProperty = rootObject.FirstProperty;
if (firstProperty != null)
{
var secondProperty = firstProperty.SecondProperty;
if (secondProperty != null)
{
var interestingString = secondProperty.InterestingString;
if (!string.IsNullOrEmpty(interestingString))
{
result = interestingString;
}
}
}
}
这样做的一个原因是 getter 可能具有复杂的逻辑,并且多次调用它可能会导致性能影响。
另一个原因是你avoid repeating yourself。没有太多重复的代码总是更易读。
当您重复自己时,可维护性也会受到影响。如果您更改其中一个属性的名称,您将不得不更改它所在的每一行代码,这将导致更难推断更改的影响。
避免深入挖掘依赖层次结构
您应该避免在同一方法中进行链式属性访问。即:
rootObject.FirstProperty.SecondProperty.InterestingString
即使您已将其拆分以避免重复自己(如我上面建议的那样),您仍然可能没有正确考虑您的代码。您的代码仍然与该数据结构的层次结构紧密耦合。每当您更改该层次结构时,都需要更改遍历该层次结构的任何代码。如果这就是你的所有代码,那么你的状态很糟糕。
为避免这种情况,请将了解每个级别的代码与其下面的级别分开。
处理根对象的代码应该只调用处理根直接下面的对象的代码。处理FirstProperty 的代码应该只知道SecondProperty 级别(在FirstProperty 下)的属性。唯一应该了解InterestingString 的代码是SecondProperty 返回的对象类型的处理程序代码。
一个简单的方法是将遍历代码拆分,并将其移动到对象本身中。
见:
拆分逻辑的示例代码:
public class SomeClassUsingRoot
{
public string FindInterestingString()
{
return root != null
? root.FindInterestingString()
: null;
}
private RootSomething root;
}
public class RootSomething
{
public string FindInterestingString()
{
return FirstProperty != null
? FirstProperty.FindInterestingString()
: null;
}
public SomethingTopLevel FirstProperty { get; set; }
}
public class SomethingTopLevel
{
public string FindInterestingString()
{
return SecondProperty != null
? SecondProperty.InterestingString
: null;
}
public SomethingLowerLevel SecondProperty { get; set; }
}
public class SomethingLowerLevel
{
public string InterestingString { get; set; }
}
这不是解决问题的唯一方法。关键是将处理每个级别的逻辑拆分为单独的方法,或者(甚至更好)单独的对象。这样,当层次结构发生变化时,您的影响就会更小。