代码重构的一个示例

代码重构的一个示例

还记得以前和大家提到过的《各种流行的编程风格》吗?有一些人问我那些编程风格具体是什么样子的。下面是一个代码重构的实例,让我们看看那个流行的编程风格是实践是什么样的。下面的这个实践不是虚构,如有雷同,请对号入座。

首先,我们有一个表达式如下所示:

s = 7;

很明显,这个表达式的变量名太没意义了,很不利于程序的可读性,所以,我们需要取一个有意义的变量名:

slots = 7;

很好,不过,那个常量7是hard-code或是一个Magic number,而且,这常量没有名字也不利于代码的可读性啊。再改:

SEVEN = 7;
...
slots = SEVEN;

靠!上面,是这是哪门子的改法?(不过,我保证这是真实发生的),常量名也要有意义一点嘛,再改:

SLOTS_PER_WIDGET = 7;
...
slots = SLOTS_PER_WIDGET;

这还差不多,不过,名字可能会重名啊,最好放到一个类中:

import widgetConstants;
...
slots = widgetConstants.SLOTS_PER_WIDGET;

现在看起来好很多了,不过,即然面向对象了,我们当然要学会使用Design Pattern,比如Factory啊,或是Singleton啊什么的:

widgetModelFactory = WidgetModelFactory.getInstance();
widgetModel = widgetModelFactory.getWidgetModel() ;
slots = widgetModel.getSlotsPerWidget();

当然,要是考虑到整体的类结构,上面的那些还不够,下面是我们最终的重构代码:(欢迎来到真实的Java世界)

context = Context.getCurrentContext();
serviceDirectoryFactory = ServiceDirectoryFactory.getServiceDirectory(context);
serviceDirectory = serviceDirectoryFactory.getServiceDirectory(context);
serviceDescriptor = ServiceDescriptorFactory.getDescriptor("widgetModelFactory");
widgetModelFactoryServiceLocator = serviceDirectory.getServiceLocator(serviceDescriptor,context);
widgetModelFactory = (WidgetModelFactory)widgetModelFactoryServiceLocator.findService(context);
widgetModel = widgetModelFactory.getWidgetModel(context);

slots = widgetModel.getSlotsPerWidget();

这就是我们的面像对象的编程模式,记得N年前在面试那家著名的以鼓吹敏捷方法论的公司时,在用程序实现一个程序题的时候,他们对我的程序很不屑一顾,原因有两个,其一、我没有使用TDD写UT Case,其二、我的程序里没有设计模式。(我才知道,编程原来是为了测试和设计模式,而不是为了原来的需求),今天,仅以此文献给钟爱于那些流行编码风格的程序员们。

其实,这段代码也是如下而已罢了。

slots = thisWidget.getSlotCount();

(全文完)

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

好烂啊有点差凑合看看还不错很精彩 (25 人打了分,平均分: 4.04 )
Loading...

代码重构的一个示例》的相关评论

  1. 在吃早餐的时候看到那一长串Java代码,我的胃表示压力很大。。。

  2. 盲目相信就是迷信。的确有太多人对面向对象啊,设计模式啊不经大脑的就相信并使用,而效果恐怕并不好。一个事务的价值是在她的优点中体现的,不是缺点。可是任何事物都是有缺点的啊,所以如果不能合理对待,很可能就是得不偿失。只知道实现设计模式却不知道这个模式好在哪,其实还不如干脆不懂或者不用。

  3. 谨慎估计这行有误:

    serviceDirectoryFactory = ServiceDirectoryFactory.getServiceDirectory(context);
    >
    serviceDirectoryFactory = ServiceDirectoryFactory.getServiceDirectoryFactory(context);

  4. 重构是为了增加代码的可读性,模式是为了提高代码的可维护性。
    考虑到时间和成本,将一段简单的代码整成这样还真是过而不及啊。

  5. 我觉得通过设计模式design出这样复杂代码来的原因有两个,如果不是下面的原因,那就是在滥用:
    1.系统复杂性的要求。如果不止一个常量呢,如果上下文真的是敏感的呢,尤其是上下文的基础设施是多变的,如果。。。
    2.对于耦合的转移的必要性要求。如果系统需要做到对耦合的转移,比如转移到配置文件、ldap等,那么掩盖这些东西复杂性的模式就显得很有必要了。

  6. 看来LZ和敏捷有仇。人家公司用敏捷方式做开发,招人当然也要招懂敏捷的。
    你一不知道测试驱动,二不知道设计模式别人凭什么招你呢?

    1. 不是不知道,就一道几十行代码的编程题,没有必要啊。我不会因为我懂TDD,我懂设计模式,于是我的程序里,到处都用TDD,到处都是设计模式。不过有一点是这样的,无论是敏捷还是瀑布,都一样,信仰的人大多时候都很教条主义。

  7. 几十行代码的编程题同样可以TDD,如果LZ你认真尝试并理解TDD,你会知道TDD的思想就是要到处都用TDD。并且TDD最大的问题不是到处用,而是没法到处用,因为世界上总用一些问题是没法TDD的。
    当然没有需求的设计模式是有害的。如果重构出来的代码中进行一个GET都要经过四五个Factory那这是个失败的重构,而不是说重构这个行为没必要
    @陈皓

    1. Lazy,呵呵,请你不要走另一个极端。我这篇文章展示的是过度重构,过度设计,或是为了重构而重构,我说是的教条,而不是完全抵制这些东西。很高兴和你交流。

  8. 请问楼主展示的“过度重构,过度设计”是真实发生的吗? 怎么我所见到的国内中小型公司,目之所及,皆是设计不足,重构不足呢
    如果哪家公司出现这样的示例,只能说明这家公司的人很清闲,没什么活做,有大把的时间整出这么繁杂的玩意儿,建议裁员,消减成本,呵呵…
    我赞成编程要以需求为导向,但重构、设计模式绝不是为了功能性的需求。用完一次就扔的程序没必要使用任何设计模式,无需重构。而一个需要持续改进和升级的系统,如果没有好的设计并持续地重构,维护将会成为一场噩梦。
    另外,这跟Java语言N不NB没啥关系,只不过java里面OO思想很自然。

  9. 重构的目的是为了重构而重构,还是为了简单而重构?
    很显然本文的例子是个失败的重构。

  10. @liuchangit 程序员闲倒不是,不过某些程序员或leader很喜欢钻技术、方法和流程的牛角尖,这和书呆子在本质上没有什么分别,如果你能够理解为什么读书会读成死读书,会读成书呆子,如果你能够理解这世上会为什么会有学院派,为什么会有理论派,那么,这样的例子你应该也不会感到诧异。OO的思想,TDD的思想,重构的思想,还有CMMi的思想,在很多人眼中都是那么的自然,但大家都忘了,OO,TDD,重构等的目的是为了更好的满足用户需求和技术需求,是把复杂的问题变成简单,而不是把需求问题变成设计模式,变成TDD。本文的例子仅是给那些TDD-Oriented, Design Pattern-Oriented,Agile-Oriented的朋友提个醒。

  11. 文中的例子没有上下文的话,有断章取义之嫌。任何较为大型的程序里,你都可以丢弃上下文以及具体需求,然后摘抄一些类似的代码,然后说这样很垃圾。请问那种是最好的呢?
    其实这是一个不好和最不坏的问题:
    slot = 7; 一般情况下,我相信没人会认为这是好的风格。当然不排除写这个程序的目的就是为了给一个变量赋值7。这种情况下我不反对你这么写。
    那么对于后面的那么一大坨代码,我相信有经验的人都不会轻易就断言这些都是垃圾。
    设计模式既然存在,就有它好的地方,当然也有被滥用的地方。这个都是要看实际情况的。所以很多人在探讨设计模式的同时也在探讨反模式。
    敏捷么,当SLOT = 7不适用的时候,再重构成更加适合的形式就可以了。
    任何一个方法,总会有些东西不合某些人的口味,其实关键在于这个某些人还没有遇到真正的这个方法能够解决的问题。
    唉。。。不想多说了,反正我也不可能改变谁的想法。呵呵…

  12. 不得不说 尽管我一直强调重构 但我还是想说出最心底的话 模式讲多了真是一坨屎啊
    看到最后那一坨代码 我敢保证这是真实写照 还好有eclipse alt+/ 就出来了 这就是为什么C程序员喜欢用VI而JAVA只能用eclipse的原因

  13. @vgamed
    一般来说,程序中不应该出现magic number的,例如:char name[50],50就是一个magic number,在很多C/C++库中,50必然会被一个有意义的常量名所取代,如:MAX_NAME_LEN 之类的,原因有二,1)如果你不用常量名的话,所有与这个变量依赖的程序你都要使用这个magic number(例如:判断数组越界等),2)magic number很不利于程序的易读和维护(例如:需要修改这个值,而且这个值是怎么来的,什么意思也没有说明)。

    设计模式和敏捷方法的存在的原因是把复杂的问题变简单,所以,如果使用设计模式或是敏捷把已有的简单的问题变复杂,那么真是“东施效颦”了。我们因为看着他人用模式用的那么好看,然后我们不分清红皂白也用,最后也只能是教条主义,画虎不成反类犬!

    本文的例子就是这么一个例子。只希望大家看个笑话的同时也能有所反思。

  14. 我还是要说类似的话:这个笑话在没有说明《上下文》的情况下,让我笑不起来。
    如果在这个笑话的开始就说明某个程序的最终功能就是给某个变量赋值,那么我会会心一笑然后飘过。
    可是笑话的开头是泛指的:
    “首先,我们有一个表达式如下所示:s = 7;”

    那么我认为这个所谓的笑话很可能会误导很多程序员。

    模仿 – 理解 – 反思和创新, 这是一个典型的人类学习过程。那么只有在理解后的反思才是真正有价值的反思。

    试问,很多人在这个笑话的回复中都在否定设计模式,那么设计模式真的那么一文不值么?

  15. 该文虽然将作者的意思表达得淋漓尽致,但过于尖酸刻薄。当软件成为产品,以工程化模式进行开发生产,其追求的利益就不再仅仅是一个眼前的“结果”。产品以流的形式繁衍进化,这就需要一个结实的“骨架”,一个可扩充的平台。否则生产的只是一次性的模具而已。至于本文中所指的特例,至少可读性的要求是必须的。对于阅读他人源码来说,毕竟频繁的检索开发文档不是最好的选择。以我个人来看,编码的可读性与编码中所体现的结构性与前瞻性既是程序员的基本技术素质又是基本的道德修养(当然,我指的是以功能点而非代码行计算工作量的公司的程序员)。

  16. @陈皓
    回复#23楼博主:我完全明白并无比赞同你的观点。你用黑体字所强调的,并非我有异议的。也许是因为职业经历和所处环境的不同,可能你所见到的把事情做过头的书呆子、学院派多一些,我所见到的粗放型不懂“设计模式”的散兵游勇多一些。我们的观点立场折射出彼此的处境。对你说的“在很多人眼中都是那么的自然,但大家都忘了……”,这里的“很多人”、“大家”,我相信只是一小撮人。毕竟,一个社会中的“书呆子、学院派”能有多少呢?
    @Gilbert 的回复,于我心有戚戚焉。

  17. 想起了奥卡姆剃刀……
    我觉得自己就一直不习惯看英文长难句一般的java代码。相比之下我更喜欢c的简洁精练。当然程序不是越简单越好,关键还在于用恰当的方式描述我们对问题的抽象:)

  18. @陈皓

    楼主,任何人任何事都不能保证是绝对正确的。比如你的这段代码,看用在什么时候了,不同的context需要不同的设计。比如你的那次面试,可能是人家低估了你的能力,可能是人家错过了一个优秀的人才,也可能是你确实不是人家想要的。但是,这种事情任何地方,任何时候都在发生着。你这样揪着不放,不是刚好表现你对人家还抱有幻想么?

    人,还是豁达点好。

  19. @liuchangit
    是啊,重构不足比过分重构的情况应该是多多了~就好像大家天天在喊“不要用setter/getter”,但是在c里面以oo方式开发的话,我宁愿看到“setter/getter”都不愿看到一个struct被直接修改。
    过犹不及,在国外很多已经“过了”的事,在中国还是“不及”。

  20. 重构来获取模式吧,不要为了模式而模式,呵呵.
    就像一群人说要抵制if-else语句一样,每个if-else都要用多态来实现,那就够你喝一壶的了

  21. 关于setter/getter,我使用它的最大理由就是调试。没有这个,你怎么调试某个变量啥时候被修改了?修改后如果没有人读那是不会出毛病的,那是谁在读它?有了setter/getter,你就可以往里面加个断点或者log出来。

  22. 国内的确实是重构做的很不够,原因大家应该都清楚,不外乎没时间,项目紧,一个接一个,再一个能力不行。

  23. @kingsun
    没有好的设计和测试做保证,重构容易引入新的bug,带来潜在的风险,这也是很多项目重构做得不够的一个原因,开发人员根本不敢去重构现有的代码。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注