減少程式中if,foreach疊加造成閱讀不易情形
最近在維護程式,常常看到不斷在使用if及foreach疊加,造成整體程式像在爬山一樣,不知道何時才會到頂點
我會建議程式一個方法method中有if及foreach疊加不要超過三層,這裡提供幾個方法簡單減少狀況:
1.利用return
2.另外包成method
3.使用linQ
範例1
protected void Page_Load(object sender, EventArgs e)
{
string alertMsg = "";
string errorMsg = "";
if (Request.Form["email"] != null)
{
string email = checkValue(Request.Form["email"], ref errorMsg);
if (errorMsg == "")
{
List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
if (messageData.Count > 0)
{
alertMsg = messageData.Error;
}
else
{
var parameters = new
{
Mail = email,
};
using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
{
if (memberData != null && errorMsg == "")
{
if (memberData.Count > 0)
{
//寄信
String mailResult = SendMail(...);
if (mailResult == "OK")
{
alertMsg = "通信寄出";
}
}
else
{
alertMsg = "查無資料";
}
}
else
{
alertMsg = "查無資料";
}
}
}
}
else
{
alertMsg = errorMsg;
}
}
}
private string checkValue(string inputValue, ref string errorMsg)
{
errorMsg = "";
if (string.IsNullOrEmpty(inputValue))
{
errorMsg = "空白或null";
return "";
}
return inputValue;
}
public List<T> GetDataFromSQLParam<T>(string msql, object parameters, ref string mmsg)
{
List<T> results = null;
using (SqlConnection connection = new SqlConnection(connectionString))
{
mmsg = "";
try
{
var ts = connection.Query<T>(msql, parameters);
results = ts.ToList();
}
catch (Exception exception)
{
mmsg = "DB error:" + exception.Message;
}
}
return results;
}
當我們在思考流程撰寫程式時,上述的範例是大腦最直覺的寫法,這樣並沒有錯,只是後續在維護時,在閱讀第幾層if時會非常不易。
先看第一層if,這邊可利用return改寫成
if (Request.Form["email"] == null)
{
return;
}
string email = checkValue(Request.Form["email"], ref errorMsg);
if (errorMsg == "")
...
並將原本包在第一層if裡面程式往外搬移,同理將剩下的相同的邏輯用這樣方式處理,可以得到結果為
if (Request.Form["email"] == null)
{
return;
}
string email = checkValue(Request.Form["email"], ref errorMsg);
if (errorMsg != "")
{
alertMsg = errorMsg;
return;
}
List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
if (messageData.Count > 0)
{
alertMsg = messageData.Error;
return;
}
var parameters = new
{
Mail = email,
};
using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
{
if (memberData != null && errorMsg == "")
{
if (memberData.Count > 0)
{
//寄信
String mailResult = SendMail(...);
if (mailResult == "OK")
{
alertMsg = "通知信寄出";
}
}
else
{
alertMsg = "查無資料";
}
}
else
{
alertMsg = "查無資料";
}
}
此時會碰到一個使用using的狀況,這邊就會建議將using底下另外包成method
protected void Page_Load(object sender, EventArgs e)
{
string alertMsg = "";
string errorMsg = "";
if (Request.Form["email"] == null)
{
return;
}
string email = checkValue(Request.Form["email"], ref errorMsg);
if (errorMsg != "")
{
alertMsg = errorMsg;
return;
}
List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
if (messageData.Count > 0)
{
alertMsg = messageData.Error;
return;
}
var parameters = new
{
Mail = email,
};
using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
{
alertMsg = ProccessDataView(memberData, errorMsg);
}
}
private string ProccessDataView(List<MemberData> memberData, string errorMsg)
{
string returnAlertMsg = "";
if (memberData != null && errorMsg == "")
{
if (memberData.Count > 0)
{
//寄信
String mailResult = SendMail(...);
if (mailResult == "OK")
{
returnAlertMsg = "通知信寄出";
}
}
else
{
returnAlertMsg = "查無資料";
}
}
else
{
returnAlertMsg = "查無資料";
}
return returnAlertMsg;
}
接下來,繼續使用return方式處理ProccessDataView方法裡的參數,這邊有一個小細節可以處理,if判斷式中是有順序的由左至右讀取的,也就是說從左邊第一個判斷式可以得知true跟false時,就不會再讀剩下的判斷式
protected void Page_Load(object sender, EventArgs e)
{
string alertMsg = "";
string errorMsg = "";
if (Request.Form["email"] == null)
{
return;
}
string email = checkValue(Request.Form["email"], ref errorMsg);
if (errorMsg != "")
{
alertMsg = errorMsg;
return;
}
List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
if (messageData.Count > 0)
{
alertMsg = messageData.Error;
return;
}
var parameters = new
{
Mail = email,
};
using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
{
alertMsg = ProccessDataView(memberData, errorMsg);
}
}
private string ProccessDataView(List<MemberData> memberData, string errorMsg)
{
string returnAlertMsg = "";
if (memberData == null || errorMsg != "" || memberData.Count == 0)
{
returnAlertMsg = "查無資料";
return returnAlertMsg;
}
//寄信
String mailResult = SendMail(...);
if (mailResult == "OK")
{
returnAlertMsg = "通知信寄出";
}
return returnAlertMsg;
}
這邊處理完後,你就會發現,萬一寄信失敗是不會有任何回傳的,但你從最開始的程式很難判斷有缺少錯誤回傳
範例2
protected string LayoutTypeSot()
{
IDictionary<string, string> list = new Dictionary<string, string>();
list.Add("A", "Apple");
list.Add("B", "banana");
list.Add("C", "Cherry");
list.Add("D", "Date");
list.Add("E", "");
list.Add("", "");
string layoutOther = "";
string layoutA = "";
foreach (var item in list)
{
if (item.Value != "")
{
if (item.Key != "A")
{
layoutOther += layoutStringOther(item.Key, item.Value);
}
else
{
layoutA += layoutStringA(item.Key, item.Value);
}
}
}
return layoutA + layoutOther;
}
private string layoutStringOther(string key, string name)
{
return string.Format("{0} 不是A開頭水果:{1} \r\n", key, name);
}
private string layoutStringA(string key, string name)
{
return string.Format("{0} 開頭水果:{1} \r\n", key, name);
}
回傳結果:
A 開頭水果:Apple
B 不是A開頭水果:banana
C 不是A開頭水果:Cherry
D 不是A開頭水果:Date
第二個範例是很常見到的情形,要在一串資料中區分不同種類並排除空值狀況,在這個情況下,我會建議使用linQ來做簡化的程式的動作,我們第一步也是處理第一個判斷式。
var filterNullEmptys = list.Where(x => !string.IsNullOrEmpty(x.Key) && !string.IsNullOrEmpty(x.Value));
string layoutOther = "";
string layoutA = "";
foreach (var item in filterNullEmptys)
{
if (item.Key != "A")
{
layoutOther += layoutStringOther(item.Key, item.Value);
}
else
{
layoutA += layoutStringA(item.Key, item.Value); ;
}
}
利用linq where 條件方式過濾掉掉所有空值或null狀況,接下我們可以再利用where方式區分成A及非A狀況
var filterNullEmptys = list.Where(x => !string.IsNullOrEmpty(x.Key) && !string.IsNullOrEmpty(x.Value));
var isADatas = filterNullEmptys.Where(x => x.Key == "A");
var otherDatas = filterNullEmptys.Where(x => x.Key != "A");
這時候我們發現,需要處理的資料被拆成兩個,所以需要兩個迴圈處理
var filterNullEmptys = list.Where(x => !string.IsNullOrEmpty(x.Key) && !string.IsNullOrEmpty(x.Value));
var isADatas = filterNullEmptys.Where(x => x.Key == "A");
var otherDatas = filterNullEmptys.Where(x => x.Key != "A");
string layoutOther = "";
string layoutA = "";
foreach (var itemA in isADatas)
{
layoutA += layoutStringA(itemA.Key, itemA.Value); ;
}
foreach (var item in otherDatas)
{
layoutOther += layoutStringOther(item.Key, item.Value);
}
從一開始的三層,最後處理完只剩一層,也省略掉使用if判斷式
上面兩個範例中if的else完全不見,在許多程式介紹書中,會強烈建議不要輕易地用else,除非你很確認你的else夠簡單
程式寫法有很多種,沒有絕對的對與錯,只是要依據專案而定,這邊在可允許下,將程式寫的方便易讀,最好的狀況就是一個月再回來看程式的時候,你還記得你寫了甚麼。