﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-Johnny's Collections-随笔分类-编程技巧</title><link>http://www.blogjava.net/johnnylzb/category/44855.html</link><description>生活总是有太多的无奈与失望，让我们以在努力学习和工作中获得的成就感和快乐来冲淡它们。</description><language>zh-cn</language><lastBuildDate>Fri, 30 Apr 2010 17:40:05 GMT</lastBuildDate><pubDate>Fri, 30 Apr 2010 17:40:05 GMT</pubDate><ttl>60</ttl><item><title>编写高质量的代码——从命名入手</title><link>http://www.blogjava.net/johnnylzb/archive/2010/04/29/319741.html</link><dc:creator>Johnny.Liang</dc:creator><author>Johnny.Liang</author><pubDate>Thu, 29 Apr 2010 14:54:00 GMT</pubDate><guid>http://www.blogjava.net/johnnylzb/archive/2010/04/29/319741.html</guid><wfw:comment>http://www.blogjava.net/johnnylzb/comments/319741.html</wfw:comment><comments>http://www.blogjava.net/johnnylzb/archive/2010/04/29/319741.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.blogjava.net/johnnylzb/comments/commentRss/319741.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/johnnylzb/services/trackbacks/319741.html</trackback:ping><description><![CDATA[笔者从事开发多年，有这样一种感觉，查看一些开源项目，如Spring、Apache Common等源码是一件赏心悦目的事情，究其原因，无外两点：1）代码质量非常高；2）命名特别规范（这可能跟老外的英语水平有关）。<br />
要写高质量的代码，不是一件容易的事，需要长年累月的锻炼，是一个量变到质变的过程，但要写好命名，只需要有比较好的英语语法基础和一种自我意识即可轻松达到。本博文将会结合本人的开发经验，总结出若干命名规则，这些命名规则纯属个人的使用习惯，不代表是一种理想的规则，在这里列举出来，供大家交流讨论。<br />
<br />
1.切忌使用没有任何意义的英语字母进行命名：<br />
&nbsp;&nbsp;&nbsp; for(int i=0; i&lt;10; i++) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ...<br />
&nbsp;&nbsp;&nbsp; }<br />
这是在很多教Java基本语法的书上常见的代码片断，作为教学材料，这样写无可厚非，但作为真正的代码编写，程序员必须要养成良好的习惯，不要使用这种没有任何含义的命名方式，这里可以使用&#8220;index&#8221;。<br />
<br />
2.切忌使用拼音，甚至是拼音首字母组合：<br />
&nbsp;&nbsp;&nbsp; cishu =5; // 循环的次数<br />
&nbsp;&nbsp;&nbsp; zzje = 1000.00 // 转账金额<br />
笔者在做代码检查的时候，无数次遇到过这样的命名，使人哭笑不得<br />
<br />
3.要使用英文，而且要使用准确的英语，无论是拼写还是语法：<br />
<ul>
    <li>名词单数，必须使用单数英文，如Account、Customer。</li>
    <li>对于数组，列表等对象集合的命名，必须使用复数，而且最好按照英文的语法基础知识使用准确的复数形式，如 List&lt;Account&gt; accounts、Set&lt;Strategy&gt; strategies。</li>
    <li>对于boolean值的属性，很多开发人员习惯使用isXXX，如isClose（是否关闭），但这里有两点建议：1）最好不要带&#8220;is&#8221;，因为JavaBean的规范，为属性生成get/set方法的时候，会用&#8220;get/set/is&#8221;，上面的例子，生成get/set方法就会变成&#8220;getIsClose/isIsClose/getIsClose&#8221;，非常别扭；2）由于boolean值通常反映&#8220;是否&#8221;，所以准确的用法，应该是是用&#8220;形容词&#8221;，上面的例子，最终应该被改为 closed，那么get/set方法就是&#8220;getClosed/isColsed/setClosed&#8221;，非常符合英语阅读习惯。</li>
