以下是一个创建资源的简单构造块:

  public static ResourceBuilder newResource (String userName) {

  ResourceBuilder rb = new ResourceBuilder();

  rb.userName = userName + UnitTestThreadContext.getUniqueSuffix();

  return rb; }

  public ResourceBuilder assignRole(String roleName) {

  this.roleName = roleName + UnitTestThreadContext.getUniqueSuffix();

  return this;

  }

  public Resource create() {

  ResourceDAO resourceDAO = new ResourceDAO(UnitTestThreadContext.getSession());

  Resource rs;

  if (StringUtils.isNotBlank(userName)) {

  rs = resourceDAO.createResource(this.userName);

  } else {

  throw new RuntimeException("must have a user name to create a resource");

  }

  if (StringUtils.isNotBlank(roleName)) {

  Role role = RoleBuilder.newRole(roleName).create();

  rs.addRole(role);

  }

  return rs;

  }

  public static void delete(Resource rs, boolean cascadeToRole) {

  Session session = UnitTestThreadContext.getSession();

  ResourceDAO resourceDAO = new ResourceDAO(session);

  resourceDAO.delete(rs);

  if (cascadeToRole) {

  RoleDAO roleDAO = new RoleDAO(session);

  List roles = rs.getRoles();

  for (Object role : roles) {

  roleDAO.delete((Role)role);

  }

  }

  }

  ResourceBuilder是创建者模式与工厂模式的一个实现,你可以以方法链接的形式使用它:

  ResourceBuilder.newResource(“Tom”).assignRole(“Developer”).create();

  其中包含了一个打扫战场的方法delete(),在这次重构练习的早期,我并没有非常频繁地调用delete()方法,因为我经常启动整个系统并添加一些测试数据以检查饼图是否正确显示。

  UnitTestThreadContext类非常有用,它保存了某个特定于线程的Hibernate Session对象,并且为你打算创建的实体提供了字符串作为名称前缀,以此保证实体的性。

  public class UnitTestThreadContext {

  private static ThreadLocal threadSession=new ThreadLocal();

  private static ThreadLocal threadUniqueId=new ThreadLocal();

  private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/

  dd HH_mm_ss_S");

  public static Session getSession(){>

  Session session = threadSession.get();

  if (session==null) {

  throw new RuntimeException("Hibernate Session not set!");

  }

  return session;

  }

  public static void setSession(Session session) {

  threadSession.set(session);

  }

  public static String getUniqueSuffix() {

  String uniqueId = threadUniqueId.get();

  if (uniqueId==null){

  uniqueId = "-"+dateFormat.format(new Date());

  threadUniqueId.set(uniqueId);

  }

  return uniqueId;

  }

  …

  }

  完成重构

  现在我终于可以启动一个小化的可运行架构,并执行这个简单的测试用例了:

  protected void setUp() throws Exception {

  TestServerMain.run(); //setup a minimum running infrastructure

  }

  public void testResourceBreakdown(){

  Resource resource=createResource(); //use ResourceBuilder to build unique resources

  List requests=createRequests(); //use RequestBuilder to build unique requests

  assignRequestToResource(resource, requests);

  List tickets=createTickets(); //use TicketBuilder to build unique tickets

  assignTicketToResource(resource, tickets);

  Map result=new ResourceBreakdownService().search(resource);

  verifyResult(result);

  }

  protected void tearDown() throws Exception {

  // use TicketBuilder.delete() to delete tickets

  // use RequestBuilder.delete() to delete requests

  // use ResourceBuilder.delete() to delete resources

  接下来我又编写了多个更复杂的测试用例,并且一边重构产品代码一边编写测试代码。

  有了这些测试用例,我可以将ResourceBreakdownService那个上帝类一点点进行分解。具体的细节不必多?嗦了,市面上已经有许多书籍指导你如何安全地进行重构。为了本文的完整性,以下是重构后的结构图:


  那个恐怖的“数组套Map再套数组再套Map……”数据结构现在已经组织为新的ResourceLoadBucket类了,它的实现用到了组合模式。它首先包含了某个特别级别的预计完成时间和实际完成时间,下一个级别的完成时间将通过aggregate()方法聚合之后得到。终的代码干净了许多,而且性能也更好。它也暴露了隐藏在原始代码的复杂性中的一些缺陷。当然,我也同时改进了我的测试用例。
  在整个练习中,我始终坚持从系统的整体方向进行思考的原则,我从这个方向开始了重构之路,并始终保持正确的方向。那些相对于手头上的任务来说不太重要的问题用临时方案绕过它。此外,我建立了一个具有小可行性的测试架构,让我的团队也可以使用它继续重构其它一些领域。在测试构架中依然保留了一些 hack的部分,因为从业务的角度来说没有太大的必要去清理它们。我所获的不仅是重构了一块非常复杂的功能区域,并且加深了对遗留系统的理解。将遗留系统当作一件易碎的瓷器并不会使你感觉更安全,只有大胆地深入它的内在并进行重构,才能使你的遗留系统在未来也能够继续它的使命。