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