敏捷开发的必要技巧:将注释转为代码

Wingel 发表于 2006-12-25 09:35:35
作者:Wingel     来源:Wingel's blog
评论数:16 点击数:1,527     投票总得分:22 投票总人次:6
关键字:敏捷;注释;Agile

摘要:

本文作者通过实例介绍了敏捷开发的必要技巧:将注释转为代码.
示例

这是一个会议管理系统. 在会议中,每个参会者都会戴一个牌子,这牌子上面有这个参会者的信息(比如姓名之类的).在这个系统中,Badge这个类用来存放这个参会者的信息.请看一下下面的代码跟注释:

    //存放参会者身上戴的牌子所显示的信息. 
    public class Badge {
        String pid;  //参会者 ID
        String engName; //英文全名
        String chiName; //中文全名
        String engOrgName; //所在部门英文名称
        String chiOrgName; //所在部门中文名称
        String engCountry; //部门所在国家的中文名称
        String chiCountry; //部门所在国家的英文名称

        //***********************
        //构造函数.
        //根据参会者的id,去数据库取出该参与者的信息.
        //***********************
       Badge(String pid) {
           this.pid = pid;
           //***********************
           //取出参会者
           //***********************
           ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
           Participant part = partsInDB.locateParticipant(pid);
           if (part != null) {
              //取出参会者的英文全名
              engName = part.getELastName() + ", " + part.getEFirstName();
              //取出参会者的中文全名
              chiName = part.getCLastName()+part.getCFirstName();
              //***********************
              //取出所在部门跟国家.
              //***********************
              OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
              //取出所在部门的id.
              String oid = orgsInDB.getOrganization(pid);
              if (oid != null) {
                  Organization org = orgsInDB.locateOrganization(oid);
                  engOrgName = org.getEName();
                  chiOrgName = org.getCName();
                  engCountry = org.getEAddress().getCountry();
                  chiCountry = org.getCAddress().getCountry();
               }
           }
        }

        ...

    }


将注释转换为代码,让代码足够清楚到可以表示注释

我们先看一下第一个注释:

    //存放参会者身上戴的牌子所显示的信息. 
    public class Badge {

        ...

    }


我们干嘛需要这个注释呢?因为程序员认为"Badge"这个类名不足以让读代码的人清楚这个类的作用,所以就写了这个注释. 那如果我们直接将注释所表达的一些信息放在类名里面的话,就没有单独写注释的必要了.比如::

    public class ParticipantInfoOnBadge { 

        ...

    }


其实很多人肯定会问?难道写注释不是一个好的编程习惯吗?这问题很好,我也想知道.在解释之前,我们先把这个示例中所有的注释都转为代码先.

将注释转换为变量名

Consider: 

    public class ParticipantInfoOnBadge {
        String pid;  //参会者 ID
        String engName; //英文全名
        String chiName; //中文全名
        String engOrgName; //所在部门英文名称
        String chiOrgName; //所在部门中文名称
        String engCountry; //部门所在国家的中文名称
        String chiCountry; //部门所在国家的英文名称

        ...

    }


这里,我们就像对属性的注释,转化为属性名, 比如:

    public class ParticipantInfoOnBadge { 
        String participantId;
        String participantEngFullName;
        String participantChiFullName;
        String engOrgName;
        String chiOrgName;
        String engOrgCountry;
        String chiOrgCountry;

        ...

     }


对参数的注释,转化为参数名

看看:

    public class ParticipantInfoOnBadge { 

        ...

        //***********************
        //构造函数.
        //根据参会者的id,从数据库取出该参与者的信息.
        //***********************
        ParticipantInfoOnBadge(String pid) {
           this.pid = pid;

            ...

        }
     }


比如:

    public class ParticipantInfoOnBadge { 

        ...

        //***********************
        //构造函数.
        //从数据库取出该参与者的信息.
        //***********************
        ParticipantInfoOnBadge(String participantId) {
           this.participantId = participantId;

            ...

        }
     }


