[译文]开发者见解系列,第1部分:编写傻瓜代码——来自四位首席Java开发者的建议(下)

原文:The Developer Insight Series, Part 1: Write Dumb Code -- Advice From Four Leading Java Developers

作者:Janice J. Heiss

出处:http://java.sun.com/developer/technicalArticles/Interviews/devinsight_1/

 

 

Cay Horstmann:模式并非魔法药水

 



关于
Cay Horstmann

 

荣获Java Champion称号的Cay Horstmann在德国北部长大,并入读波罗的海边上的海港城市KielChristian-Albrechts-Universität大学,拥有Syracuse大学计算机科学的M.S.学位和Michigan大学的数学方面的Ph.D.学位,他现在是CaliforniaSan Jose州立大学的计算机科学教授。他曾是一家新兴的网络公司的VPCTO,在此之前,他拥有一家很成功的销售编辑科技文档的DOS程序的公司。Horstmann利用空余时间提供Internet编程方面的咨询。

 

我同意Brian Goetz的观点。就我这些年的经验来说,除非已做了配置,否则你不会优化代码,我们更发愁的是数据的缓存而不是重新计算他们、消除层次等这样的事情,更多时候,这仅使性能稍有不同,但却在调试方面引入了颇为痛疼的问题。

我看到你有问Heinz Kabutz相同的问题,他说:“我通常都鼓励软件开发公司在设计模式方面培训他们的所有的开发者,包括从最初级的人员到最聪明的架构师。”我对这一说法有些不太能接受。我同意模式应该是每个人接受培训的一部分,但我有太多的初级程序员在他们的代码上放上了过多的模式,以期改善代码。模式并非魔法药水,且需要相当多的经验而不是一般情况下就能做到明智地使用他们。

Java I/O库为例,该库就深深浸染了装饰模式(decorator pattern)的价值观念,例如,BufferedReader是一个装饰器,从文件中获得缓冲读,你这样做:

 

Reader reader = new BufferedReader(new FileReader("foo.txt"));

 

 

如果你还想往前看看(lookahead)的话该怎样呢?现在你需要在装饰器链中插入一个PushbackReader

真是痛苦!我倒宁愿更多一点可用性而少一点模式教条,在C++中,缓冲以及往前查看是每个文件流的组成部分,在实践中这一方面提供了更多的便利性。

 

查阅对Cay Horstmann所做的完整访谈。

 

Kirk Pepperdine:傻瓜代码的可读性更强

 



关于
Kirk Pepperdine

荣获Java Champion称号的Kirk Pepperdinejavaperformancetuning.com的主要贡献者,该网站被广泛当作Java性能调优信息来源的首要网站,KirkAnt Developer’s Handbook一书的合著者,自开始编程生涯起,他就一直积极地参与应用性能方面的工作,并对各种语言编写的应用进行调优,这些语言包括:Cray汇编语言、CSmalltalk等,以及自1996年以来的Java技术;他还致力于为分布式应用构建中间件。

他曾在加拿大国防部做过Cray超级计算机方面的工作,以及做过Florida Power & Light的顾问和GemStone Systems的高级顾问,他目前是一个独立顾问,以及TheServerSide.com的编辑。

 

我询问Pepperdine如何看待Brian Goetz的观点。

 

实际上这里有两个问题:首先,编写傻瓜式代码如何有助于性能?其次,编写结构性好的代码如何有助于性能?我先来回答“傻瓜代码”的问题。

虽然我们编写的代码运行在机器上,但代码的主要消费者是人,傻瓜代码往往更具可读性,因此更易于理解。如果我们能消除某些缺陷的话,我们就会更好地避免一些愚蠢的、可能会被聪明的代码隐藏起来的错误。

HotSpotIBMMMI以及JIT都是通过动态配置来进行代码优化工作的工具,复杂的代码往往会使这些工具陷于混乱,结果他们要么提供不太理想的优化,要么完全没有进行优化。

我们可以通过一个编写良好的微性能基准来了解这一点。大部分编写良好的微基准代码都会导致JIT的困惑,以致JIT不能把我们的代码转换成不再丈量我们感兴趣的效果的东西,虽然微基准有可能是一种非常态的情况,但是同样的这些情况有可能会发生在实际应用的代码中。

另一个编写傻瓜代码的理由是,大部分的复杂性出于每个人都认为是需要的那些优化。在许多情况下,这些优化是不成熟的,虽然我非常赞成做性能计划,但我坚决反对不成熟的优化,计划何时只是一个计划?何时还不算成熟?我猜这有点类似于是艺术与色 情之间的区别:你看到时自然就会知道。

这就带来了我们的第二个问题:结构良好的代码如何有助于性能?大部分的性能问题只能通过添加或者修改应用中的代码来解决,我发现,如果代码结构良好且是低耦合的,如果代码是内聚的,且代码用到了代理的话,那么在开始修改代码的时候就能够避免这种打地鼠(whack-a-mole)问题。