</ul>
<br />
4.方法名的命名，需要使用&#8220;动宾结构短语&#8221;或&#8220;是动词+表语结构短语&#8221;，笔者曾看到过千奇百怪的方法命名，有些使用名词，有些甚至是&#8220;名词+动词&#8221;，而且，如果宾语是一个对象集合，还是最好使用复数：<br />
&nbsp;&nbsp;&nbsp; createOrder(Order order) good&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; orderCreate(Order order) bad<br />
&nbsp;&nbsp;&nbsp; removeOrders(List&lt;Order&gt; orders) good <br />
&nbsp;&nbsp;&nbsp; removeOrder(List&lt;Order&gt; order) bad<br />
<br />
5.对于常见的&#8220;增删改查&#8221;方法，命名最好要谨慎：<br />
<ul>
    <li>增加：最常见使用create和add，但最好根据英语的语义进行区分，这有助于理解，create代表创建，add代表增加。比如，要创建一个Student，用createStudent要比用addStudent好，为什么？想想如果有个类叫Clazz(班级，避开Java关键字），现在要把一个Student加入到一个Clazz，Clazz很容易就定义了一个 addStudent（Student student)的方法，那么就比较容易混淆。</li>
    <li>修改：常见的有alter、update、modify，个人觉得modify最准确。</li>
    <li>查询：对于获取单个对象，可以用get或load，但个人建议用get，解释请见第7点的说明，对于不分条件列举，用list，对于有条件查询，用search（最好不要用find，find在英文了强调结果，是&#8220;找到&#8221;的意思，你提供一个&#8220;查询&#8221;方法，不保证输入的条件总能&#8220;找到&#8221;结果）。</li>
    <li>删除：常见的有delete和remove，但删除建议用delete，因为remove有&#8220;移除&#8221;的意思，参考Clazz的例子就可以理解，从班级移除一个学生，会用removeStudent。</li>
