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

上一篇 / 下一篇  2007-12-07 14:51:36 / 个人分类:敏捷开发

    这是一个会议管理系统。在会议中,每个参会者都会戴一个牌子,牌子上面有该参会者的信息(比如姓名等)。在该系统中,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 rgsInDB = OrganizationsInDB.getInstance();
   //取出所在部门的ID
   String id = orgsInDB.getOrganization(pid);
   if (oid != null) {
    Organization rg = orgsInDB.locateOrganization(oid);
    engOrgName = org.getEName();
    chiOrgName = org.getCName();
    engCountry = org.getEAddress().getCountry();
    chiCountry = org.getCAddress().getCountry();
   }
  }
 }
 ...
}

    如何将注释转换为代码,让代码足够清楚到可以表示注释?我们先看一下第一个注释:

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

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

public class ParticipantInfoOnBadge {
 ...
}

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

将注释转换为变量名

    比如:

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 rgsInDB = OrganizationsInDB.getInstance();
  //取得参会者被雇佣部门的ID
  String id = orgsInDB.getOrganization(participantId);
  if (oid != null) {
   Organization rg = 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 rgsInDB = OrganizationsInDB.getInstance();
 String id = orgsInDB.getOrganization(participantId);
 if (oid != null) {
  Organization rg = 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 rgsInDB = OrganizationsInDB.getInstance();
 //取得参会者被雇佣部门的ID
 String id = orgsInDB.getOrganization(participantId);
 if (oid != null) {
  Organization rg = 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 rgsInDB = OrganizationsInDB.getInstance();
  //取得参会者被雇佣部门的ID
  String id = orgsInDB.getOrganization(participantId);
  if (oid != null) {
   Organization rg = 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 rgsInDB = OrganizationsInDB.getInstance();
  String id = orgsInDB.findOrganizationEmploying(participantId);
  if (oid != null) {
   Organization rg = orgsInDB.locateOrganization(oid);
   engOrgName = org.getEName();
   chiOrgName = org.getCName();
   engOrgCountry = org.getEAddress().getCountry();
   chiOrgCountry = org.getCAddress().getCountry();
  }
 }
}
public class OrganizationsInDB {
 ...
 void findOrganizationEmploying(String participantId) {
  ...
 }
}

改进完的代码

    看看改进完的代码,如果让其他人读我们的代码,现在看懂需要多长时间?而重构之前,那个人又需要花多长时间?

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

 ParticipantInfoOnBadge(String participantId) {
  loadInfoFromDB(participantId);
 }
 void loadInfoFromDB(String participantId) {
  this.participantId = participantId;
  getParticipantFullNames();
  getOrgNameAndCountry();
 }
 void getParticipantFullNames() {
  ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
  Participant part = partsInDB.locateParticipant(participantId);
  if (part != null) {
   participantEngFullName = part.getEFullName();
   participantChiFullName = part.getCFullName();
  }
 }
 void getOrgNameAndCountry() {
  OrganizationsInDB rgsInDB = OrganizationsInDB.getInstance();
  String id = orgsInDB.findOrganizationEmploying(participantId);
  if (oid != null) {
   Organization rg = orgsInDB.locateOrganization(oid);
   engOrgName = org.getEName();
   chiOrgName = org.getCName();
   engOrgCountry = org.getEAddress().getCountry();
   chiOrgCountry = org.getCAddress().getCountry();
  }
 }
}

为什么要删除额外的注释?

    为什么要删除额外的注释?其实,加注释本身是一件正确的事情,因为注释可以让人更容易地理解代码。但问题在于,因为常常没有把代码写清楚,所以我们就找了一个捷径,那就是写上注释。注释不够清楚,再写上文档。这样子,程序可以让人看懂了吧。这样造成的结果就是,没有人愿意去好好地组织代码,让代码清楚起来,因为他们觉得加上注释就好了。之后,代码更新,程序员却常常忘了去更新注释(我们不得不承认,这种情况经常发生)。过了一段时间,这些过时的注释不仅不能让代码更容易懂,反而会误导读代码的人。到了最后,我们剩下的东西就是:本身就不清晰的代码,加上一些不正确的注释。
    因此,不论什么时候当我们要加注释时,我们应该再三想想:我的注释能否转化在代码里面,让代码跟注释一样的清晰呢?大多数情况下,我们得到的答案都是:能。每一个注释都是一个改进代码的好机会。我们不能说注释少的代码就是好代码,但我们绝对可以说,包含太多注释的代码绝对不是高质量的代码。

方法名太长

    请看下面的例子:

class StockItemsInDB {
//找出所有数目少于10的海外存货
 StockItem[] findStockItems() {
  ...
 }
}

    如果要将这段注释转为代码,原则上我们会改成这样:
class StockItemsInDB {
 StockItem[] findStockItemsFromOverseasWithInventoryLessThan10() {
  ...
 }
}

    可惜的是,这个方法名实在太长了。别急,这并不表明我们不能去掉注释,相反这是在警告我们,代码有问题了。有什么问题呢?我们暂且不说,先看在这种情况下要怎么做呢?现在我们需要判断的是:这个系统的用户确实只对数目少于10的海外存货有兴趣吗?他有没有可能对那些数目少于20的海外存货有兴趣?他对本地存货就没兴趣了?或者超过25的存货有没有兴趣呢?基于以上考虑,我们可以把这里的注释转化到参数里面。具体如下:

class StockItemsInDB {
 StockItem[] findStockItemsWithFeatures(
     boolean isFromOverseas,
     InventoryRange inventoryRange) {
  ...
 }
}
class InventoryRange {
 int minimumInventory;
 int maximumInventory;
}

    如果客户真是只对少于10的海外存货有兴趣呢?那他肯定有一些特殊的理由(为什么只是这些条件,而不是其他呢?)所以经过了解,我们发现因为海运所花时间很长,所以他需要补充这些存货。此时,我们发现客户真正感兴趣的是那些需要补充的存货,而不仅仅是那些海外数目少于10的存货。因此,我们可以将他真正的意思转化在方法名和方法的实现里。

class StockItemsInDB {
 StockItem[] findStockItemsToReplenish() {
  StockItem stockItems[];
  stockItems = findStockItemsFromOverseas();
  stockItems = findStockItemsInventoryLessThan10(stockItems);
  return stockItems;
 }
}


TAG:

 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

我的栏目

日历

« 2008-08-30  
     12
3456789
10111213141516
17181920212223
24252627282930
31      

数据统计

  • 访问量: 6619
  • 日志数: 70
  • 建立时间: 2007-11-28
  • 更新时间: 2008-06-25

RSS订阅

Open Toolbar