[C#] 對Function之參數進行驗證

  • 21862
  • 0

[C#] 對Function之參數進行驗證


前言


我們在寫程式時,為了避免程式執行時發生預期之外的錯誤,

通常都會對function的參數進行驗證。

(例如為了避免 DivideByZeroException,我們會檢查除數是否為 0)


public double Divide(double a,double b)
{
        if (b == 0)
            throw new ArgumentException("b can't be zero!");

        return a / b;
}

當我們所需要對參數驗證的規則比較少時還好,

但若我們需要對參數進行很多項規則的檢驗時,

這種方式就會讓程式碼顯得十分雜亂而且不容易管理。

(例如註冊時檢查Name、Email、Age)


public void Register(string name,string email,string,int age)
{
    if (string.IsNullOrEmpty(name))
        throw new ArgumentNullException();
    if (string.IsNullOrEmpty(email))
        throw new ArgumentNullException();            
    if(!Regex.IsMatch(email,@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$"))
        throw new ArgumentException();                      
    if(age < 18 )
        throw new ArgumentOutOfRangeException();

    ...
}

 

實際演練


如果我們能夠把上面檢查參數的邏輯,簡化成如下圖,

並且將各種檢查的方法封裝成一個個的Function,不但能夠方便Reuse,

而且是不是看起來就乾淨許多了呢?


public void Register(string name, string email, int age)
{
    ValidateHelper.Begin()
                  .NotNull(name)
                  .NotNull(email)
                  .InRange(age, 18, 120)
                  .Check(() => Regex.IsMatch(email, @"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$"));
}

首先,我們需要一個ValidateHelper的Class來當作起點,

並創建一個Validation的Class來當作我們驗證的媒介,使驗證語法可以Chain的方法呈現。


public static class ValidateHelper
{
    public static Validation Begin()
    {
        return null;
    }
}

public sealed class Validation
{
    public bool IsValid { get; set; }
}

接下來我們透過擴充方法,來為Validation增加一個個驗證的邏輯,

不過在建立之前,我們會建立一個通用的邏輯來當作我們擴充的起點。


public static class ValidationExtensions
{
    private static Validation Check<T>(this Validation validation, Func<bool> filterMethod, T exception) where T : Exception
    {
        if (filterMethod())
        {
            return validation ?? new Validation() { IsValid = true };
        }
        else
        {
            throw exception;
        }
    }

    public static Validation Check(this Validation validation, Func<bool> filterMethod)
    {
        return Check<Exception>(validation, filterMethod, new Exception("Parameter InValid!"));
    }
}

有了這個方法之後,對Email進行驗證就可以改寫如下


ValidateHelper.Begin().Check(() => Regex.IsMatch(email, @"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$"));

接下來,我們就可以根據這些,來對其他常用的驗證邏輯來進行擴充了!

(例如檢查是否為Null,以及數字是否在否個區間之中)


public static Validation NotNull(this Validation validation, Object obj)
{
    return Check<ArgumentNullException>(
        validation,
        () => obj != null,
        new ArgumentNullException(string.Format("Parameter {0} can't be null", obj))
    );
}

public static Validation InRange(this Validation validation, double obj, double min, double max)
{
    return Check<ArgumentOutOfRangeException>(
        validation,
        () =>
        {
            double input = double.Parse(obj.ToString());
            if (obj >= min && obj <= max)                    
                return true;                    
            else
                return false;
        },
        new ArgumentOutOfRangeException(string.Format("Parameter should be between {0} and {1}", min, max))
    );
}

或許你會說,那Email的驗證邏輯是不是也可以封裝起來呢? 當然是可以的


public static Validation RegexMatch(this Validation validation, string input, string pattern)
{
    return Check<ArgumentException>(
        validation,
        () => Regex.IsMatch(input, pattern),
        new ArgumentException(string.Format("Parameter should match format {0}", pattern))
    );
}

public static Validation IsEmail(this Validation validation, string email)
{
    return RegexMatch(validation, email, @"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$");
}

所以最後,我們在function中,對參數進行驗證的部份就可以改寫如下


public void Register(string name, string email, int age)
{
    ValidateHelper.Begin()
                  .NotNull(name)
                  .NotNull(email)
                  .InRange(age, 18, 120)
                  .IsEmail(email);
}

 

結語


除了應用在function的參數驗證之外,

這個方法也可以應用在Model的驗證上,

將驗證的邏輯封裝起來後,不但可以Reuse,還可以讓程式碼更容易閱讀。

如果有任何意見,歡迎大家多多分享指教喔 ^_^

 

參考文章: