QR code

Puzzle Driven Development

pdd

PDD, or Puzzle Driven Development, is a method used to break down programming tasks into smaller ones and enable their implementation in parallel. The PDD method is used widely in XDSD methodology. The method is pending a USPTO patent (application no. 12/840,306).

Let's review the method with an example. Say, for instance, you are a programmer and have been tasked to design and implement a Java class. This is the 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 to you that one hour is not enough because the problem is much bigger and requires more work than the slotted time allows. Additionally, there are a numerous unknowns:

  • What information do we need to save, and in what format?
  • What is the DB schema? Is it an SQL or NoSQL database?
  • How to connect to the DB? JDBC? JPA? DAO?
  • How to handle exceptions?

Let's keep all these unknowns in mind as we 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 the above test, we define the expected behavior of the class. The test fails to compile because there are two missing classes: DataBridge and DBWriter. Let's implement the bridge first:

import java.io.IOException;
public interface DataBridge {
  void insert(String text) throws IOException;
}

Next, 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);
  }
}

Using the above code, we solve the problem. In the example, we successfully designed, implemented and tested the required DBWriter class. Subsequently, the class can now 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. Furthermore, we still aren't answering the majority of questions asked in the sample scenario. For instance, we still don't know exactly how the database needs to be connected, its type (SQL or NoSQL,) the correct data format and so on. However, we've already made a number of important 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 refine and correct our assumptions. Here is the first puzzle we need to add:

public interface DataBridge {
  /**
   * @todo #123 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 may 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 locator and a comment. Locator displays the following: "The puzzle was created while working with ticket #123".

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 its 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 because we are not sure that the architectural decision is right. Actually, the design is very primitive at the moment and very likely to be incorrect. To refine it and refactor, we require more information from the task specifier.

The task is finished. Now, you can reintegrate your branch into master and return the ticket to whoever 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. Consequently, our simple one-hour task can potentially generate hundreds of other tasks, which may take days or even years to complete. Nevertheless, your goal of working with your specific task is to finish it as soon as possible and reintegrate your branch into master.

Best Practices

There are a few simple rules that help you to place puzzles correctly.

First, you should put your @todo annotations at the point where your code hits a stub. For example, in a unit test. You're implementing a test and it fails because the class has not yet been implemented. You skip the test with the @Ignore annotation and add a @todo puzzle to its JavaDoc.

Second, your puzzle should remain as near as possible to the code element that is hitting the stub. Say that you have a unit test that has three test methods. All of them fail now because the class has not been implemented. The best approach would be to ignore every one of them and create three (!) puzzles. Each one of the puzzles should explain what you expect from the class and how it should be implemented.

Third, be as descriptive as possible. Your puzzle will soon be a task definition for someone else. So, explain clearly what you expect the next person to implement, how to do it, which documentation to use and so on and so forth. There should be enough information present that the next person assigned to the puzzles is able to implement your required classes without additional input from you!

BTW, puzzle collection process can be automated by means of our PDD Ruby gem.