将注释转换为方法的一部分

上面的构造函数中,有两句注释,第一句我们已经解决了,那么还有"从数据库取出该参与者的信息"?  这句注释描述了,这个构造函数是如何实现的(就是从数据库里面取出信息),我们将这句话转化:

    public class ParticipantInfoOnBadge { 

        ...

        //***********************
        //构造函数.
        //***********************
        ParticipantInfoOnBadge(String participantId) {
           loadInfoFromDB(participantId);//现在,看一下这个构造函数内部,我们就能知道这个构造函数是做什么了吧.
        }
        void loadInfoFromDB(String participantId) {
           this.participantId = participantId;

            ...

        }
     }


删掉没用的注释

有时候,我们会碰到一些注释,很明显没什么用处的,比如:

    public class ParticipantInfoOnBadge { 

        ...

        //***********************
        //构造函数.
        //***********************
        ParticipantInfoOnBadge(String participantId) {

            ...

        }
     }


就算去掉这些注释,我们也能看得出来,这是个构造函数.这个注释并没什么用处.
什么样的类是看代码的人最喜欢的?那就是简单易看的类.一个设计得好的类,能够让人家一眼就能出你这个类都有些什么东西,明白你这个类都做了一些什么事.如果看这个类的时候,要不停的将屏幕滚来滚去,而思维还要随屏幕的滚动跳转,无形中,看懂这个类需要花的时间就多了.
一个屏幕,差不多只能显示20行左右的代码,而这个没用的注释,一下子就占用了3行的代码,一些有用的信息反而被挤掉了(比如说代码),得不偿失啊!我看还是赶紧移除这个注释:

    public class ParticipantInfoOnBadge {

        ...

        ParticipantInfoOnBadge(String participantId) {

            ...

        }
     }


将一部分代码重构成方法,用方法名来表达注释的意思

先看看下面这个注释:

        void loadInfoFromDB(String participantId) { 
           this.participantId = participantId;
           //***********************
           //取得参会者的全名.
           //***********************
           ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
           Participant part = partsInDB.locateParticipant(participantId);
           if (part != null) {
               //取得参会者的英文全名.
               engFullName = part.getELastName() + ", " + part.getEFirstName();
               //取得参会者的中文全名.
               chiFullName = part.getCLastName()+part.getCFirstName();
               //***********************
               //取得参会者所在部门和国家.
               //***********************
               OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
               //取得参会者被雇佣部门的id.
               String oid = orgsInDB.getOrganization(participantId);
               if (oid != null) {
                  Organization org = orgsInDB.locateOrganization(oid);
                   engOrgName = org.getEName();
                   chiOrgName = org.getCName();
                   engOrgCountry = org.getEAddress().getCountry();
                   chiOrgCountry = org.getCAddress().getCountry();
               }
            }
        }


现在我们已经看清这段注释要表达一些什么信息,如果要使代码跟注释一样清楚,我们可以将注释所解释的那部分代码抽取出来,做成一个方法,然后让方法名来表达注释的意思.如果可以的话,我们就不需要额外的注释了:

        void loadInfoFromDB(String participantId) { 
           this.participantId = participantId;
           getParticipantFullNames(); //(取得参会者的全名,注意,我们已经将注释去掉了.)
            //***********************
            //取得参会者所在部门和国家.
            //***********************
            //取得参会者被雇佣部门的id.
           OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
           String oid = orgsInDB.getOrganization(participantId);
           if (oid != null) {
               Organization org = orgsInDB.locateOrganization(oid);
               engOrgName = org.getEName();
               chiOrgName = org.getCName();
               engOrgCountry = org.getEAddress().getCountry();
               chiOrgCountry = org.getCAddress().getCountry();
            }
        }
        void getParticipantFullNames() {
           ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
           Participant part = partsInDB.locateParticipant(participantId);
           if (part != null) {
               //取得参会者的英文全名.
               engFullName = part.getELastName() + ", " + part.getEFirstName();
               //取得参会者的中文全名.
               chiFullName = part.getCLastName()+part.getCFirstName();
            }
        }