</ul>
<br />
6.宁愿方法名冗长，也不要使用让人费解的简写，笔者曾经遇到一个方法，判断&#8220;支付账户是否与收款账户相同&#8221;，结果我看到一个这样的命名：<br />
&nbsp;&nbsp;&nbsp; checkIsOrderingAccCollAccSame(...) 很难理解，我马上把它改为：<br />
&nbsp;&nbsp;&nbsp; isOrderingAccountSameAsCollectionAccount(...)，虽然有点长，但非常容易阅读，而且这种情况总是出现得比较少。<br />
<br />
7.如果你在设计业务系统，最好不要使用技术化的术语去命名。笔者曾经工作的公司曾经制订这样的命名规则，接口必须要以&#8220;I&#8221;开头，数据传输对象必须以&#8220;DTO&#8221;作为后缀，数据访问对象必须以&#8220;DAO&#8221;作为后缀，领域对象必须以&#8220;DO&#8221;作为后缀，我之所以不建议这种做法，是希望设计人员从一开始就引导开发人员，要从&#8220;业务&#8221;出发考虑问题，而不要从&#8220;技术&#8221;出发。所以，接口不需要非得以&#8220;I&#8221;开头，只要其实现类以&#8220;Impl&#8221;结尾即可（注：笔者认为接口是与细节无关的，与技术无关，但实现类是实现相关的，用技术化术语无可口非），而数据传输对象，其实无非就是保存一个对象的信息，因此可以用&#8220;**Info&#8221;，如CustomerInfo，领域对象本身就是业务的核心，所以还是以其真实名称出现，比如Account、Customer，至于&#8220;DAO&#8221;，这一个词来源于J2ee的设计模式，笔者在之前的项目使用&#8220;***Repository&#8221;命名，意味&#8220;***的仓库&#8221;，如AccountRepository，关于&#8220;Repository&#8221;这个词的命名，是来源于Eric Evans的《Domain-Driven Design》一书的仓库概念，Eric Evans对Repository的概念定义是：领域对象的概念性集合，个人认为这个命名非常的贴切，它让程序员完全从技术的思维中摆脱出来，站在业务的角度思考问题。说到这里，可能有人会反驳：像Spring、Hibernate这些优秀的框架，不是都在用&#8220;I&#8221;作为接口开头，用&#8220;DAO&#8221;来命名数据访问对象吗？没错！但千万别忽略了语义的上下文，Spring、Hibernate框架都是纯技术框架，我这里所说的场景是设计业务系统。<br />
<br />
8.成员变量不要重复类的名称，例如，很多人喜欢在Account对象的成员变量中使用accountId，accountNumber等命名，其实没有必要，想想成员变量不会鼓孤立的存在，你引用accountId，必须是account.accountId，用account.id已经足够清晰了。<br />
<br />
&#8220;勿以善小而不为，勿以恶小而为之&#8221;、&#8220;细节决定成败&#8221;，有太多的名言告诉我们，要注重细节。一个优秀的程序员，必须要有坚实的基础，而对于命名规则这样容易掌握的基础，我们何不现行？<br />
<img src ="http://www.blogjava.net/johnnylzb/aggbug/319741.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/johnnylzb/" target="_blank">Johnny.Liang</a> 2010-04-29 22:54 <a href="http://www.blogjava.net/johnnylzb/archive/2010/04/29/319741.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>“设计”你的代码</title><link>http://www.blogjava.net/johnnylzb/archive/2010/04/28/319551.html</link><dc:creator>Johnny.Liang</dc:creator><author>Johnny.Liang</author><pubDate>Tue, 27 Apr 2010 16:51:00 GMT</pubDate><guid>http://www.blogjava.net/johnnylzb/archive/2010/04/28/319551.html</guid><wfw:comment>http://www.blogjava.net/johnnylzb/comments/319551.html</wfw:comment><comments>http://www.blogjava.net/johnnylzb/archive/2010/04/28/319551.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.blogjava.net/johnnylzb/comments/commentRss/319551.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/johnnylzb/services/trackbacks/319551.html</trackback:ping><description><![CDATA[今天一个曾经共事的同行问我：&#8220;要从编码转为设计，大概需要多长时间？&#8221;<br />
我的回答是：&#8220;编码本身就是一种设计，你可以设计你的代码。&#8221;<br />
<br />
其实正如概要设计与详细设计，系统设计与架构设计一样，编码与设计也是没有明显的边界，每个正确成长的程序员，都必须从编码开始，慢慢锻炼抽象思维、逻辑思维、面向对象思维，然后慢慢的过渡到系统设计，再随着经验和知识的积累，慢慢过渡到架构设计。下面我将会以最近的一个手头的编码任务，简单介绍一下如何&#8220;设计&#8221;你的代码。<br />
<br />
任务是这样的，某银行支付系统的客户端接收银行用户录入的转账数据，当转账数据被审批通过后，状态转变为&#8220;transfer&#8221;，同时，该客户端需要通过JMS以异步的方式向支付系统后台发送一条带有转账记录（Instruction)的消息，后端在接收到信息之后，需要根据Instruction的一些相关信息，首先确定这笔转账数据是直接发送给真正进行转账的清算（Clearing）银行系统，还是停留在后端系统，等待后端系统中需要执行的工作流程（work flow）。而后端系统需要对Instruction执行的工作流程有两个，同时需要根据Instruction的一些相关信息进行选择。<br />
为了简化复杂度，我这里假设系统有一个InstructionHandleMsgDrivenBean，该bean有一个onMessage()方法，所有业务逻辑需要在该方法中实现。<br />
<br />
同时解释一下详细的业务细节：<br />
<ul>
    <li>判断Instruction是否需要停留在后端等待执行指定的工作流程有三个条件：xx、yy、zz，当三个条件都为true时，停留。
    </li>
    <li>判断Instruction需要走A流程还是B流程，由4个因素的组合确定，如果用&#8220;Y&#8221;代表true，&#8220;N&#8221;代表false，那么由这个四个因素组成的&#8220;XXXX&#8221;一共有16种组合，不同的组合分别走A和B流程，如：YYNN、YYNY to A，NNYY、NNNY to B，&#8230;&#8230;不累赘。 </li>
