怎样写更优秀的面向对象的代码 How to write better OO codes

1.    目的

         整理此文档的目的旨在建议和帮助团队提高面向对象编码和设计水平。在美国的这段时间使我有机会同时见到接下来提到的他们并能跟他们请教学习,他们都富有优秀的经验和乐于助人。从他们那里不仅能学到知识,而且能感受他们的优美的人生观。我仅仅把他们的原始建议翻译一下,供大家学习参考。

2.    代码建议

2.1. Andrew Harrison

 

这问题问的很好。我的编程必杀技是《程序员修炼之道》。本书充满了经典和智慧。我第一次读它已是十年前的事了,到现在还经常翻阅研究。

像其他经典之作一样,它总暗喻日常编程中的各种问题,但又凌驾于它们。

然后,我觉得以下做法很实用---尝试定义和归类你的日常问题,把这些问题归类到更大的模型/问题下。如:生产力、内存、并发、繁多的java类、推拉数据等。这时你可以借助团队成员的经验,就能很轻松的解决它们,并积累又一个经验。当然记住,要共享。

我常用的做法是:

在设计中总是思考某个事物/模块需要知道多少。它知道的越少,依赖的就越少;然后你的世界就越灿烂。因为你可以轻松的切换、增加和减少模块。这个道理在方法的级别已被证实,对于组件和系统级别也同样适用。

 

考虑一个事物/模块(如:方法、组件、系统)做什么。努力尝试让它只做一件事这样你就能更容易的创造出朴素简单的代码。

这里我们说的方法、组件和系统的唯一区别就是抽象级别。举个例子,“一个事物由什么组成?”这个问题将特别难回答。

接下来我们谈另外一个问题---封装。

现在你已经做好了很多“do”模块(或类或方法),然后你需要把他们联合起来组装成一个更大的模块。这里面可能出现如:一个方法调用其他几个方法;一个组件使用另外几个组件;一个系统协调不同的组件完成一个很不可思议的工作。

完全正确的做到封装是很难的,因为小模块调用有可能跟整个调用流程矛盾对立,使得你在封装和流程上难以取舍。但是如果较小的模块被很好地封装的话,那你的世界将又变的灿烂了起来,而且你还可以依照这个准则继续前进。

这里我们要再次提到抽象,最需要解决问题的是我们需要抽象几层。特别提示: “请注意继承”。因为继承可以打破封装,我建议最好选择“适配器”的方式。

另外一本牛逼的书是Joshua Bloch的《EssentialJava》。这本书已经有了基于1.5+的新版《Effective Java》。虽然我没有读新版(增加了怎样扩展枚举类型的章节),但是就算是旧版的都已经足够好了。

事实上Block的书里面给出了一个Java核心包中失败的继承设计的例子:- java.util.Properties 继承java.util.Hashtable。这着实很不好,因为即使是一个Properties对象被定义为只能包含字符串,你仍可以给它添加对象Object(就是因为他继承了Hashtable)。一个稍微好点的方案是:让Properties包含一个Hashtable。这样让Properties自己控制基于字符串的存储和访问接口。这比使用继承好多了,因为它不会把原有父类的某些接口不必要的暴漏出去。

最后,读Java四人帮的《设计模式》这本书那也是必须的。

希望这些能帮助你们--呵呵,如果给我机会我还能说几个小时!

2.2. Wenzi Li

 我完全同意Andrew所说的这些方法和思路。总结如下:

1.      确保每个功能都有相应的测试或单元测试。

2.      永远保持一个方法短而简,一般准则是一个方法不要超过20行。

3.      要常做重构,以使代码复用性提高

4.      总是首选接口而不是类,这样我们才能轻松测试和使用不同的实现。

5.      把逻辑封装到具体的类实现里或者使用另外的方法。

6.      适度的使用Spring的代码注入。

7.      异常应该被重新抛出以确保内部的异常不会丢失[i]

8.      松耦合商业逻辑,把工程分成更小的模块。

9.      避免冗余,本条要求适当的时候对于相同逻辑的代码进行重构和复用。

10.  有目的的写代码,不要留下无用代码。

2.3. Shumin Liu

Andrew 和 文治已经概括了基本上所有的面向对象的武林秘籍。我想谈一谈集成和互操作。我将使用我们的一些产品作为例子,如果有不恰当的地方还请指正。在最后还有一些关于日志的想法。所以这封邮件事实上不全是“怎样写更优秀的面向对象代码”。如果需要我们可以在讨论。

SOA[ii]的问世已经快30年了(1993提出)。Webservice是个大家都想跳上去的舒服小马车[iii],因为Webservice提供了非常优越的平台--独立的互操作性。很多人认为Webservice就是SOA。我曾遇到过一个资深的Oracle架构师,按照他的说法:“所有的东西都应该实现成异步的Webservice, 那让后你的的应用就自动变成SOA的了”。我非常不同意他的观点。

·        Webservice是个规范,而SOA是个概念架构。Webservice实现了SOA中定义的互操作和服务黑箱性[iv]

·        异步调用有一些优点如:你可以保证服务方一定会收到请求信息(在调用一个服务前,保存请求信息),重试回调,等。但是同时需要付出时间上的代价。所以最后的决定主要取决于商业需求。我见到过很多实现把Webservice调用全部做成了异步的,就只是因为Oracle所说的(而且很多SOA的实现只是为了做SOA而做SOA)。

·        SOA概念把服务分成了3类:实体服务,商业过程服务和公用服务。这和面向对象的思路是一致的(Andrew也有提到):一个工作单元是什么,我们应该怎样为它构造对象?大体上说,这3个分类给我们粗略的描绘了“服务层级”这个概念:实体服务,有点像J2EE中的实体Bean[v];商业过程服务则是一个完整的商业逻辑或者可以说是一个协调各种资源的商业处理过程;最后公用服务则是一般的常用的工具服务集合,如一些导入导出工具或接口。

o  以下引用Andrew的原话:“考虑一个事物/模块(如:方法、组件、系统)做什么。努力尝试让它只做一件事这样你就能更容易的创造出朴素简单的代码。这里我们说的方法、组件和系统的唯一区别就是抽象级别。举个例子,“一个事物有什么组成?”这个问题将特别难回答。”

当我们需要处理多个数据源(尤其是有时要支持一些未知的数据源)和/或多重终端的时候,请尝试使用“星状枢纽模型”的架构设计而,而不要使用点对点的方案。让我们来举一个大家都很熟悉的例子:PIX Manager。我们现在的工程有V2的处理代码,也有V3的处理代码,它们的代码和逻辑的实现都是独立的。我们做个假设,如果有一天出现了新的消息标准V4,那我们将必须重新写一套V4的实现,而且现有的代码和逻辑很少能被用上。这不是我们所鼓励的。我建议我们最好是使用一套通用的标准数据模型,可能包含表示病人信息、数据与后台交换的过程和一些公用的商业逻辑和服务。这样,当这个通用的部分完成后并经过测试,他们就成为了固定下来的可以复用的公用逻辑。仔细想想这不是SOA所要做的吗?那之后,在未来的实现中我们就只需要把新的数据源、数据格式转换成已有的数据定义使用已有的处理逻辑和后台交互。这是不是很有条理、很轻松?事实上这种理念或设计可以用于很多情况。再想想NHIN和IHE,其实他们就是通用的标准数据模型的提供者。

下面我们来谈谈日常开发:

·        以上这个理念不仅适用于较高层次的设计,同样也适用于代码模型设计。如尝试封装好一个优秀的或经典的操作或过程,测试保持其稳定和独立;然后复用它而不是重新造车。

·        通常来说,我们需要一个如下的序列或处理过程:接收层 -> 数据转换层 -> 数据持久层 -> 后台(如数据库、文件、网络云等)。我想我们的XDS已经运用了这种设计思路。只是我们XDS现在接收到的输入都已经是”通用的标准书模型“了。因为大家都是按照同样的IHE标准、使用同样的schema。所以大家或许感觉不到通用设计的特点和它能给我们带来的好处。不过我还是想在这个问题上举2个例子。如果有一天产生了新的标准版本(它重新定义或添加了数据格式),我们的XDS系统需要既需要兼容新版有需要兼容旧的版本,这样一个数据转换层的需求就变成了迫不及待的事情。另外、我们正在做的Auroin Adapter (双妮肯定很清楚)事实上就是为了兼容和转换不同的消息格式而做的数据转换层。