这一问题也被称作是霰弹式重构(shotgun refactoring)——如果在应用的某一部分中做了修改的话,应用中看起来很随机的其他部分就会遭到破坏,但当我修补了某个破损之处时,我又创建了一系列新的破坏,如此等等。

那么我们怎样才能避免此种情况呢?首先,遵循DRY——不要重复自己——这一原则,让我们来看一下集合的一个例子,传统上,我们在Java中会通过创建一个迭代器来管理一个对集合的查询:

....
Iterator iter = customers.iterator();
while (iter.hasNext()) {
    Customer c = (Customer)iter.next();
    doStuff( c);
}
....

 

 

 

这儿有一个陷阱:如果应用的另一个部分需要用到doStuff()的话,那么就有可能或是通过剪切复制或是通过简单地重写来重复这部分代码,无论使用哪种方式,你都违反了DRY原则,这会引发几种后果。你还忽略了另一个设计原则:委托,不承担(责任)。

在不做委托的情况下,你会冒着违反DRY原则的风险,且你一定会违反信息隐藏原则。从这方面来考虑一下:通过调用get方法,你不仅违反了封装,而且把你的类和被调用者紧密地耦合到了一起。当你因导出状态而违反了封装时,你就被迫还要导出需要用来管理状态的应用逻辑,结果又违反了DRY原则,因此你可以看到,就从多个不同的角度来说,这种做法都是错的。

这是一个很重要的性能方面的技巧:假定正在使用的数据结构是不太理想的,并且假设你意识到需要做出修改,如果你使用迭代器或者别的什么来导出状态和行为的话,你就已经造出了一个打地鼠问题。让我们来看看当我们采用委托时情况会怎样:

public class Customers {

    Hashmap customers = new Hashmap();

    public void putCustomer( Customer customer) {
        allCustomers.put( customer.getId(), customer);
    }

    public Customer getCustomer( String id) {
        allCustomers.get( id);
   }

   public Customers getCustomers( String pattern) {
        return doSomeStufftoGetACollectionOfCustomer( pattern);
    }

 

 

 

这里的代码量会多些,不过在一个较小的人为例子中说明问题时这是经常会发生的情况,你只会见到大的代码库的好处,他们提供的例子当然也是会吓坏人的。

除此之外,我们还有一个用来封装预期集合的类,新类在语义上要适合我们的领域,有多少问题领域是包含了Hashmap这个词,以及有多少领域是包含了Customers了的?因此我们已经对词汇表做了修正。

接下来,我们为查询提供一个落脚点,至少是那些我们已预期到的查询。如果我们取得其他的查询模式的话,那么就有可能加入一个有不同键值的二级集合,可把它看成是在数据库表上添加另一个索引。

这一做法的美妙之处在于,因为我已经封装并委派了调用,于是我可以自由地做出我的客户可能知道也可能不知道的修改而不受限制,换言之,不用打地鼠。此外,每个客户都会感受到优化带来的性能好处,因此这是一个多赢的做法。

我有意地不使用泛型,使用这种开发模式的话,我就不需要编译时的集合类型检查,因为类API提供了所需的所有安全性。我一直认为,在API中暴露原始的集合是个错误,且因为我从来都不觉得泛型的那些主要的使用场景是合理的,不过这是另一个话题了。

那么,如果有人需要我们并未提供的查询话,该怎么办呢?闭包看起来像是一个好的解决办法,但需要非常小心地实现他们,否则的话我们可能会把自己“闭包”到某个打地鼠问题中了。

闭包应该只访问那些被归类为非基本的元素,在这其中我指的是那些无需了解底层结构就能够工作的元素,这是相对于原始方法或是需要了解类的底层数据结构的方法而言的,关键之处是要隐藏我们领域之外的事务的实现细节。

总之,性能调优通常要求涉及代码,这与重构并没有太多不同。敏捷开发那帮人提出的所有论点——松散耦合、简单代码、遵循好的设计模式、制定好的单元测试、自动构建等等——同样适用于性能调优。

 

查阅对Kirk Pepperdine所做的完整访谈。

 

其他参考

 

Brian Goetz访谈博客

Heinz Kabutz访谈Java Specialists’ Newsletter

Cay Horstmann访谈博客

Kirk Pepperdine访谈博客

Java Champion

顶级Java开发者给学生提出的建议

GoogleJoshua Bloch谈更加有效的Java

 

讨论

 

你有关于如何编写更好的代码的建议吗?特别是,你会不同意编写傻瓜代码这一通常建议吗?我们欢迎所有的意见,且特别鼓励不同意本文观点的开发者发表你的不同见解。欢迎你加入到我们的社区中来,请保证评论的礼貌性和相关性,你可以选择提供电子邮件地址以获得对你的答复的通知——你的个人信息不会做其他用途。在提交评论时,你要同意这些使用条款

 

你可能感兴趣的:(java,设计模式,数据结构,网络应用,敏捷开发)