[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,還可以讓程式碼更容易閱讀。
如果有任何意見,歡迎大家多多分享指教喔 ^_^
參考文章:


