• Guard Clauses设计模式-简化复杂性最有效的手段
  • 发布于 1周前
  • 49 热度
    0 评论
1. Guard Clauses
当业务增长导致代码变得臃肿且复杂时,函数作为最小的可执行单元,其易读性变得尤为重要。函数的清晰性可以通过良好的命名和结构来实现,而Guard Clauses设计模式则是简化复杂性的一种有效手段。通过使用Guard Clauses,我们可以在函数开始处进行预判并立即返回或抛出异常,以确保函数仅在有效条件下执行,避免深度嵌套的if和switch语句。进一步的优化是通过反转流程,即编写主逻辑而将错误情况作为else语句处理,使得代码更加简洁、清晰,减少了深层嵌套和沉长的可能性。

如下是一个简单的函数没有使用Guard Clauses
public void Subscribe(User user, Subscription subscription, Term term)
{
    if (user != null)
    {
        if (subscription != null)
        {
            if (term == Term.Annually)
            {
                // subscribe annually
            }
            else if (term == Term.Monthly)
            {
                // subscribe monthly
            }
            else
            {
                throw new InvalidEnumArgumentException(nameof(term));
            }
        }
        else
        {
            throw new ArgumentNullException(nameof(subscription));
        }
    }
    else
    {
        throw new ArgumentNullException(nameof(user));
    }
}
我们可以反转一下if语句中的逻辑并将抛出异常的语句放到if中,从而来消除else语句
public void Subscribe2(User user, Subscription subscription, Term term)
{
    if (user == null)
    {
        throw new ArgumentNullException(nameof(user));
    }
    if (subscription == null)
    {
        throw new ArgumentNullException(nameof(subscription));
    }
    if (term == Term.Annually)
    {
        // subscribe annually
    }
    else if (term == Term.Monthly)
    {
        // subscribe monthly
    }
    else
    {
        throw new InvalidEnumArgumentException(nameof(term));
    }
}
对null和抛出特定类型异常的常见行为的检查显然违反了DRY原则。可以将此代码提取到一个Helper方法中:
public static class Guard
{
    public static void AgainstNull(object argument, string argumentName)
    {
        if (argument == null)
        {
            throw new ArgumentNullException(argumentName);
        }
    }
    public static void AgainstInvalidTerms(Term term, string argumentName)
    {
        // note: currently there are only two enum options
        if (term != Term.Annually &&
            term != Term.Monthly)
        {
            throw new InvalidEnumArgumentException(argumentName);
        }
    }
}
现在可以调用这些帮助方法,设置不需要在函数中包含任何if语句,因为如果发生异常,它将从原始函数退出。现在你的代码将变成如下:
public void Subscribe3(User user, Subscription subscription, Term term)
{
    Guard.AgainstNull(user, nameof(user));
    Guard.AgainstNull(subscription, nameof(subscription));
    Guard.AgainstInvalidTerms(term, nameof(term));
    if (term == Term.Annually)
    {
        // subscribe annually
        return;
    }
    // subscribe monthly
}
随着项目的不断演进,您可以继续添加额外的辅助方法覆盖别的应用场景,例如空字符串、负数、无效枚举值等。

二. Ardalis.GuardClauses 
Ardalis.GuardClauses是一个第三方的包,用来实现扩展Guard Clauses
2.1 支持如下Guard Clauses
Guard.Against.Null
描述:如果输入为空抛出异常

Guard.Against.NullOrEmpty
描述:输入字符串,guid或者array为null或者empty抛出异常

Guard.Against.NullOrWhiteSpace
描述:输入的字符串为null,empty或者whitespace抛出异常

Guard.Against.OutOfRange
描述:如果 integer/DateTime/enum 超出提供的范围抛出异常

Guard.Against.EnumOutOfRange
描述:如果提供的枚举值超出了定义枚举值的范围则抛出异常

Guard.Against.OutOfSQLDateRange
描述:输入的日期类型超出SQL Server Datetime 范围则抛出异常

Guard.Against.Zero
描述:输入数字为0则抛出异常

Guard.Against.Expression
描述:自定义表达式

Guard.Against.InvalidFormat
描述:使用正则表达式或者函数自定义格式

10.Guard.Against.NotFound 
   描述:使用id/key查找,如果没有发现对象,抛出异常

2.2 拓展自己的Guard Clauses
你可以通过如下代码来扩展你自己的Guard:
// Using the same namespace will make sure your code picks up your 
// extensions no matter where they are in your codebase.
namespace Ardalis.GuardClauses
{
    public static class FooGuard
    {
        public static void Foo(this IGuardClause guardClause,
            string input, 
            [CallerArgumentExpression("input")] string? parameterName = null)
        {
            if (input?.ToLower() == "foo")
                throw new ArgumentException("Should not have been foo!", parameterName);
        }
    }
}
// Usage
public void SomeMethod(string something)
{
    Guard.Against.Foo(something);
    Guard.Against.Foo(something, nameof(something)); // optional - provide parameter name
}

用户评论