以下是关于日志的一些建议:

·        日志标识和关联信息。当前,当我们的系统并发处理多个不同请求时,日志信息就会变得非常杂乱。很难分清哪个线程/日志属于哪个请求。这导致我们很难区分错误和来源。我在Connectathon测试过程中就看到Forcare公司的做法就很好,他们有清晰的线程、事务标识、请求信息,你能很轻松的找到不同的请求者发来的不同请求和相应的处理返回。这是个很好的参照。我的建议也大致相同,在记录日志的时候给每个请求加一个事务标识,然后所有的与那个请求相关的日志都是用同一个标识。ATNA 日志在这方面已经做了很多工作;但仍有不足的是一旦通用异常抛出后,没有上下文信息。我们很难看出是到底是哪个逻辑或过程出了问题。

·        序列化问题。在我以前的工作中,我曾使用过这样一个软件webMethods Integration Server(softwareag.com),而且近十年之久。它有两个特别有用的工具:SavePipeline 和 RestorePipeline。我多次因他们而得益。它们做的工作很简单,就是把所有的变量和内存中的状态值存到XML文件。这在系统出错的时候特别有用:你可以直接找到出错的模块,恢复管道和各种入参信息,重新直接调试而不需要重复之前的步骤。看起来这有点像断点恢复一样。这对于网页程序更有用处,做过网页程序的人都应该清楚,因为有时你很难重现错误。当然了,你不能啥都不干就达到这样的神奇功效。最起码的数据序列化和反序列化是实现这个功能的必须和前提。

·        较低层的异常处理。对于应该较低层出现的异常,它们没有上下文或更多的原因信息,应该抛出往更高层。当到达某个可以较准确定义其错误原因的层级再做异常和逻辑处理。这时就可以提示更加有意义的日志信息。一个简单的SQLException、NullPointerException将很难说明程序中那个逻辑或流程出了问题。最后对于记录异常时,如果能把目标的参数做一个序列化那将是非常有利于问题排查的!

 

2.4. Marc Lottman

我不知道我是不是最合适给此类指导的人,但是我可以提供一些小建议:

1.      请确保阅读《Effective Java》,它有很多货真价实的东西在里面。

2.      反复阅读Carefx 代码标准,他的位置在http://dev.carefx.local/wiki/index.php/Java_Programming_Practices。

3.      确保你的注释有意义。如果你觉得将要添加的注意不能带来更多的信息,请不要添加。

4.      不要让你的方法体太长。一个500行的方法是非常糟糕和容易出问题的。考虑重新组织你的代码。

5.      永远不要害怕重构代码。我自己常做彻底的代码重构,以使它们到达最理想的状态。

6.      当你的程序遇到了问题,想一想你能不能通过配置Carefx日志的方式找到问题的根源。如果不能请一步一步做到那样。

 ===================================================================================================================================

1.1. 建议原文

1.1.1.            Andrew Harrison

These raise interesting issues. Mycoding bible is 'The Pragmatic Programmer':

http://pragprog.com/the-pragmatic-programmer.

It is full of wisdom; I turn to itagain and again and first read it ten years ago. Like any book of wisdom, itinspires (crucial) but is sometimes hard to relate to day to day problems. Inthe end I believe that is the trick - to try to define your day to day problemin archetypal terms - as a part of a greater pattern or problem (throughput,memory, concurrency, too many classes, push or pull of data etc) - then you cansolve it because you can draw on the experience of all developers who haveshared their experience.

 

My simple mantras would be:

Always think about how much somethingneeds to know. The less something needs to know, the less it relies upon, theeasier your life will be because you can swap things in and out. This is trueat the method level, as well as component and system levels.

Think about what a thing (method,component, system) does. Try to have it only ever do one thing - that way youare more likely to achieve simplicity. The only difference here betweenmethods, components and systems is the level of abstraction, i.e. whatconstitutes 'one thing'. That can be quite hard.

