Puzzle Driven Development (PDD)
The article is still in draft.
PDD is a method of breaking down a programming task into smaller ones, and enabling their implementation in parallel. PDD method is widely used in XDSD methodology. The method is pending USPTO patent (application no. 12/840,306).
Let's review the method by example. You are a programmer and you have a task to design and implement a Java class. This is a formal task description: "class DBWriter has to extend java.io.Writer abstract class and save all incoming data into the database".
You have one hour to implement this task. It is obvious that one hour is not enough, because the problem is bigger than this time slot, and there are a number of unknowns:
- What information we need to save, in what format?
- What is the DB schema? Is it SQL or NoSQL database?
- How to connect to the DB? JDBC? JPA? DAO?
- How to handle exceptions?
Let's keep all this unknowns in mind and try to solve the problem on the highest level of abstraction. Of course, we start with a test:
import org.junit.*;
import static org.mockito.Mockito.*;
public class DBWriterTest {
@Test
void testSavesDataIntoDatabase() throws Exception {
DataBridge mapper = mock(DataBridge.class);
Writer writer = new DBWriter(mapper);
try {
writer.write("hello, world!");
} finally {
writer.close();
}
verify(mapper).insert("hello, world!");
}
}
In this test we defined an expected behavior of the class. The test will fail to compile because there are two classes missed: DataBridge and DBWriter. Let's implement the bridge first:
import java.io.IOException;
public interface DataBridge {
void insert(String text) throws IOException;
}
And the writer itself:
import java.io.IOException;
import java.io.Writer;
import java.utils.Arrays;
public class DBWriter implements Writer {
private DataBridge bridge;
public DBWriter(DataBridge brdg) {
this.bridge = brdg;
}
@Override
void flush() throws IOException {
}
@Override
void close() throws IOException {
}
@Override
void write(char[] cbuf, int off, int len) throws IOException {
String data = new String(Arrays.copyOfRange(cbuf, off, off + len));
this.bridge.insert(data);
}
}
The problem is solved. We successfully designed, implemented and tested the required DBWriter class, which immediately can be used "as is" by other classes.
Of course, the implementation is not finished, since we are not writing anything to the database, and we aren't answering the majority of questions asked above. We still don't know how exactly the database has to be connected, whether it's SQL or NoSQL, what is the right data format, etc. However, we already made a number of architectural assumptions, which allowed us to implement the class and make it usable by other classes.
Now it's time to identify the unknowns in our code and mark them with puzzles. Every puzzle is a request for refinement. We want to ask someone else to help us to refine and correct our assumptions. Here is the first puzzle we want to add:
public interface DataBridge {
/**
* @todo #123:2h I assumed that a simple insert() method will be
* enough to insert data into the database. Maybe it's
* not true, and some sort of transaction support will be
* required. We should implement this interface and create
* an integration test with a database.
*/
void insert(String text) throws IOException {
}
}
The puzzle has three elements: @todo tag, #123:2h locator, and a comment. Locator says the following: "The puzzle was created while working with ticket #123, and its resolution will require 2 hours, if I would do it."
Let's add one more puzzle:
void write(char[] cbuf, int off, int len) throws IOException {
// @todo #123? I assumed that the data should be sent to the database
// as they are received by the writer. Maybe this assumption
// is wrong and we should aggregate data into blocks/chunks
// and then send them to the data bridge.
String data = new String(Arrays.copyOfRange(cbuf, off, off + len));
this.bridge.insert(data);
}
This puzzle indicates one of our concerns, since we are not sure that the architectural decision is right. Actually, the design is very primitive at the moment and very likely is wrong. To refine it and refactor we need more information from the task specifier. That's why we added ? mark to the locator.
The task is finished. Now you can reintegrate your branch into trunk and return the ticket back to those who assigned it to you. His task now is to find other people who will be able to resolve the puzzles we just created.
Every puzzle created now will produce other puzzles, which will be resolved by other people. Thus, our small task of 1 hour size may generate hundreds of other tasks, which will be resolved in days or even years. However, your goal, while working with your particular task, is to finish it as soon as possible and reintegrate your branch into trunk.
Grammar
You can format your puzzles according to the following convention:
| @todo #123 | I want this puzzle to be resolved by someone else. |
| @todo #123! | I will resolve it myself later, when all other puzzles in this ticket are resolved. Please, revert the ticket back to me then. |
| @todo #123? | There is a problem with the uplink code, which I have to design my code for. I want the author of the uplink ticket to solve the puzzle and then I will continue with my ticket. |
| @todo #123:2hrs | I estimate that the resolution of this puzzle should take no more than two hours. |
| @todo #123/3 | Resolution of this puzzle may change/delete only the next three lines. All other lines in the method should be untouched. |
Of course you can combine them, for example: @todo #123?:2hrs/2