這個重構技巧的核心就是不要在Runnable#run
方法里寫業務邏輯,將業務邏輯抽成一個單獨的方法,測試起來更方便。
重構前:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.apache.commons.lang3.StringUtils;
public class Service {
private BlockingQueue<String> queue = new ArrayBlockingQueue<>(128);
public void start() {
new Thread(new EventRunnable()).start();
}
public void doService() {
String event = null;
xxx;
xxxx;
xxxx;
queue.offer(event);
}
private class EventRunnable implements Runnable {
@Override
public void run() {
try {
String event = queue.take();
if (StringUtils.isNotBlank(event)) {
xxx;
xxxx;
xxx;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
這段代碼很好理解,Service#doService
處理后將event
放到隊列中,然后由消費線程獲取并處理EventRunnable#run
。
這段代碼最大的問題就是可測性差,重構一下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.apache.commons.lang3.StringUtils;
public class Service {
private BlockingQueue<String> queue = new ArrayBlockingQueue<>(128);
public void start() {
new Thread(new EventRunnable()).start();
}
public void doService() {
String event = null;
xxx;
xxxx;
xxxx;
enQueue(event);
}
private void enQueue(String event) {
queue.offer(event);
}
private class EventRunnable implements Runnable {
@Override
public void run() {
try {
String event = queue.take();
processEvent(event);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void processEvent(String event) {
if (StringUtils.isNotBlank(event)) {
xxx;
xxxx;
xxx;
}
}
}
抽出enQueue
和processEvent
方法。這樣只要測試processEvent
就能知道邏輯是否正確,不涉及多線程。
更多的,還可以通過Mock
把異步代碼變成同步調用。
public void mock() {
new MockUp<Service> () {
@Mock
public void start() {
// 不用啟動異步線程
}
@Mock
public void enQueue(Invocation invocation, String event) {
// 直接變成同步調用,串聯起邏輯
Service instance = invocation.getInvokedInstance();
instance.processEvent(event);
}
};
}
以上代碼以
JMockit
為例,用Mockito
也差不多。