此外,还有一个注释:"取得参会者所在部门和国家",也是可以重构在方法名里面的:

        void loadInfoFromDB(String participantId) { 
           this.participantId = participantId;
           getParticipantFullNames();
           getOrgNameAndCountry(); //又抽取掉了一个注释
        }
        void getParticipantFullNames() {
           ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
           Participant part = partsInDB.locateParticipant(participantId);
           if (part != null) {
               //取得参会者的英文全名.
               engFullName = part.getELastName() + ", " + part.getEFirstName();
               //取得参会者的中文全名.
               chiFullName = part.getCLastName()+part.getCFirstName();
            }
        }
        void getOrgNameAndCountry() {
           OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
           //取得参会者被雇佣部门的id.
           String oid = orgsInDB.getOrganization(participantId);
           if (oid != null) {
               Organization org = orgsInDB.locateOrganization(oid);
               engOrgName = org.getEName();
               chiOrgName = org.getCName();
               engOrgCountry = org.getEAddress().getCountry();
               chiOrgCountry = org.getCAddress().getCountry();
            }
        }


抽取出方法,放于另一个类

请看一下下面这两个注释:

    public class ParticipantInfoOnBadge { 

        ...

        void getParticipantFullNames() {
           ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
           Participant part = partsInDB.locateParticipant(participantId);
           if (part != null) {
               //取得参会者的英文全名.
               engFullName = part.getELastName() + ", " + part.getEFirstName();
               //取得参会者的中文全名.
               chiFullName = part.getCLastName()+part.getCFirstName();
            }
        }
     }


因为程序员觉得,这些代码片段还是不够清楚,所以还是要用注释还解释它们. 但这回移除注释时,我们会将抽取出来的方法,放到Participant这个类里面,而不是ParticipantInfoOnBadge了:

    public class ParticipantInfoOnBadge { 

        ...

        void getParticipantFullNames() {
           ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
           Participant part = partsInDB.locateParticipant(participantId);
           if (part != null) {
               engFullName = part.getEFullName(); //将职责交给domain自己,也就是Participant.
               chiFullName = part.getCFullName();
            }
        }
     }
    public class Participant {
        String getEFullName() {
           return getELastName() + ", " + getEFirstName();
        }
        String getCFullName() {
           return getCLastName() + getCFirstName();
        }
     }


用注释去命名一个已经存在的方法

请看下面的注释,也是这个例子中的最后一个注释了:

    public class ParticipantInfoOnBadge { 

        ...

        void getOrgNameAndCountry() {
           OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
           //取得参会者被雇佣部门的id.
           String oid = orgsInDB.getOrganization(participantId);
           if (oid != null) {
               Organization org = orgsInDB.locateOrganization(oid);
               engOrgName = org.getEName();
               chiOrgName = org.getCName();
               engOrgCountry = org.getEAddress().getCountry();
               chiOrgCountry = org.getCAddress().getCountry();
            }
        }
     }


我们之所以要用这个注释"取得参会者被雇佣部门的id",是因为这个方法名"getOrganization"取得不够清楚. 所以,我们将注释表达的信息,放在这个方法名里面:

    public class ParticipantInfoOnBadge { 

        ...

        void getOrgNameAndCountry() {
           OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
           String oid = orgsInDB.findOrganizationEmploying(participantId);
           if (oid != null) {
               Organization org = orgsInDB.locateOrganization(oid);
               engOrgName = org.getEName();
               chiOrgName = org.getCName();
               engOrgCountry = org.getEAddress().getCountry();
               chiOrgCountry = org.getCAddress().getCountry();
            }
        }
     }

    public class OrganizationsInDB {

        ...

        void findOrganizationEmploying(String participantId) {

            ...

        }
     }


