這幾天,接手一個同事的程式碼,關於微信介面開發的,那一堆的 if,看得哥蛋痛了,這個毛病也是很多新手容易犯的,所以特地把這次重構寫出來。
下面來我們看看這個程式碼的問題所在,if else 裡面的程式碼塊邏輯,不好改,使得它的重用性為 0,並且難以閱讀。當然,如果 if 只有一兩個,或者3個,這樣寫是問題不大的。
但是如果多了,這種程式碼便會讓維護變得困難起來。
if (strMsgType == "text") { textContentClient = rootElement.SelectSingleNode("Content").InnerText; strResult = SetMsgType_Text(strClientName, textContentClient, db, strServerName, Identity); System.Diagnostics.Trace.WriteLine(strResult); return Content(strResult); } else if (strMsgType == "event") { string eventType = rootElement.SelectSingleNode("Event").InnerText.ToLower(); if (eventType == "subscribe") { string keyCode = ""; if (rootElement.SelectSingleNode("EventKey") != null) keyCode = rootElement.SelectSingleNode("EventKey").InnerText.ToLower(); strResult = FormatEventSubscribe(keyCode); RecordReplyMessage(); return Content(strResult); } else if (eventType == "scan") { string keyCode = rootElement.SelectSingleNode("EventKey").InnerText.ToLower(); var outLetName = "歡迎關注"; var outletDB = ShoppingContext.CreateInstance(Identity); var outLetModel = outletDB.Outlets.FirstOrDefault(o => o.SceneId == Int32.Parse(keyCode)); if (outLetModel != null) outLetName += outLetModel.Name; return Content(GetTextTemp(strClientName, strServerName, outLetName)); } else if (eventType == "click") { string keyCode = rootElement.SelectSingleNode("EventKey").InnerText.ToLower(); strResult = FomatMenuMessage(keyCode); return Content(strResult); } else if (eventType == "unsubscribe") { var subIds = db.ReplyRecords.Where(r => r.FromOpenId == this.ClientId.ToString() && r.EMessType == EEventType.Subscribe.ToString() && r.KeyWord != null).Select(o => o.KeyWord).ToArray(); var unSubIds = db.ReplyRecords.Where(r => r.FromOpenId == this.ClientId.ToString() && r.EMessType == EEventType.Unsubscribe.ToString() && r.KeyWord != null).Select(o => o.KeyWord).ToArray(); var SencesId = ""; foreach (var k in subIds) { if (!unSubIds.Contains(k)) { this.ReplyModel.KeyWord = k; break; } } this.ReplyModel.EMessType = EEventType.Unsubscribe.ToString(); RecordReplyMessage(); } } else if (strMsgType.ToLower() == "location") { string strLocation_X = rootElement.SelectSingleNode("Location_X").InnerText; string strLocation_Y = rootElement.SelectSingleNode("Location_Y").InnerText; strResult = FormatOutLetLBS(double.Parse(strLocation_X), double.Parse(strLocation_Y), 10); //strResult = FormatTextMessage(strLocation_X + "|" + strLocation_Y); return Content(strResult); } else if (strMsgType.ToLower() == "image") { string strImgUrl = rootElement.SelectSingleNode("PicUrl").InnerText; }
一種比較好的處理方法就是把語句塊內的程式碼抽出來,寫成函式,如下面所示:
public class MessageProcesser { public ReplyMessage Process(string xml) { var msg = PostMessage.FromXml(xml); switch (msg.MsgType) { case Models.PostMessageType.Event: var eventType = ((EventMessage)msg).Event; switch (eventType) { case EventType.Click: return ProcessClickEvent((ClickEvent)msg); case EventType.Location: return ProcessLocationEvent((LocationEvent)msg); case EventType.Scan: return ProcessScanEvent((ScanEvent)msg); case EventType.Subscribe: return ProcessSubscribeEvent((SubscribeEvent)msg); case EventType.Unsubscribe: return ProcessUnsubscribeEvent((UnsubscribeEvent)msg); } break; case Models.PostMessageType.Image: return ProcessImageMessage((ImageMessage)msg); case Models.PostMessageType.Link: return ProcessLinkMessage((LinkMessage)msg); case Models.PostMessageType.Location: return ProcessLocationMessage((LocationMessage)msg); case Models.PostMessageType.Text: return ProcessTextMessage((TextMessage)msg); case Models.PostMessageType.Video: return ProcessVideoMessage((VideoMessage)msg); case Models.PostMessageType.Voice: return ProcessVoiceMessage((VoiceMessage)msg); } return null; } protected virtual ReplyMessage ProcessClickEvent(ClickEvent msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessLocationEvent(LocationEvent msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessScanEvent(ScanEvent msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessSubscribeEvent(SubscribeEvent msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessUnsubscribeEvent(UnsubscribeEvent msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessImageMessage(ImageMessage msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessLinkMessage(LinkMessage msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessLocationMessage(LocationMessage msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessTextMessage(TextMessage msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessVideoMessage(VideoMessage msg) { return DefaultProcess(msg); } protected virtual ReplyMessage ProcessVoiceMessage(VoiceMessage msg) { return DefaultProcess(msg); } protected virtual ReplyMessage DefaultProcess(PostMessage msg) { var reply = new TextReply(msg); if (msg.MsgType == PostMessageType.Event) { reply.Content = string.Format("{0} event is not processed.", ((EventMessage)msg).Event); } else { reply.Content = string.Format("{0} message is not processed.", msg.MsgType); } return reply; } }
在使用的時候,繼承上面的類就行了。
public class MyMessageProcesser : WeiXin.MessageProcesser { public MyMessageProcesser() { } protected override ReplyMessage ProcessSubscribeEvent(SubscribeEvent msg) { var reply = new TextReply(msg); reply.Content = "你好,歡迎關注"; return reply; } protected override ReplyMessage ProcessUnsubscribeEvent(UnsubscribeEvent msg) { var reply = new TextReply(msg); reply.Content = "取消關注"; return reply; } }
歡迎討論,歡迎板磚。
=======================================================
有朋友說,我只是把 if 換成了 switch,這個沒錯,但更重要的是,換成了一個可重寫方法的類。
有朋友說,使用命令模式會不會更好點。是這樣的,因為是微信的介面,以後就算是增加,也是很少會發生的,並且要作的改動也不多。所以不想變得太複雜了。現在是 if 內的語句塊需要變動,因為要面對不同的使用者,他們的處理都是不同的。
有不少朋友都誤會了,以為我是為了消除 if,else,在這裡,if、else 帶來的問題只是閱讀讀上的不便,真正要害的的地方是if、else間的邏輯程式碼塊,這些程式碼會因為不同的客戶,做出不同的處理,每換一個客戶,都泛及到修改裡面的程式碼,所以很有必要對它進行重構。(很多朋友都抓我 switch case 的小辨子,因為特地強調一下這裡)
為什麼不用命令模式、或者把型別與處理方法儲存在鍵值對。我們先來考慮一個問題,這些型別有沒有增加的可能性?有,但是這個慨率比較小的,就算髮生了,修改的正本也是非常低的,而且,當然你增加了一個後,以後需要增加一個的慨率更加小了。這些型別的數量,不會大大地增加的。
總結一下這種處理的好處:對於開發人員來說,它非常便於閱讀和理解,而對於使用者來說,通過過載來實現,也是很容易接受的。
認同者寡,而反對者眾,我是不是可以這麼認為,能把程式碼寫好的人,還是少數的。
你們如果覺得我太懂得自我安慰了,請給我一板磚,把我給拍醒吧。^_^
看這位朋友這麼熱心給我留言,我就花點時間來回答一下這個問題:
========================================================
提出以下疑問
首先:
var msg = PostMessage.FromXml(xml);
這個方法其實已經將msg例項成一個具體的型別了
下面的判斷為什麼不是針對
msg is Type 而是判斷字串或列舉?
後面的全是強轉,不怕報錯嗎?
然後:
既然第一句就已經區分了msg的型別
為什麼不可以直接
var msg = PostMessage.FromXml(xml);
msg.Process();
還需要如此多的判斷?
----------------------------------------------------------------------------
回答:
首先,你得了解我的背景,也就是這個程式碼所應用的情景,面對不同的客戶,處理的邏輯都是不同的。
var msg = PostMessage.FromXml(xml);
msg.Process();
能夠做到,不同的客戶,不同的實現嗎?
如果強轉換會出錯,那肯定是開發人員的錯。^_^ 難道你寫完程式碼了,不檢查,也不測試的嗎?
================================================
疑問:
大家如果對我做的東西感興趣,可以和我聯絡:
QQ: 81932759
Q群一: 71418067
Q群二: 88718955
上海的朋友,可以掃一掃下這面這個公眾號,建這個公眾號的目的,希望能夠通過組織一些線下活動,讓更多的程式設計師相互認識。
protected virtual ReplyMessage ProcessScanEvent(Message msg)
這樣可以把判斷型別(或者說轉換型別)的工作交給具體實現去完成
可以避免在公共方法中的越權控制(你不能確定實現類是否可以處理一種新型別)