为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

从把三千行代码重构成15行代码谈起

2017-12-28 15页 doc 110KB 7阅读

用户头像

is_954223

暂无简介

举报
从把三千行代码重构成15行代码谈起从把三千行代码重构成15行代码谈起 邮件群发 如果你认为这是一个标题党,那么我真诚的恳请你耐心的把文章的第一部分读完,然后再下结论。如果你认为能够戳中您的G点,那么请随手点个赞。 把三千行代码重构为15行 那年我刚毕业,进了现在这个公司。公司是搞数据中心环境监控的,里面充斥着嵌入式、精密空调、总线、RFID的概念,我一个都不懂。还好,公司之前 用Delphi写的老客户端因为太慢,然后就搞了个Webform的替代,恰好我对Asp.Net还算了解,我对业务的不了解并不妨碍我称成为这个公司的 一个程序员。小公司也有小公司的好,...
从把三千行代码重构成15行代码谈起
从把三千行代码重构成15行代码谈起 邮件群发 如果你认为这是一个标题党,那么我真诚的恳请你耐心的把文章的第一部分读完,然后再下结论。如果你认为能够戳中您的G点,那么请随手点个赞。 把三千行代码重构为15行 那年我刚毕业,进了现在这个公司。公司是搞数据中心环境监控的,里面充斥着嵌入式、精密空调、总线、RFID的概念,我一个都不懂。还好,公司之前 用Delphi写的老客户端因为太慢,然后就搞了个Webform的替代,恰好我对Asp.Net还算了解,我对业务的不了解并不妨碍我称成为这个公司的 一个程序员。小公司也有小公司的好,人少,进去很快负责代码开发。我当然也就搞这个数据中心智能管理系统啦。 这个系统非常的庞大,尤其牛逼的是支持客户端组态,然后动态生成网页,数据还能通过Socket实时监控(那时我还真就不懂网络编程)。这个对于当时的我来说,真真是高、大、上呐~~当时跟着了解整个系统大半个月才算能够调试,写一些简单的页面。 在维护系统的过程中,时不时要扩展一些功能,也就接触了下面这个类: 看到没有,就是当年最最流行的三层架构的产物,对于刚出茅庐的毛头小子来说,这是多么专业的文件头注释,还有反射也就算了,这构造函数还能静态的,还能私 有的,那时刚接触这么高大上的代码的我,瞬间给跪了~ 但是,类写多了,我就感觉越来越别扭,就是下面这段代码: 每 增加一个,除了要改接口、要改DAL、要改BLL之外,还得在这个工厂类添加一个方法,真真是累到手抽筋,即使有当时公司了的G工给我推荐的神器——动 软代码生成器,这粘贴复制的几遍,也是让我感觉到异常繁琐,有时候打键盘稍微累了点,还把复制出来代码改错了,你妹的,难道这就是程序员该干的事情,不, 绝对不是~我想起了一句至理名言:当你觉得代码重复出现在程序中的时候,就应该重构了。是的,在这句话的指导下,我开始了折腾,决定挑战这个高大上的代码,事实证明,思想的力量是无穷的。 那么,怎么修改呢,仔细观察之后,发现其中className的生成跟返回的类型非常类似,只是一个是类名,一个是字符串,这两者之间应该能够关联起来。于是google了一下(当时GFW还没猖獗起来哈),隐隐约约就找到了“反射”这两个字,深入了解之后,确定可以完成。 接下来,就是返回的类型了,返回的类型并不固定,但是似乎很有规律……这个似乎好像在哪里见过,对了,,C++课程上有讲过的,于是再次google,了解到了C#中使用了泛型代替了C++中的模板。在学习完泛型和反射之后,并参考了网上的一些文章,我捣鼓出了下面的代码: 没错,就是它了,三层架构年代最流行的工厂类…… 看着原来滚十几屏幕的代码,变成了十多行的代码,真是爽到了骨子里去了,太干净了~唯一让我担忧的是,我进公司的时候,帮忙整理公司申请软件著作权 都是需要代码量的,根据代码多少行来评估软件的大小,万一老板知道了我非但没有帮公司增加代码量,还减少了,会不会立即把我开掉,我没敢给我们老板展示我 优秀的成果,所幸,这段代码非但没有出过任何问题,还避免了以前同事老是在新增一个类之后,把代码复制过来,但是没有正确修改的问题,大大提高了效率。虽 然,我没敢大事宣布我的劳动成果,但是这次成功的修改,则彻底让我走上了代码重构的不归路。 看到这里,大家应该知道这个案例是否真实的了吧。我相信,从08年开始的码农们,看到这种类似的代码绝对不比我少。那么,我想告诉你们的是什么呢, , 要在编程过程中多思考 , 编程的思想很重要,请多看点经典的书 , 从小处着眼,慢慢重构,尤其在应对一个大型的系统 , 当重复出现的时候,你应该考虑重构了 , 粘贴复制的代码越少,你的系统越稳定 少用代码生成器 我们来分析一下,为什么我之前的前辈会写出上面的代码。我归结起来有以下几 点: , 因为使用了动软代码生成器,生成代码方便,就没多想了。 , 三层架构的概念倒是了解了,但是没有去深入思考就拿来应用 , 遇到重复的代码,没有重构的概念,这是思想的问题——思想比你的能力重 要 至今为止,还是很多人使用代码生成器,那么我们应该怎么对待这个问题呢。我认 为,代码生成器确实可以减少你不少工作,但是少用,那些重复性的工作, 除了 部分确实是没有办法的,其他大部分都是可以通过框架解决的,举例来说,像三层 架构,真正需要用到代码生成器的,也就是Model类而已,其他的完全可 以在框 架中完成。因此你要竭尽全力的思考怎么在框架中来减少你的重复性工作,而不 是依赖于代码生成器。 另外,如果你还是在用相关的代码生成工具,请重新定义“动软代码生成器”的代 码模板,自己写一个模板;或者使用CodeSmith来完全制定自己的代码生成,因 为动软给的代码模板真心乱,比如下面这段代码: 1. for (int n = 0; n < rowsCount; n++){ model = new DB Access.Model.eventweek(); if(dt.Rows[n]["GroupNo"].ToS tring()!="") { model.GroupNo=int.Parse(dt.Rows[n]["Gro upNo"].ToString()); } if(dt.Rows[n]["Week0"].ToString( )!="") { model.Week0=int.Parse(dt.Rows[n]["Week0"].ToS tring()); } if(dt.Rows[n]["Week1"].ToString()!="") { m odel.Week1=int.Parse(dt.Rows[n]["Week1"].ToString()); }} 首先,你就不能用 var row=dt.Rows[n] 替代吗?其次,直接用int.Parse如果抛出了异 常性能得有多低,再次,这段代码要是有点修改,我不是要每个dt.Rows[n]得改一 遍, 不要重复发明轮子 我们再来看看其他的一些代码: 1. public List GetDevices(string dev){ List devs=new List(); 2. int start=0; for(int i=0;i规范
以及编程的原则,其实也是有条件限制的,他可能在大部分的时候是正确的,能够指导你完成你的任务,但是,并不是 在所有地方都是适用的。比如数据库范式,但实际中我们的往往会考虑冗余,这是违背范式的,但是为什么还有那么多人趋之若鹜呢,因为我们可能需要用空间 换时间。 如果我们一开始就考虑重写,那么你可能会陷入以下的困境: , 需要花更大的精力来完成一些看似简单的BUG 你要知道,有一部分看似错误或者非常不优美的代码,其实恰恰是为了解决 一些非常刁钻的问题的。 , 再也无法兼容老的系统了 你急于把原有系统重写,却往往忽略了对原有系统的兼容,那么你新的系统 的推进则会十分缓慢。而老系统的维护,又会陷入及其尴尬的情况。 , 过度设计,导致重写迟迟无法完成 有重写冲动的程序员往往是在架构设计上有一些读到的见解,他们善于利用 所学的各种设计模式和架构技巧来建立系统,但是越是想尽可能的利用设计 模式,越是陷入过度设计的困局,导致重写的计划迟迟都无法完成。 , 无法有效利用现有系统已经完成并测试的代码 如果你确实有必要进行重写,我还是建议你把代码尽可能的重构。因为重构 之后的系统,能够让你更轻易的重写,又最大限度了保留以前可用的业务 代码。 我举个例子,说明如何通过重构更好的利用现有代码的。 我有一个非常庞大的系统,其中有一块功能是用于数据采集、存储、告警管理以及 电话、短信等告警通知。大致的结构如下: 1. class MainEngine:IEngine{ public MainEngine(ConfigSe ttings config){ } 2. public void Start(); public void Stop();} 需要增加新的业务功能时,程序员写的代码往往是这样的:首先时修改配置类 1. class MainEngine:IEngine{ private NewFuncClass newCl s=new NewFuncClass(); public MainEngine(ConfigSettings config){ } 2. public void Start(){ if(config.NewFuncEnable) newCl s.Start(); } public void Stop(){ if(config.NewFu ncEnable) newCls.Stop(); }} 接着修改主程序: 1. class MainEngine:IEngine{ private NewFuncClass newCl s=new NewFuncClass(); public MainEngine(ConfigSettings config){ } 2. public void Start(){ if(config.NewFuncEnable) newCl s.Start(); } public void Stop(){ if(config.NewFu ncEnable) newCls.Stop(); }} 在修改的过程中,往往是根据配置文件来判断新功能是否启用。上面代码会造成什 么问题呢: , 主程序代码和扩展功能耦合性太强,每增加一个功能都要修改主程序代码, 这里非常非常容易出错。尤其是新的人进度开发组,很容易就忘主程序中增 加了一些致命性的代码。比如上述的扩展功能,可能是在特定的项目中才会 有这个扩展功能,但是,写代码的人忘记增加是否启用的配置选项了,导 致所有的项目都应用了这个功能,而这个功能需要特定的表,这样就悲剧 了。即使是你增加了配置,也是非常的不美观,因为在通用的版本中使用了 这个配置,往往会让定制项目以外的人员感到困惑。 , 增加扩展功能的人还需对整个MainEngine代码有一定的熟悉,否则,他根 本就不知道在Start方法和Stop方法进行newClas的对应方法的调用 , 如果你打算对这段代码进行重写,那么,你会感到非常的困难,因为你分不 清楚newCls这个新实例的作用,要么你花大精力去把所有代码理清楚,要 么直接就把这段新增的业务代码去掉了。 那么我们如何对这段代码进行重构呢。首先,我们把新功能注册的代码抽取出来, 通过反射来实现新的功能的注册。 1. private void RegisterTaskHandlerBundles() { var bundles = xxx.BLL.Caches.ServiceBundleCache.In stance.GetBundles("TaskHandlerBundle"); if (bundles != null && bundles.Count > 0) { var asmCache = new Dicti onary(); foreach (var bundle in bund les) { try { if (!asmCache.ContainsKey(bundle.Category )) asmCache.Add(bundle.Category, Assembly.Load(bundle. AssemblyName)); var handler = (ITaskHandler)asmCache[b undle.Category].CreateInstance(bundle.ClassName, false , BindingFlags.Default, null, new object[] { this, bun dle }, null, null); _taskHandlerBundles.Add(bundle, ha ndler); } catch (Exception e) { NLogHelper.Instance.Er ror("加载bundle[Name:{0},Assembly:{1}:Class:{2}]异常: {3}", bundle.Name, bundle.AssemblyName, bundle.ClassNa me, e.Message); } } } } 2. 3. 修改MainEngine代码 4. 5. class MainEngine:IEngine{ private NewFuncClass newCl s=new NewFuncClass(); public MainEngine(ConfigSettings config){ RegisterTaskHandlerBundles(); } 6. public void Start(){ _taskHandlerBundles.Start(); } public void Stop(){ _taskHandlerBundles.Stop(); }} OK,现在我们再来看看怎么实现原来的新增功能:你只需按规范新建一个类,继 承ITaskHandler接口,并实现接口的方法。最后在XTGL_ServiceBundle表中新增 一条记录即可。我们再来看看这么做有什么好处: , 新增的类只需按规范写即可,完全对MainEngine代码没有任何影响。你甚 至可以把这个MainEngine代码写在一个新建的Dll中。 , 新增功能的这个业务类跟原来的代码解耦,非常方便进行新功能的业务测 试,而无需考虑原有框架的影响 , 新增功能的业务类与架构完全分离,我们在重写代码中只要保证接口的稳定 性,无论我们怎么把系统架构重写,我们可以马上就重用上原有的业务功能 代码。 重构的目标之一,就是把框架和业务完全分离。 有志于深入了解的同学,可以了解下反射、Ioc和插件话编程等。 学会单元测试,培养你的重构意识 可能上面说了这么多,还是有很多人并不理解重构。没关系,在这里我教你们一个快速入门的办法,就是单元测试。什么是单元测试,请自行google。单元测试有什么要求,就是要求你要把每个方法都弄成尽量可以测试的。尽量让你的方法变成是可测试的,就是培养你重构意识的利器。 在你要求把方法变成可测试的过程,你就会发现你必须得不断的修改你的方法,让它的职责尽量单一,让它尽量的与上下文无关,让它尽可能通过方法参数的输入输 出就能完成相关的功能,让依赖的类都尽量改为接口而不是实例。最终,你就会发觉,这就是重构~而且是在不 平也会大大提升~ 知不觉中,你重构的功力就会大大提升,你编程的水 看到这里,有经验的程序员就会问,你这是在鼓励我使用TDD吗,不,不是的。TDD(Test-Driven Development)鼓励的是测试驱动开发,未开发之前先编写单元测试用例代码,测试代码确定需要编写什么产品代码。这是一种比较先进的开发方法,但 是在编程的实践过程中,我认为它过于繁琐,很多中小企业很难实施,更别提我们个人开发者。我这里提倡你用单元测试培养你的重构意识,可以说是一种后驱动, 用于提高你的重构能力和重构愿望,你完全可以把我的这个方法称为“TDR(Test-Driven Refactoring)——测试驱动重构”。当然,在开发之前如果你有意识的让方法可测试,那么你写出来的函数将会是比较高质量的代码。当你的函数都是一个个可重用性高的函数之时,你将会发现,写代码其实就像堆积木一样,可以把一个大型的需求分解成无数细小的功能,很快的把需求实现。 以下是一个超大方法中的一段代码,如果你懂得怎样让这段代码编程一个可测试的方法,那么,恭喜你,你入门了。 所谓重构 如果你有耐心看到这里,你应该知道,我并非一个标题党,而这篇文章也许称为“如何在编程中应用重构的思想”更为贴切,但是我不想用这么严肃的标题。 很多编程初学者,或者有多年编程经验的人都觉得阅读别人的代码非常困难,重构更是无从谈起,他们要么对这些代码望洋兴叹,要么就是推翻从来。但是,如果我们有重构的意识,以及在编程的过程中熟悉一些代码调整和优化的小技巧,你自然而然就会培养出重构的能力。 重构,其实很简单: , 把基础打牢固 , 多看点优秀的代码 , 避免复制粘贴,如果看见重复代码时应该有意识要消灭它 , 减少对代码生成器的依赖 , 在处理现有代码时尽量用重构代替重写,在重写之前一定要先重构 , 尽量让所有的方法都是可测试的 如果你坚持这么去做了,一段时间之后感觉自然就出来了。 重构的目的,是让你的代码更为精简、稳定、能够重用,是最大程度的让功能和业务分离。在重构的过程中,你的阅读代码的能力、写出优秀代码的能力以及系统架构能力都会稳步提升。你成为一个优秀的程序员将指日可待。
/
本文档为【从把三千行代码重构成15行代码谈起】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索