pdf下载:http://www.blogjava.net/Files/Wingel/敏捷开发的必要技巧第1,2章.rar

本页页面地址:

投票评分(记入本贴作者的专家分)

     非常好 还行 一般 扔鸡蛋          投票总得分: / 投票总人次:

用户评论列表

#1 评论作者: fiher 发表时间: 2006-12-25 11:25 上午

这位兄弟反对注释有一定道理,不过完全去掉注释肯定是有问题的。将变量和方法名命名清楚是好的,但是注释同样还是需要的。

#2 评论作者: microtea 发表时间: 2006-12-25 01:59 下午

命名固然重要,注释也不能少

#3 评论作者: microtea 发表时间: 2006-12-25 01:59 下午

命名固然重要,注释也不能少

#4 评论作者: bigpear 发表时间: 2006-12-25 10:25 下午

注释写得过多的确没用,但是如果属性过多或属性的英文意思相似就容易搞错,而且变量名过长打的也麻烦。
现在java也支持中文变量名和方法的,之所以不用就是因为打的太慢了。

#5 评论作者: henry_four 发表时间: 2006-12-26 08:58 上午

的确.
取一个容易理解的变量名固然重要, 但是注释是不可以没有的....

#6 评论作者: cleverpig 发表时间: 2006-12-27 09:05 上午

对于产品来讲,注释是生成javadoc的基础,它可以帮助开发者顺利地生成具有良好风格的javadoc文档,而且针对QA来讲,代码具有一定限度的注释利于其理解代码、正确地进行测试(呵呵,虽然应该测试先行)。

去掉注释的另一个方式就是在重构时发现method中存在一些注释,将此注释的语句(通称为一些逻辑判断)提炼为独立的method,这样会减少method的长度,使其更加明确,同时还可能去掉了重复代码。

#7 评论作者: yhyaohu 发表时间: 2006-12-28 01:41 下午

有意义的变量名和方法名可以清楚了解变量和方法的用途,注释对于方便别人还有自己阅读、理解代码还是不可少的。使用有意义的变量名和方法名是个不错的习惯。

#8 评论作者: tcgjz 发表时间: 2006-12-29 11:53 上午

对英文好的还好说对英文不好的就郁闷啊

#9 评论作者: sorrybaby 发表时间: 2006-12-29 11:03 下午

支持楼主!
减少不必要的注释.
楼上几位理解的太绝对了.

#10 评论作者: yp0227 发表时间: 2006-12-30 09:32 上午

这个完全是相对的,尽量减少不必要的注释,添加必要的,有一个方法,原则,权衡的把握在里面,不是一两句话说的清楚的,经验到了就好了。但有一句话要记住:代码是写给别人看的,不是写给自己看的。

#11 评论作者: angelleecash 发表时间: 2007-01-04 03:33 下午

LZ的意思是减少不必要的注释
我觉得这是有必要的,以前认为注释越多越好
不过时间证明,过多的注释只能说明小段代码过于复杂
或者自己的逻辑不清
物极必反,物理。

#12 评论作者: wq830630 发表时间: 2007-01-15 04:10 上午

注释这东西还是要得,注释是给别人看的,代码才是自己看的
不过能变量名取得好的确可以节省点注释

#13 评论作者: feidi 发表时间: 2007-01-18 01:52 下午

嗯,易于理解的命名,是沟通的基础,一切的开始...

#14 评论作者: lifeng_chen1973 发表时间: 2007-05-04 01:44 上午

好的命名的确可以减少一些注释,但不可以取代注释,只要取得两者的平衡就好!

#15 评论作者: r444 发表时间: 2007-07-24 11:19 上午

4444444444

#16 评论作者: hudong0178 发表时间: 2008-03-30 11:51 上午

好的命名很重要啊..注释很很重要..


发表我的评论 (评论可增加个人积分...)

用户*: E-mail:
评论内容*:

支持BBCode
算术题*: + =