</ul>
好了，对于一个纯编程人员来说，拿到这样的需求，感觉逻辑很简单，可以直接编码了，于是，他开始一行一行的编写代码（伪代码）：<br />
<br />
public void onMessage(InstructionInfo instructionInfo) {<br />
&nbsp;&nbsp;&nbsp; if(xx &amp;&amp; yy &amp;&amp; zz) { // 停留在后端等待执行指定的工作流程<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // 根据每种组合进行条件判断，走哪个流程<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(a==true &amp;&amp; b==true &amp;&amp; c==true &amp;&amp; d==true {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ...<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; else if(...) {...}<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; else if(...) {...}<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ...<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; else(...) {...} &nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
这种做法是最为开发人员欢迎的，因为它简单、直接，但这种做法也恰恰反映了开发人员的通病——使用Java编写纯面向过程的代码。<br />
<br />
好了，说了一大堆，如何&#8220;设计&#8221;你的代码呢？答案是：使用面向对象思维：<br />
<br />
我们拿到需求之后，可以分析，这个需求大体上分为两部分：<br />
<ul>
    <li>判断是否需要停留在后端等待执行指定的工作流程的部分
    </li>
    <li>选择走哪个工作流程的部分 </li>
</ul>
<br />
有了这个前提，我可以设计出两个职责单一的对象了：<br />
<br />
public class InstructionHandleDecisionMaker {<br />
&nbsp;&nbsp;&nbsp; public static boolean isHandledByBackEnd(InstructionInfo info) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return (isXX(...) &amp;&amp; isYY(...) &amp;&amp; isZZ(...));<br />
&nbsp;&nbsp;&nbsp; }<br />
<br />
&nbsp;&nbsp;&nbsp; private booolean isXX(...) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implement the logic<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return false;<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; private booolean isYY(...) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implement the logic<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return false;<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; private booolean isZZ(...) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implement the logic<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return false;<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
public class InstructionWorkFlowSelector {<br />
&nbsp;&nbsp;&nbsp; private static Map mapping = new HashMap();<br />
&nbsp;&nbsp;&nbsp; static {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; mapping.input("YYNN",WorkFlow.A);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; mapping.input("NNYY",WorkFlow.B);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ...<br />
&nbsp;&nbsp;&nbsp; }<br />
<br />
&nbsp;&nbsp;&nbsp; public static WorkFlow getWorkFlow(Instruction info) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; StringBuilder result = new StringBuilder();<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; result.append(isA(...)).append(isB(...));<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; result.append(isC(...)).append(isD(...));<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return mapping.get(result.toString());<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; private static String isA(...) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implment the logic<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return "N";<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; private static String isB(...) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implment the logic<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return "N";<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; private static String isC(...) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implment the logic<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return "N";<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; private static String isD(...) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implment the logic<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return "N";<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
可以看到，我先按职责划分了类，再按职责抽取了私有方法，&#8220;框架&#8221;设计好 ，为了让编译通过，我上面完整的填写了代码的，然后加上TODO标识，然后，我可以编写我的onMessage方法了：<br />
<br />
public void onMessage(InstructionInfo instructionInfo) {<br />
&nbsp;&nbsp;&nbsp; if( InstructionHandleDecisionMaker.isHandledByBackEnd(...) ) {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; WorkFlow wf =InstructionWorkFlowSelector.getWorkFlow(...);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO Implment the logic<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
到目前为止，我已经用纯面向对象的思维方式&#8220;设计&#8221;好我的代码了，这时，我思维非常清晰，因而代码结构也非常清晰，职责单一，内聚高，耦合低，最后，我可以根据需求文档的细节（没有描述）慢慢的编写我的实现了。<br />
<br />
复杂的事物总是由一些较简单的事物组成，而这些较简单的事物也是由更简单的事物组成，如此类推。因此，在编写代码的时候，先用面向对象的思维把复杂的问题分解，再进一步分解，最后把简单的问题各个击破，这就是一种设计。开发人员只要养成这种习惯，即使你每天都只是做最底层的编码工作，其实你已经在参与设计工作了，随着知识和经验的累积，慢慢的，你从设计代码开始，上升为设计类、方法，进而是设计模块，进而设计子系统，进而设计系统&#8230;&#8230;，最终，一步一步成为一个优秀的架构师。<br />
<br />
最后，有一个真理奉献给浮躁的程序员：<br />
<br />
<strong>优秀的架构师、设计师，必定是优秀的程序员，不要因为你的职位上升了，就放弃编码。</strong><br />
<br />
<strong>补充说明：</strong>本博文纯粹是讨论一种思维习惯，不要把其做法生搬硬套，不管实际情况，直接在编码的时候这样做，不见得是最好的选择。在实际编码中，有如下问题你必须考虑：<br />
<ul>
    <li>你需要考虑业务逻辑的可重用性和复杂程度，是否有必要设计出新的类或抽取新的私有方法来封装逻辑，或者直接在原方法上编码（如果足够简单）。</li>
    <li>新的业务逻辑，是否在某些地方已经存在，可以复用，即使不存在，这些逻辑是应该封装到新的类中，还是应该放置到现有的类中，这需要进行清晰的职责划分。</li>
    <li>需要在设计和性能上作出权衡。</li>
    <li>如果在现成的系统中增加新的功能，而现成系统的编码风格与你想要的相差很远，但你又没有足够的时间成本来进行重构，那么还是应该让你的代码与现成系统保持一致的风格。<br />
    </li>
</ul>
<br />
<img src ="http://www.blogjava.net/johnnylzb/aggbug/319551.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/johnnylzb/" target="_blank">Johnny.Liang</a> 2010-04-28 00:51 <a href="http://www.blogjava.net/johnnylzb/archive/2010/04/28/319551.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>