每个开发人员都知道单元测试提高了代码的质量。我们还从静态代码分析中获益,并在我们的构建管道中使用SonarQube等工具。然而,我仍然发现许多开发人员并不知道检查代码有效性的一种更古老的方法:断言。在这篇文章中,我将向您介绍使用断言的好处,以及.NET应用程序的一些配置技巧。我们还将学习.NET和Windows如何支持它们。
什么是断言,什么时候使用它们
断言声明某个谓词(真-假表达式)在程序中的特定时间必须为真。当断言的计算结果为false时,会发生断言失败,这通常会导致程序崩溃。我们通常在调试版本中使用断言,并在调试器或某些特殊日志中处理断言异常(稍后我们将重点讨论配置)。在.NET中,有两种使用断言的方法:Debug.Assert or Trace.Assert.第一个方法的定义如下:
[System.Diagnostics.Conditional("DEBUG")] public static void Assert(bool condition, string message) { TraceInternal.Assert(condition, message); }
如您所见,只有在定义调试编译符号(通常仅用于调试生成)时,它才会出现在生成的IL中。另一方面,Assert使用跟踪编译符号,默认情况下,编译器不会在发布版本中剥离它。我更喜欢使用Debug.Assert方法,并且在推送到生产环境的二进制文件中没有断言。
让我们来看看我们可能使用断言的一些场景。我将使用corecrl存储库中的代码片段。
验证内部方法参数
断言是执行内部/私有方法参数验证的极好方法。我们应该将它们放在方法的开头,这样任何计划使用我们方法的人都会立即看到它的期望值,例如:
private static char GetHexValue(int i) { Debug.Assert(i >= 0 && i < 16, "i is out of range."); if (i < 10) { return (char)(i + '0'); } return (char)(i - 10 + 'A'); }
或者
internal static int MakeHRFromErrorCode(int errorCode) { Debug.Assert((0xFFFF0000 & errorCode) == 0, "This is an HRESULT, not an error code!"); return unchecked(((int)0x80070000) | errorCode); }
通常真假表达式就足够了,但是对于更复杂的场景,我们可以考虑使用Debug.Assert(bool condition,string message)变量(如上面的示例所示),在这里我们可以解释我们的需求。
我们不能使用断言来验证公共API方法参数。首先,断言将在发布版本中消失。其次,我们的API客户机期望某些特定类型的异常。如果仍要在公共API方法中使用断言,则应同时使用异常和断言来验证参数,例如:
public User FindUser(string login) { if (string.IsNullOrEmpty(login)) { Debug.Assert(false, "Login must not be null or empty"); // or equivalent: Debug.Fail("Login must not be null or empty"); throw new ArgumentException("Login must not be null or empty."); } }
验证正在执行上下文
要查看使用断言进行逻辑验证的示例,我们将分析StringBuilder类的Length属性和AssertInvariants方法。注意,断言(突出显示)如何在方法执行的各个阶段验证上下文。它们反映了编写代码的开发人员的假设,同时帮助我们更好地理解代码的逻辑:
/// <summary> /// Gets or sets the length of this builder. /// </summary> public int Length { get { return m_ChunkOffset + m_ChunkLength; } set { //If the new length is less than 0 or greater than our Maximum capacity, bail. if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NegativeLength); } if (value > MaxCapacity) { throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_SmallCapacity); } int originalCapacity = Capacity; if (value == 0 && m_ChunkPrevious == null) { m_ChunkLength = 0; m_ChunkOffset = 0; Debug.Assert(Capacity >= originalCapacity); return; } int delta = value - Length; if (delta > 0) { // Pad ourselves with null characters. Append('