Mob Refectoring:扎堆儿编程的一次经历

乔梁 | 2021-01-12

这是我在2009年带领 GoCD 团队时的一个经历。套用现在的时髦词汇,就叫“mob programming”。

团队对某一模块的代码进行了一次重构,引入了我们之前代码中遗失的一个领域对象,让代码看起来更简单,容易维护。

一、什么是 Mob Programming

在 wikipedia 上的定义是:

Mob programming (informally mobbing) is
a software development approach
where the whole team works on the same thing,
at the same time,
in the same space, and
at the same computer.
This is similar to pair programming.

简单来说,就是整个团队在同一时间一起做同一个任务。也就是:

  • one team
  • one (active) keyboard
  • one screen (projector of course)

二、我们的 Mob 是什么样的

其实,在我2009年的文章中,我把它叫做 Team Pair-Programming。

这个过程大约花了两个小时。

在这两个小时里,整个团队的开发人员坐在一个小会议室里,用一台电脑,一个投影仪,对我们的技术债进行了重构。

这种方式带来的好处是:

Everone on the same page.

所有人都很容易理解为什么要做这个修改,以及相应的代码变更是什么,并将其结果(包括领域概念的变化)应用于接下来的开发工作中。 所有

三、工作流畅的重要前提

在两个小时内,我们就可以直接动手重构这么多的内容,还有两个重要因素,功不可没。它们是:

  1. 一个强大重构功能的开发环境
  2. 全面的自动化测试

四、我们的成果

1. 总结出一个重构手法

通过分析对象的生命周期,找到缺失的Domain对象。

2. 如何断定技术债

重构之前,所有的代码都可以工作。

可是,随着Feature的增多,每当需要改动这个区域的代码时,开发人员都觉得非常麻烦,小心翼翼的,而且代码读起来也不是很流畅。  

3. 原始代码是什么样的

下面是伪代码:

public class A {
   @Autowired private BService b;
   public A() {
   }
   public doWork() {
       .....
       C c = b.doSomething ();
  .....
   }
}

A 是一个 Domain 对象,却被注入了 Service B。显然,这是不对的,但是问题在哪里呢?

4. 重构前的问答

问题一:这个对象的生命周期是怎样的呢?

从两个类的命名上看,并没有什么必然的联系,追溯代码发现,在 A 的 doWork() 中调用了 BService 的某个方法。

问题二:为什么在这里调用了 BService 的这个方法呢?

因为A的 doWork() 中需要调用 BService 的 doSomethong() 来得到其提供的对象 C .

问题三:为什么注入 BService ,而不直接注入 C 呢?

看来找到问题了.那么就让我们注入这个对象吧!!!!

(重构开始了...)

5. 重构期间的问答

问题四:A 的 doWork() 方法中为什么要使用对象 C 呢?从类名上看,这个对象 C 应该负责A所要达到的目的吗?

A 需要 doX() 能力 ,而 对象 C 可以提供这个能力 。当然,对象 C 还有提供一些其它方法,如doY() 和 doZ(),而这些方法似乎与 A 毫不相关.

问题五:那么究竟是 C 只应该提供 doX() 呢?还是应该由别的对象来负责提供doX()?

从命名上看,C 应该负责doY() 和do Z(),而 doX()这个能力应该由名为 D 的对象来负责。

(那就创建这个对象吧)

问题六:这个新的对象 D 是不是应该由 BService 来提供呢?

似乎应该是由BService来提供的,因为目前除了BService,没有任何对象拥有这个能力,可以提供对象 D。

检验对象 BService,查看其所有 Public 方法,观察方法的命名,是否发现所有方法名称中的异样了呢?

在两个月前,该对象的责任非常单一。但自从上个月加入某新功能以后,BService 就对外提供了不同的 Domain对象,而这两个 Domain 对象并不在同一个领域抽象层次上。

所以,我们应该增加新的对象 EService。

问题七:那么我们要在A中注入EService吗?

不应该注入EService,而应该是对象D.

自此,我们找到了两个缺失的对象,重构之后,代码看起来有如行云流水,让人感觉心旷神怡。

public class A {
   public A() {
   }
   public void doWork(D) {

       d.doXXX ();
   }
}

public class D {
   public D() {
   }
   public boolean doXXX() {
       ........
       return m=null? true:faulse;
   }
}

public class EService {
   public EService(.....) {
   }
   public D findDByID(int id) {
       ........
       return d;
   }
}

而调用的方法则改为如下所示:

a.doWork(eService.findDbyID(id));
原文链接:

重构实践之一:通过分析生命周期,找出缺失的Domain对象