`
mondayw
  • 浏览: 143770 次
  • 性别: Icon_minigender_2
  • 来自: 广州
社区版块
存档分类
最新评论

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

    博客分类:
  • 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/

 

历年来,开发者总是会讨论他们最喜欢的代码、最有趣的代码、最酷的代码,以及如何编写代码,如何避免编写代码,编写好代码的障碍,与编写代码相关的爱恨情仇,以及编写代码的过程等等,他们给出了很多值得我们铭记在心的真知灼见。

在这一由多篇文章组成的系列的第一部分中,我以一个稍微常见的建议作为开始:编写傻瓜代码。

 

目录

 

-           Brian Goetz:编写傻瓜式代码

-           Heinz Kabutz:通过使用好的面向对象的设计模式来界定“优质”的做法

-           Cay Horstmann:模式并非魔法药水

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

-           其他参考

-           讨论

 

Brian Goetz:编写傻瓜式代码

 



关于
Brian Goetz

Brian GoetzSun Microsystems的一位技术传道者,自2000年以来,已经发表了75篇关于最佳实践、平台内核及并发编程方面的文章,他是Java Concurrency in Pracitce一书的主要作者,该书入围2006年的Jolt大奖,是2006年度的JavaOne会议上的最畅销书。在20068月加盟Sun之前,他作为其软件公司Quiotix的一位顾问工作了十五年,除了进行Java技术方面的写作之外,他还经常在各种会议上发言,就线程、Java编程语言的内存模型、垃圾收集、Java技术的性能神话以及其他的一些议题进行演讲。

此外,他也在内核、设备驱动程序、协议实现、编译器、服务器应用、web应用、科学计算、数据可视化以及企业基础设施工具等方面提供咨询。Goetz参加了几个开源项目,其中包括Lucene的全文搜索和检索系统,以及FindBugs的静态分析工具包。

Sun公司,作为一个顾问,对那些从Java并发到Java开发者需求中拓展出来的各种各样涉猎广泛的主题,他都提供咨询,为Java平台的开发贡献他的力量。

 

开发者如何才能编写出运行良好的代码呢?

 

答案似乎是违反直觉的,通常,编写Java应用的快速代码的方法是编写傻瓜代码——简单、干净并且遵循了最明显的面向对象准则的代码,这需要配合动态编译器的特性,他们是大的模式匹配引擎。因为编译器是由那些受着进度和时间预算限制的人来编写的,因此编译器的开发者把他们的精力集中在最常见的代码模式上,因为在这些地方他们的工作能得到最大体现。因此如果你用简单的面向对象准则来编写代码的话,那么比起编写那些粗糙的、看起来很聪明但编译器却无法有效优化的破解(hacked-up)代码或者位拆列(bit-banging)代码来说,你能够获得更好的编译器优化。

因此,正好与用C语言做开发所教给我们的相反,干净的、傻瓜式的代码通常都会比真正聪明的代码运行得更快一些。在C语言中,聪明的源代码可转换成预期的机器代码级别的专有语句,但在Java应用中它不能以这种方式来工作。我的意思并不是说,Java编译器过于愚蠢,不能把聪明的代码转换成相应的机器代码,实际上,其对Java代码的有效优化甚于C编译器所能做的。

我的建议是:编写简单直接的代码,然后,如果性能还是没有“足够好”的话,就进行优化。不过“足够好”这一概念背后所隐含的意思是你必需有明确的性能指标,没有这些指标,你将永远都不会知道何时应该进行优化。你手边还必须有一个实际的、可重复进行的测试程序来确定这些指标是否被满足,一旦你能够在实际的操作情况下检测程序的性能,那么就可以开始进行调整了,因为这样你会知道调整是否有所帮助。但是,如果只是做假设:“哎呀,我想如果修改这个地方,它会运行得更快,”通常这在Java编程中会适得其反。

因为Java代码是动态编译的,所以现实的测试条件是至关重要的,如果你从上下文中拿出一个类,那么对它的编译将不同于其在应用中的情况,这意味着性能必须是在现实的条件下进行衡量的。因此,性能指标应该与有商业价值的指数挂钩——每秒钟事务数、平均服务时间、最坏情况下的延迟——这些你的客户会感知到的因素。在微观水平上关注性能特征通常会产生误导并难以进行测试,因为很难为一些脱离了上下文环境的小代码块制作出符合实际情况的测试用例。

 

2003年的时候你曾说过:“开发者喜欢优化代码,并且有着充分的理由,这是如此的令人满足和充满乐趣。不过,知道何时进行优化才更为重要,不幸的是,就哪方面才真正是应用的性能问题这一情况来说,开发者一般都有着糟糕的直觉。”你现在还是这样认为吗?

 

与四年之前比起来,这一说法现在更为确实,且相对于C开发者来说,与Java开发者的情况更吻合。大部分情况下的性能调整都会让我想起一个关于在厨房中找钥匙的家伙的老笑话,虽然钥匙是被丢在大街上了,但是厨房的光线更好一些。我们是如此密切地熟知自己编写的代码,我们所依赖的外部服务,无论是库还是外部代理,诸如数据库和web service等,都在我们的意识和视线之外,因此,当遇到某个性能问题时,我们倾向于在自己的代码中考虑我们能想象的性能问题出现的地方,但通常情况下,那并不是性能问题的根源所在——问题出在应用架构的其他方面。

现如今,大部分的性能问题都是架构导致的后果,而非编码——做了太多的数据库调用或者是无数次序列化每一样东西到XML然后反序列化,这些代码通常每天都在你编写和看到的代码之外运行着,不过他们才是性能问题的真正根源所在。因此,如果只是在你熟悉的地方寻找的话,那么你就是正在厨房中找钥匙,这是一个开发者总是会犯的错误,应用越是复杂,性能越是取决于不是你编写的代码,因此,问题越有可能是处于你的代码之外。

越是简单的地方,Java编程中的性能分析就越难于C,因为C具有与汇编语言相当的相似性,从C代码到机器代码的映射是相当直接的,在其不能表达的地方,编译器能直接显示机器代码。Java应用与C不一样,运行时会不断基于变化的条件和观察所得修改代码,其在开始时会解释代码并编译之,但可能会根据配置数据或者载入的其他来类来使已编译的代码失效,然后再重新编译它。结果,你的代码的性能特征会极大戏剧化地依赖于代码所运行的环境,这使得很难这样说:“这一代码快于那一代码”,因为你需要考虑更多的上下文环境以便做出更合理的性能分析。另外,还存在着一些诸如时机、编译特性、载入类的交互以及垃圾收集等一类的非确定性因素,因此,相对于C来说,更难于对Java代码进行这种微性能优化。

同时,编译在执行时完成这一事实意味着,与C编译器相比,优化器有着更多可用于工作的信息,其知道哪些类已被载入,以及已经编译的方法实际上是如何被使用的,因此,相对于一个静态的编译器来说,其可以做出更好的优化决策,这对于性能来说是好事,但意味着更难于预测给定代码块的性能。

 

查阅对Brian Goetz所做的完整访谈。

 

Heinz Kabutz:通过使用好的面向对象的设计模式来界定“优质”的做法

 



关于
Heinz Kabutz

荣获Java Champion称号的Heinz KabutzSouth AfricaCape Town长大,其在初中时,就爱在一台ZX Spectrum计算机上捣鼓,培养出了对编程的兴趣。他获得了Cape Town大学的B.S.学位,并在25岁时获得了Ph.D.学位,两个学位都是属于计算机科学方面的。1998年,他开始成立了自己的软件开发公司Java Specialists,编写合同软件、接受咨询并提供Java技术和设计模式方面的课程。

Kebutz作为免费的Java Specialists’ Newsletter的创建者而为人所知,其目标是帮助Java开发者更加专业化。

 

我询问Kabutz如何看待Brian Goetz的关于编写“傻瓜式”代码的这一建议。

 

就我的经验来说,好的面向对象设计会产生更快且更易于维护的Java代码。但什么是好的代码呢?我发现通过使用好的面向对象的设计模式来界定“优质”会更容易一些,我通常都鼓励软件开发公司在设计模式方面培训他们的所有的开发者,包括从最初级的人员到最聪明的架构师。

采用了好的设计模式的团队会发现调整代码的工作会更容易一些,代码会不那么脆弱而且需要更少的复制与粘贴操作。Java.util.Arrays就是糟糕代码的一个很好的例子,其包含了两个mergeSort(Object[])方法,一个用到了Comparator,而另一个则用 Comparable,这两个方法几乎是一样的,可以通过引入一个使用Comparable方法的DefaultComparator来把它们合并成一个,这一策略模式(strategy pattern)能够避免这样的设计缺陷。

通过Ctrl-CCtrl-V来编码还有可能会隐藏了性能问题,假设你有几个算法,几乎都相同,但位于系统的不同地方,

如果衡量性能的话,你可能会发现每个算法占用了CPU5%,不过如果把它们加在一起的话,那几乎就是20%,好的设计让你能够更轻松地修改代码并检测到瓶颈所在,让我们举个例子来证明Brian Goetz的观点。

Java编程的早期,我有时会求助于“聪明的”代码,例如,我在优化德国一家公司编写的一个系统时,在已经优化了系统的架构和设计之后,我想对事情做一些改善,于是我修改了String这一类型的使用,使用StringBuffer类型作为代替,不要去读太多的微基准评测(microbenchmark),性能提升来自于好的设计和适当的架构。

我们以一个基于+=操作的基本的串联接为开始:

 

public static String concat1(String s1, String s2, String s3,
                               String s4, String s5, String s6) {
    String result = "";
    result += s1;
    result += s2;
    result += s3;
    result += s4;
    result += s5;
    result += s6;

    return result;
  }

 

 

String是不可变的,因此编译后的代码会创建出许多的中间String对象,这些对象会使垃圾收集器工作负荷加大,一种常见的补救办法是引用StringBuffer,使得代码看起来像这个样子:

 

public static String concat2(String s1, String s2, String s3,
                             String s4, String s5, String s6) {
    StringBuffer result = new StringBuffer();
    result.append(s1);
    result.append(s2);
    result.append(s3);
    result.append(s4);
    result.append(s5);
    result.append(s6);
    return result.toString();
  }

 

 

不过代码会变得不那么易读,在这一方面是不太令人满意的。

使用JDK 6.0_02和服务器HotSpot的编译器,我可以在2010毫秒之内执行concat1()一百万次,但执行concat2()相同的次数只需要734毫秒,就这一点来说,我很高兴自己让代码的运行速度加快了三倍,不过,如果只是0.1%的程序的速度变得加快三倍的话,用户是不会注意到这一点的。

追溯回到JDK1.3的年代,我还有第三个用来使自己的代码运行得更快的方法,作为创建一个空的StringBuffer的替代,我让变量的大小符合所需的字符的个数,像这样:

 

  public static String concat3(String s1, String s2, String s3,
                               String s4, String s5, String s6) {
    return new StringBuffer(
        s1.length() + s2.length() + s3.length() + s4.length() +
            s5.length() + s6.length()).append(s1).append(s2).
        append(s3).append(s4).append(s5).append(s6).toString();
  }	

 

我设法使得在604毫秒之内调用该方法一百万次,甚至快过concat2(),不过,这是最好的添加串的方法吗?而什么样的方法才是最简单的呢?

 

Concat4()中的方法给出了另一种方式:

 

  public static String concat4(String s1, String s2, String s3,
                               String s4, String s5, String s6) {
    return s1 + s2 + s3 + s4 + s5 + s6;
  }

 

 

几乎不可能有比这更简单的做法了,有趣的是,在Java SE 6中,我可以在578毫秒之内调用这段代码一百万次,这甚至比远更复杂的concat3()好得多。与我们之前最好的努力结果比起来,这个方法更清晰、更易懂且速度更快。

SunJ2SE 5.0中引入了StringBuilder,除了不是线程安全的这一点之外,其几乎与StringBuffer相同,对于StringBuffer来说,线程安全通常是不必要的,因为其很少在线程之间共享。在使用+操作符来添加串时,J2SE 5.0Java SE 6的编译器会自动地使用StringBUilder,如果StringBuffer已是硬编码了的话,则这一优化将不会发生。

当应用中的一个时间关键的方法引发了明显的瓶颈时,这样做有可能会提高串连接的速度:

 

  public static String concat5(String s1, String s2, String s3,
                               String s4, String s5, String s6) {
    return new StringBuilder(
      s1.length() + s2.length() + s3.length() + s4.length() +
          s5.length() + s6.length()).append(s1).append(s2).
        append(s3).append(s4).append(s5).append(s6).toString();
  }

 

 

不过,这样的做法会妨碍Java平台的未来版本对系统的自动提速,同样,其使得代码更难于读懂。

 

查阅对Heinz Kabutz所做的完整访谈。

 

 

  • 大小: 14 KB
  • 大小: 10.4 KB
分享到:
评论

相关推荐

    Nutch,第1部分:爬行(译文)

    ( Nutch,第1部分:爬行(译文) ( Nutch,第1部分:爬行(译文)

    java及web中英对照译文

    JavaServer Pages(JSP)是Java技术领域中用于构建动态网页的一种标准技术,由Sun Microsystems公司发起并由多家公司共同参与开发。JSP类似于微软的ASP技术,它允许开发者在HTML或XML文档中嵌入Java代码片段...

    译文:Fork and Join: Java Can Excel at Painless Parallel Programming Too!

    Java平台上的并发编程一直是一个重要的话题,特别是在多核处理器普及的今天。Fork/Join框架是Java SE 7引入的一项重要技术,它使得编写高效、并行的程序变得更加容易。本文将简要回顾Java中的并发编程基础知识,介绍...

    探讨计算机软件开发的JAVA 编程语言应用.doc,原文+译文。

    Java的虚拟机(JVM)允许编译后的Java代码在任何支持Java的设备上运行,这极大地推动了其在各种领域的应用,包括桌面应用、企业级应用、移动应用(尤其是Android系统)以及云计算等。 在软件开发中,Java语言提供了...

    有关java,jsp类论文可用的英文论文及中文译文

    它的设计目标是“一次编写,到处运行”(Write Once, Run Anywhere),这意味着用Java编写的代码可以在任何支持Java的平台上运行,无需重新编译。Java的特点包括垃圾回收机制、自动内存管理、丰富的类库和强大的网络...

    java毕业设计——基于J2EE的B2C电子商务系统开发

    提供的文件列表中,"答辩.ppt"可能是项目演示的PPT,"Doc1.doc"可能是文档的一部分,"开题报告.doc"记录了项目的初衷和计划,"译文.doc"可能是对相关论文的翻译,"文献综述.doc"总结了相关领域的研究,"毕业设计任务...

    JAVA英语单词.pdf

    第一章:基本概念 * public:公共的、公用的 * static:静的、静态的、静止的 * void:空的 * main:主要的、重要的 * class:类 * system:系统、方法 * out:出现、出外 * print:打印 * eclipse:Java编程软件 ...

    Java 并发核心编程原文+译文

    **并发工具类**:Java 5引入了`java.util.concurrent`包,包含许多并发工具类,如`ExecutorService`、`Future`、`Semaphore`、`CountDownLatch`和`CyclicBarrier`等,它们提供了线程池、任务提交、同步控制等多种...

    SDN开发:Floodlight开发文档的译文

    Floodlight开发文档的译文是一个非常重要的资源,对于想要深入了解SDN技术和Floodlight控制器的开发者来说,它提供了详细的开发指南和实践经验。通过该实例,开发者可以直接在Mininet+Floodlight中对SDN的控制器进行...

    译文:驱动开发之六:介绍显示驱动(含Mirror Driver介绍)

    此函数的第一个参数实际上是传入DriverEntry函数的`pDriverObject`参数(即驱动对象),第二个参数通常是传递给DriverEntry的第二个参数`RegistryPath`。 在迷你小端口驱动中,除了使用以`VideoPort`开头的API之外...

    wxPython in Action译文中文.pdf

    如果你是wxPython初学者,你一定会想从第一部分开始。第一章至第三章帮助您夯实 wxPython相关概念的坚实基础。第六章则对构建合理大小程序的步骤进行了完整回顾。第五章介绍如何让代码更易于管理的方法,第四章提供...

    Floodlight开发者文档(译文)

    Floodlight开发者文档(译文) Floodlight不仅仅是一个支持OpenFLow协议的控制器(FloodlightCOntroller),也是一个基于Floodlight控制器的应用集。 当用户在OpenFLow网络上运行各种应用程序的时候,Floodlight控制器...

    重构:改善既有代码的设计

    通过学习《重构:改善既有代码的设计》,开发者可以掌握一系列实用的重构技巧,提高代码质量,进而提升整个项目的质量和开发团队的生产力。无论是在团队协作还是个人项目中,这本书都是不可或缺的参考资料。

    基于JAVA的蓝牙无线技术研究.doc,原文+译文。

    在本篇论文“基于JAVA的蓝牙无线技术研究”中,作者探讨了无线通信领域中的一个重要分支——蓝牙技术,以及如何利用Java为移动设备构建蓝牙应用。这篇毕业设计的外文原文与译文提供了深入理解这一主题的基础。 1.1 ...

    深度学习三维重建 PVSNet-2020 (原文+译文)

    深度学习三维重建 PVSNet——2020 (原文+译文) 深度学习三维重建 PVSNet——2020 (原文+译文) 深度学习三维重建 PVSNet——2020 (原文+译文) 深度学习三维重建 PVSNet——2020 (原文+译文) 深度学习三维重建...

    JAVA在线考试管理系统(源代码+论文+开题报告+外文翻译+英文文献+答辩PPT).rar

    Java在线考试管理系统是一款基于Java技术构建的教育信息化软件,它为教师、学生和管理员提供了一种便捷的方式来组织、实施和评估在线考试。本系统涵盖了考试的全过程,包括试题库管理、考试安排、考生答题、自动评分...

Global site tag (gtag.js) - Google Analytics