把三千行代碼重構爲15行c++
那年我剛畢業,進了如今這個公司。公司是搞數據中心環境監控的,裏面充斥着嵌入式、精密空調、總線、RFID的概念,我一個都不懂。還好,公司以前用Delphi寫的老客戶端由於太慢,而後就搞了個Webform的替代,剛好我對Asp.Net還算了解,我對業務的不瞭解並不妨礙我稱成爲這個公司的一個程序員。小公司也有小公司的好,人少,進去很快負責代碼開發。我固然也就搞這個數據中心智能管理系統啦。程序員
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。數據庫
這個系統很是的龐大,尤爲牛逼的是支持客戶端組態,而後動態生成網頁,數據還能經過Socket實時監控(那時我還真就不懂網絡編程)。這個對於當時的我來講,真真是高、大、上吶!!當時跟着瞭解整個系統大半個月纔算可以調試,寫一些簡單的頁面。編程
在維護系統的過程當中,時不時要擴展一些功能,也就接觸了下面這個類:設計模式
看到沒有,就是當年最最流行的三層架構的產物,對於剛出茅廬的毛頭小子來講,這是多麼專業的文件頭註釋,還有反射也就算了,這構造函數還能靜態的,還能私有的?那時剛接觸這麼高大上的代碼的我,瞬間給跪了!網絡
可是,類寫多了,我就感受愈來愈彆扭,就是下面這段代碼:架構
每增長一個表,除了要改接口、要改DAL、要改BLL以外,還得在這個工廠類添加一個方法,真真是累到手抽筋,即便有當時公司了的G工給我推薦的神器——動軟代碼生成器,這粘貼複製的幾遍,也是讓我感受到異常繁瑣,有時候打鍵盤稍微累了點,還把複製出來代碼改錯了,你妹的,難道這就是程序員該乾的事情,不,絕對不是!我想起了一句至理名言:當你以爲代碼重複出如今程序中的時候,就應該重構了。是的,在這句話的指導下,我開始了折騰,決定挑戰這個高大上的代碼,事實證實,思想的力量是無窮的。框架
那麼,怎麼修改呢,仔細觀察以後,發現其中className的生成跟返回的類型很是相似,只是一個是類名,一個是字符串,這二者之間應該可以關聯起來。因而google了一下(當時GFW還沒猖獗起來哈),隱隱約約就找到了「反射」這兩個字,深刻了解以後,肯定能夠完成。編程語言
接下來,就是返回的類型了,返回的類型並不固定,可是彷佛頗有規律……這個彷佛好像在哪裏見過,對了,模板,C++課程上有講過的,因而再次google,瞭解到了C#中使用了泛型代替了C++中的模板。在學習完泛型和反射以後,並參考了網上的一些文章,我搗鼓出了下面的代碼:分佈式
沒錯,就是它了,三層架構年代最流行的工廠類……
看着原來滾十幾屏幕的代碼,變成了十多行的代碼,真是爽到了骨子裏去了,太乾淨了!惟一讓我擔心的是,我進公司的時候,幫忙整理公司申請軟件著做權都是須要代碼量的,根據代碼多少行來評估軟件的大小,萬一老闆知道了我非但沒有幫公司增長代碼量,還減小了,會不會當即把我開掉?我沒敢給咱們老闆展現我優秀的成果,所幸,這段代碼非但沒有出過任何問題,還避免了之前同事總是在新增一個類以後,把代碼複製過來,可是沒有正確修改的問題,大大提升了效率。雖然,我沒敢大事宣佈個人勞動成果,可是此次成功的修改,則完全讓我走上了代碼重構的不歸路。
看到這裏,你們應該知道這個案例是否真實的了吧。我相信,從08年開始的碼農們,看到這種相似的代碼絕對不比我少。那麼,我想告訴大家的是什麼呢?
少用代碼生成器
咱們來分析一下,爲何我以前的前輩會寫出上面的代碼。我歸結起來有如下幾點:
至今爲止,仍是不少人使用代碼生成器,那麼咱們應該怎麼對待這個問題呢。我認爲,代碼生成器確實能夠減小你很多工做,可是少用,那些重複性的工做,除了部分確實是沒有辦法的,其餘大部分都是能夠經過框架解決的,舉例來講,像三層架構,真正須要用到代碼生成器的,也就是Model類而已,其餘的徹底能夠在框架中完成。所以你要不遺餘力的思考怎麼在框架中來減小你的重複性工做,而不是依賴於代碼生成器。
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
另外,若是你仍是在用相關的代碼生成工具,請從新定義「動軟代碼生成器」的代碼模板,本身寫一個模板;或者使用CodeSmith來徹底制定本身的代碼生成,由於動軟給的代碼模板真心亂,好比下面這段代碼:
for (int n = 0; n < rowsCount; n++) { model = new DBAccess.Model.eventweek(); if(dt.Rows[n]["GroupNo"].ToString()!="") { model.GroupNo=int.Parse(dt.Rows[n]["GroupNo"].ToString()); } if(dt.Rows[n]["Week0"].ToString()!="") { model.Week0=int.Parse(dt.Rows[n]["Week0"].ToString()); } if(dt.Rows[n]["Week1"].ToString()!="") { model.Week1=int.Parse(dt.Rows[n]["Week1"].ToString()); } }
首先,你就不能用 var row=dt.Rows[n] 替代嗎?其次,直接用int.Parse若是拋出了異常性能得有多低?再次,這段代碼要是有點修改,我不是要每一個dt.Rows[n]得改一遍?
不要重複發明輪子
咱們再來看看其餘的一些代碼:
public List<string> GetDevices(string dev){ List<string> devs=new List<string>(); int start=0; for(int i=0;i<dev.Length;i++){ if(dev[i]=='^'){ devs.Add(dev.SubString(start,i)); start=i+1; } } return devs; }
有沒有很眼熟,沒錯,這就是對String.Split()函數的簡單實現。個人前輩應該是從c++程序員轉過來的,習慣了各類功能本身實現一遍,可是他忽略了C#的不少東西。咱們不去評判這段代碼的優劣,而實際上他在很長一段時間都運行得很好。咱們來看看使用這一段代碼有什麼很差的地方:
那麼,咱們應該怎樣去避免重複發明輪子呢?我從我的的經從來提出如下幾點,但願可以對各位有所幫助:
這裏我再舉一個我本身的例子。在我現有的程序中,我發現我須要愈來愈多的線程來執行一些簡單的任務,好比在天天檢測一下硬盤是否達到90%了,天天9點要控制一下空調的開啓而在網上6點的時候把空調關掉。線程使用愈來愈多,我越是以爲浪費,由於這些現場僅僅只需完成一次或者有限的幾回,大部分時間都是沒有意義的,那麼怎麼辦呢?我決定本身寫一個任務類,來完成相關的事情。說幹就幹,我很快把這個類寫出來了。
public abstract class MissionBase : IMission { private DateTime _nextExecuteTime; protected virtual DateTime[] ExecuteTimePoints { get; private set; } protected virtual int IntervalSeconds { get; private set; } protected IEngine Engine { get; private set; } public bool IsCanceled{get{……}} public bool IsExecuting{get{……}} public bool IsTimeToExecute{get{……}} public abstract bool Enable { get; } public abstract string Name { get; } protected MissionBase(IEngine engine) { ExecuteTimePoints = null;//默認採用間隔的方式 IntervalSeconds = 60 * 60;//默認的間隔爲1個小時 Engine = engine; } /// 任務的執行方法 public void Done() { if (Interlocked.CompareExchange(ref _isExecuting, 1, 0) == 1) return; try { …… } finally { Interlocked.CompareExchange(ref _isExecuting, 0, 1); } } ///實際方法的執行 protected abstract void DoneReal(); }
可是,實際上這個任務方法,並很差用,要寫的代碼很多,並且可靠性尚未保障。固然,我能夠繼續完善這個類,可是我決定搜索一下是否還有其餘的方法。直到有一天,我再次閱讀《CLR Via C#》,看到線程這一章,講到了System.Threading.Timer以及ThreadPool類時,我就知道了,使用Timer類徹底能夠解決個人這個用盡可能少的線程完成定時任務的問題。
由於從原理上來講,Timer類不管你聲明瞭多少個,其實就只有一個線程在執行。當你到了執行時間時,這個管理線程會用ThreadPool來執行Timer中的函數,由於使用的ThreadPool,執行完成以後,線程就立刻回收了,這個其實就徹底實現了我所須要的功能。
等你沒法重構的時候再考慮重寫
我帶過不少優秀的程序員,也與不少優秀的程序員共事過。有一大部分的程序員在看到一套系統不是那麼滿意,或者存在某些明顯的問題,就老是忍不住要把整套系統按本身以爲能夠優化的方向來重寫,結果,重寫結構每每並不使人滿意。系統中確實存在不少不合理的地方,可是有很多的這種代碼,偏偏是爲了解決一些特定場景下的問題的。也就是說,全部的規範以及編程的原則,其實也是有條件限制的,他可能在大部分的時候是正確的,可以指導你完成你的任務,可是,並非在全部地方都是適用的。好比數據庫範式,但實際中咱們的設計每每會考慮冗餘,這是違背範式的,可是爲何還有那麼多人趨之若鶩呢?由於咱們可能須要用空間換時間。
若是咱們一開始就考慮重寫,那麼你可能會陷入如下的困境:
我舉個例子,說明如何經過重構更好的利用現有代碼的。
我有一個很是龐大的系統,其中有一塊功能是用於數據採集、存儲、告警管理以及電話、短信等告警通知。大體的結構以下:
class MainEngine:IEngine{ public MainEngine(ConfigSettings config){ } public void Start(); public void Stop(); }
須要增長新的業務功能時,程序員寫的代碼每每是這樣的:首先時修改配置類
class ConfigSettings{ public bool NewFuncEnable{get;private set;} public ConfigSettings(){ NewFuncEnable=xx;//從配置文件讀取 } }
接着修改主程序:
class MainEngine:IEngine{ private NewFuncClass newCls=new NewFuncClass(); public MainEngine(ConfigSettings config){ } public void Start(){ if(config.NewFuncEnable) newCls.Start(); } public void Stop(){ if(config.NewFuncEnable) newCls.Stop(); } }
在修改的過程當中,每每是根據配置文件來判斷新功能是否啓用。上面代碼會形成什麼問題呢:
那麼咱們如何對這段代碼進行重構呢。首先,咱們把新功能註冊的代碼抽取出來,經過反射來實現新的功能的註冊。
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
private void RegisterTaskHandlerBundles() { var bundles = xxx.BLL.Caches.ServiceBundleCache.Instance.GetBundles("TaskHandlerBundle"); if (bundles != null && bundles.Count > 0) { var asmCache = new Dictionary<string, Assembly>(); foreach (var bundle in bundles) { try { if (!asmCache.ContainsKey(bundle.Category)) asmCache.Add(bundle.Category, Assembly.Load(bundle.AssemblyName)); var handler = (ITaskHandler)asmCache[bundle.Category].CreateInstance(bundle.ClassName, false, BindingFlags.Default, null, new object[] { this, bundle }, null, null); _taskHandlerBundles.Add(bundle, handler); } catch (Exception e) { NLogHelper.Instance.Error("加載bundle[Name:{0},Assembly:{1}:Class:{2}]異常:{3}", bundle.Name, bundle.AssemblyName, bundle.ClassName, e.Message); } } } }
修改MainEngine代碼
class MainEngine:IEngine{ private NewFuncClass newCls=new NewFuncClass(); public MainEngine(ConfigSettings config){ RegisterTaskHandlerBundles(); } public void Start(){ _taskHandlerBundles.Start(); } public void Stop(){ _taskHandlerBundles.Stop(); } }
OK,如今咱們再來看看怎麼實現原來的新增功能:你只需按規範新建一個類,繼承ITaskHandler接口,並實現接口的方法。最後在XTGL_ServiceBundle表中新增一條記錄便可。咱們再來看看這麼作有什麼好處:
重構的目標之一,就是把框架和業務徹底分離。
有志於深刻了解的同窗,能夠了解下反射、Ioc和插件話編程等。
學會單元測試,培養你的重構意識
可能上面說了這麼多,仍是有不少人並不理解重構。不要緊,在這裏我教大家一個快速入門的辦法,就是單元測試。什麼是單元測試,請自行google。單元測試有什麼要求?就是要求你要把每一個方法都弄成儘可能能夠測試的。儘可能讓你的方法變成是可測試的,就是培養你重構意識的利器。在你要求把方法變成可測試的過程,你就會發現你必須得不斷的修改你的方法,讓它的職責儘可能單一,讓它儘可能的與上下文無關,讓它儘量經過方法參數的輸入輸出就能完成相關的功能,讓依賴的類都儘可能改成接口而不是實例。最終,你就會發覺,這就是重構!並且是在不知不覺中,你重構的功力就會大大提高,你編程的水平也會大大提高!
看到這裏,有經驗的程序員就會問,你這是在鼓勵我使用TDD嗎?不,不是的。TDD(Test-Driven Development)鼓勵的是測試驅動開發,未開發以前先編寫單元測試用例代碼,測試代碼肯定須要編寫什麼產品代碼。這是一種比較先進的開發方法,可是在編程的實踐過程當中,我認爲它過於繁瑣,不少中小企業很難實施,更別提咱們我的開發者。我這裏提倡你用單元測試培養你的重構意識,能夠說是一種後驅動,用於提升你的重構能力和重構願望,你徹底能夠把個人這個方法稱爲「TDR(Test-Driven Refactoring)——測試驅動重構」。固然,在開發以前若是你有意識的讓方法可測試,那麼你寫出來的函數將會是比較高質量的代碼。當你的函數都是一個個可重用性高的函數之時,你將會發現,寫代碼其實就像堆積木同樣,能夠把一個大型的需求分解成無數細小的功能,很快的把需求實現。
如下是一個超大方法中的一段代碼,若是你懂得怎樣讓這段代碼編程一個可測試的方法,那麼,恭喜你,你入門了。
所謂重構
若是你有耐心看到這裏,你應該知道,我並不是一個標題黨,而這篇文章也許稱爲「如何在編程中應用重構的思想」更爲貼切,可是我不想用這麼嚴肅的標題。
不少編程初學者,或者有多年編程經驗的人都以爲閱讀別人的代碼很是困難,重構更是無從談起,他們要麼對這些代碼望洋興嘆,要麼就是推翻歷來。可是,若是咱們有重構的意識,以及在編程的過程當中熟悉一些代碼調整和優化的小技巧,你天然而然就會培養出重構的能力。
重構,其實很簡單:
若是你堅持這麼去作了,一段時間以後感受天然就出來了。
重構的目的,是讓你的代碼更爲精簡、穩定、可以重用,是最大程度的讓功能和業務分離。在重構的過程當中,你的閱讀代碼的能力、寫出優秀代碼的能力以及系統架構能力都會穩步提高。你成爲一個優秀的程序員將指日可待。
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。