As a result of these two things -basically encapsulation - you end up with lots of bits that do stuff. Then youneed to combine those into something bigger - a method that calls other methods,a component that uses other components, a system that does cool stuff and useslots of components. Doing this right can be hard because the smaller bits mayhave contradictory concepts of process flow etc. But if the smaller bits arewell encapsulated, then your life is easier and you can follow the same rules.The main thing is to work out the levels of abstraction again.

One specific thing I would say is"beware inheritances". Inheritance can break encapsulation. Preferadapters instead.

Another great book is Joshua Bloch's"Essential Java". He has a newer version out which deals with Java1.5+ but I have not read this (apart form the chapter about how to extendsenums). But even the older version is great. Actually, Bloch's book has a greatexample of bad inheritance design in core Java - java.util.Properties extendingjava.util.Hashtable. This is bad because you can actually add a Objects toProperties (because it inherits from Hashtable), although the contract ofProperties is Strings. A much better approach would have been for Properties tocontain a HashTable and control access and storage to it via its own stringbased interface, rather than inheritance which opens your interface toeverything in the super class.

And of course everyone should read theGang of Four's "Design patterns : elements of reusable object-orientedsoftware " :-)

I hope this helps - I could go on forhours :-)

1.1.2.            Wenzi Li

I agree with these approaches. In summary, we should have these best practices:

1.         Make sure we write a test code for allfunctionality.

2.         Keep a method simple and short, i.e. each method isnever more than 20 lines.  

3.         Always do refactoring to reuse the code.

4.         Try best to use interface instead of class, so thatdifferent implementations can be plugged in.

5.         Encapsulate logics in concrete class implementationor delegate the logics to other methods.

6.         Leverage Spring style of code injection.

7.         Exception should be re-thrown so that the innerException won’t be lost.

8.         Divide the project into small modules to decouplebusiness logics.

 

In addition, here are the 5 usefulprinciples to writer effective OO code: http://www.ehow.com/how_2001455_write-effective-object-oriented-code.html

 

There is also an excellent javaprogramming book “Effective Java” written by Josh Bloch.  I suggest Willpurchase one copy for the China team.  The book review is here: http://book.douban.com/review/3307178/

 

And the book can be purchased fromAmazon China: http://www.amazon.cn/mn/detailApp/ref=asc_df_0321356683359404/?asin=0321356683&tag=douban-23&creative=2384&creativeASIN=0321356683&linkCode=asn

 

1.1.3.            Marc Lottman

I’m not sure that I’m the best to givea lesson on this type of thing, but I can provide a few tips:

1.           Make sure youread “Effective Java” by Joshua Bloch.  It has a lot of really goodinformation.

2.           Reviewthe Carefx coding standards: 
http://dev.carefx.local/wiki/index.php/Java_Programming_Practices

3.           Makesure your code comments are meaningful.  Don’t put in a comment if itdoesn’t add any value.

4.           Don’tmake your method bodies too long.  A method that is 500 lines is reallytoo long.  Consider restructuring your code.

5.           Don’tbe afraid to refactor the code.  I usually end up doing quite a bit ofrefactoring before I get the code into an optimal state.

6.           Whenyou run into problems, think about whether your could figure them out just byturning on Carefx logging.  If you can’t, then add additional logstatements to the code. 

Thanks,

Marc

 

1.1.4.            Shumin Liu

Andrew and Wenzhi already had great inputon writing OO. I would like to provide some comments from the perspectives ofintegration and interoperability. I will use some examples of our products,please correct it if my quote is not accurate. I also added some of my thoughtsabout logging in the end. So this email is really not all about “How to writebetter OO codes”. We can discuss if you want.

 

-         SOA has been the buzz-word fora while. Webservice is the bench wagon everyone wants to jump on. Sincewebservice provides great platform-independent interoperability, many peoplethink webservice == SOA. I met an Oracle senior architect before. According tohim, “Everything should be implemented as asynchronous webservice, then you areSOA-enabled”. I strongly disagree with him.

o  Webservice is a specificationand SOA is a conceptual architecture. Webservice implemented the concepts ofinteroperability and service agnostic  of SOA;

o  Asynchronous calls have someadvantage: you can incorporate guaranteed delivery (save the message beforeinvoking a service), call-back retry, etc. The price is performance. So thedecision is based on the business need. I’ve seen many implementations doingall-async just because Oracle said so (and many implementations doing SOA forthe sake of SOA).

o  SOA concept puts services intothree categories: Entity Service, Task Services and Utility services. This isactually consistent with the OO concepts (also in Andrew’s comment): what is aunit of work for which I should construct an object? Basically this 3-category conceptgives a rough guideline about the “level of a service”: Entity Service issimilar to an  EntityBean, a Task Service is business process (ororchestration process) and utility services are generic services, e.g. somegeneral import/export tools/APIs.

§  Quote Andrew’s comment: Think about what a thing (method,component, system) does. Try to have it only ever do one thing - that way youare more likely to achieve simplicity. The only difference here betweenmethods, components and systems is the level of abstraction, i.e. whatconstitutes 'one thing'. That can be quite hard.

 

 

-         When we have different datasources (especially some unknown and unpredicted ones) and/or multipledestinations, try to use a hub-spoke architecture instead of point-to-pointintegration. One example is the one you are very familiar with: PIX manager. Wehave a set of code for V2 and a set of code for V3. There is no sharedcode/logic between V2 implementation and V3 implementation. If there would be afuture V4, we might need to write a new set of code and probably little of theold code could be reused. I would like to suggest to have a canonical datamodel to represent the patient identity and a procedure to insert the data toour backend. This part can be fixed after it’s developed and tested. Then allthe future implementation will only need to convert the new source into thecanonical model and call the already-define procedure to insert the data. Thistheory/pattern can be applied in many cases. Actually NHIN and IHE are thecanonical data model / communication protocol providers. As to our day-to-daydevelopment:

o  This theory can be applied notonly on the high level, but also on the code module level. Try to encapsulate awell-defined procedure and keep it stable and independent; try to reuse itinstead of rewriting something new.

o  Generally we need to have apipeline like this: Receiving layer -> transformation layer (convert tocanonical)-> DAL -> Backend. (I think our XDS is already using thisstyle. But since the input is always in the same format, i.e. people follow IHEstandards and use the same schemas, the IHE formats are already the canonical formats. One potential issue is backward compatibility. If a system isrequired to support both new version of formats and old ones, a transformationlayer will definitely help.)

-         Logging:  

o  When serving multiple requestsfrom different requestors, the logged information tends to be intermingled inthe log file. It’s hard to get a clear “thread” about what happened for aspecific request/requestor. I would suggest to include a transactionID for eachrequest  and all the logging related to that transaction will carry thesame ID. ATNA logging already has a lot of things covered, but when generic exceptions occur, the context is lost;

o  In webMethods Integrationserver, two very useful functions are SavePipeline and RestorePipeline. I usedthese two frequently in my previous job. Basically what it does it to save allthe variables and their values in memory to an XML file. This is very helpfulwhen an exception occurs: you can go to the specific module where the erroroccurred, restore the pipeline and start debugging without going through allthe prior steps. This would be helpful for web applications, too. You would beable to re-construct the hot scene without starting the web server and goingthrough many pages to reach the issue point. This of course would require extracoding on serialization/ de-serialization on major business objects.

o  Lower level of error handlingshould propagate the exception to a specific higher level. Since the lowerlevel might not have the business knowledge (and/or might not have access tothe business objects in the higher level), it doesn’t know what to say in theerror message using business terms (other than something like SQLException:value would be truncated, or NullPointerException). With appropriateescalation/propagation, the appropriate higher level would be able to catch theerror and create meaningful logging message, and additionally, a full dump ofmajor objects can be created for pipeline restoration.




[i] 参考刘树民的关于日志的建议章节。

[ii] SOA面向服务的体系结构(Service Oriented Architecture,SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。

[iii] 原文是badge wagon

[iv] 服务黑箱性指服务只体现接口和功能,隐藏具体的实现。

[v] 实体Bean已很少被提及,它于普通的Bean有细微的差别呢。实体Bean包含简单的商业逻辑(如:地址信息实体Bean有个getAddress()  方法,可能就包含组装Housenumber, street, city, state, postal code的过程),而普通的Bean只是数据载体。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值