March 21, 2008

Programming as if the domain mattered (SPA 2008)

Keith Braithwaite and I presented a session called Programming as if the domain mattered at SPA 2008. I feel that the part of the session that I prepared code for didn't go great (I hadn't prepared the packaging of the code well enough, so it took longer for people to get set up than it should have). I think the part Keith prepared for went better.

Domainification

The results from the whole session came as a suprise to us. It was an experimental session from which we wanted to encourage participants to include as much domain stuff as possible (I'll call this "domainification") in some code and find how far people think this should be taken.

It seemed from the retrospective at the end of the session that many participants didn't feel it was worth taking the "domainification" as far as myself and Keith had expected.

I now wonder whether this was because of the guidance (or lack of) that we gave, due to the time being too short, or whether I just have a weird programming style (and by implication Keith too, but I shouldn't really say that!).

Therefore, I'm writing this to explain what I thought would happen to the code. I wonder whether people's opinions would have been different if they had had time to go further with the example. I hope this article will show participants of the session (and readers of this blog) some domainification of code to consider whether it is worth "programming as if the domain mattered".

There are more improvements that can be made ... and what has been done could have been done differently ... this article just shows some things that could have been done ...

"Traction control" - a simple continuous integration server

My example code was "Traction control" - written specifically for the session and roughly based on how the very first incarnation of build-o-matic worked (which is now very considerably different!). What "Traction control" does is:

1 an update (svn up)
2 if there are changes, writes a web page with a text area for the build output
3 runs the build putting the build output into the file used on the web page to show the build output
4 once the build has finished, writes either a red or green web page
5 go to 1

The code

Here's the code (with the least interesting bits missed out, and a bogus space in "< script>" due to rendering problem):

public class TractionControl {
	public static void main(String[] args) throws IOException {
		String buildCommand = args[0];
		int previous = -1;
		while (true) {
			exec("svn up");
			String output = exec("svnversion");
			int current = Integer.parseInt(output.trim());
			if (current != previous) {
				StringBuffer stringBuffer = new StringBuffer();
				stringBuffer.append("<html><head>");
				stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
				stringBuffer.append("<title>Traction Control build running</title></head><body>");
				stringBuffer.append("<iframe src=\"buildoutput.txt\" width=\"600\" height=\"180\"/>");
				stringBuffer.append("</body></html>");
				write("results.html", stringBuffer.toString());
				exec(buildCommand + " > buildoutput.txt");
				output = readContents("buildoutput.txt");
				stringBuffer = new StringBuffer();
				boolean success = output.toLowerCase().contains("build successful");
				stringBuffer.append("<html><head>");
				stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
				stringBuffer.append("<title>Traction Control results</title></head><body>");
				stringBuffer.append("< script>document.bgColor=\"");
				stringBuffer.append(success ? "#33ff33" : "#ff3333");
				stringBuffer.append("\";</script>");
				stringBuffer.append("Build " + (success ? "passed" : "failed"));
				stringBuffer.append("</body></html>");
				write("results.html", stringBuffer.toString());
				previous = current;
			}
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
			}
		}
	}
	
	// definitions of "exec", "readContents" and "write" follow - you can guess roughly what they are ...

Procedural refactoring

The most horrible feature of this code is the creation of the web pages - I'll factor those out into methods:

public class TractionControl {
	public static void main(String[] args) throws IOException {
		String buildCommand = args[0];
		int previous = -1;
		while (true) {
			exec("svn up");
			String output = exec("svnversion");
			int current = Integer.parseInt(output.trim());
			if (current != previous) {
				writeBuildingPage();
				exec(buildCommand + " > buildoutput.txt");
				writeResultsPage();
				previous = current;
			}
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
			}
		}
	}

	private static void writeResultsPage() throws IOException {
		String output = readContents("buildoutput.txt");
		StringBuffer stringBuffer = new StringBuffer();
		boolean success = output.toLowerCase().contains("build successful");
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control results</title></head><body>");
		stringBuffer.append("< script>document.bgColor=\"");
		stringBuffer.append(success ? "#33ff33" : "#ff3333");
		stringBuffer.append("\";</script>");
		stringBuffer.append("Build " + (success ? "passed" : "failed"));
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}

	private static void writeBuildingPage() throws IOException {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control build running</title></head><body>");
		stringBuffer.append("<iframe src=\"buildoutput.txt\" width=\"600\" height=\"180\"/>");
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}
		
	// definitions of "exec", "readContents" and "write" follow - you can guess roughly what they are ...

Introducing more domain

Factoring out those methods makes it much clearer, but there is a missing domain concept - the results page, so I'll introduce that:

public class TractionControl {
	public static void main(String[] args) throws IOException {
		String buildCommand = args[0];
		int previous = -1;
		while (true) {
			exec("svn up");
			String output = exec("svnversion");
			int current = Integer.parseInt(output.trim());
			if (current != previous) {
				new ResultsPage().writeBuildingPage();
				exec(buildCommand + " > buildoutput.txt");
				new ResultsPage().writeResultsPage();
				previous = current;
			}
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
			}
		}
	}
	
	// now, definition of "exec" only

and

public class ResultsPage {
	public void writeResultsPage() throws IOException {
		String output = readContents("buildoutput.txt");
		StringBuffer stringBuffer = new StringBuffer();
		boolean success = output.toLowerCase().contains("build successful");
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control results</title></head><body>");
		stringBuffer.append("< script>document.bgColor=\"");
		stringBuffer.append(success ? "#33ff33" : "#ff3333");
		stringBuffer.append("\";</script>");
		stringBuffer.append("Build " + (success ? "passed" : "failed"));
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}

	public void writeBuildingPage() throws IOException {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control build running</title></head><body>");
		stringBuffer.append("<iframe src=\"buildoutput.txt\" width=\"600\" height=\"180\"/>");
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}
		
	// definitions of "readContents" and "write" follow - you can guess roughly what they are ...

Responsibilities mixed up

In the code as shown, the results page is responsible for running the build and deciding whether it passed. This doesn't seem right. Looking at the code, there is no reason why the "success" of the build shouldn't be passed in to the writeResultsPage method, and I feel like it would be better. This refactoring results in:

public class TractionControl {
	public static void main(String[] args) throws IOException {
		String buildCommand = args[0];
		int previous = -1;
		while (true) {
			exec("svn up");
			String output = exec("svnversion");
			int current = Integer.parseInt(output.trim());
			if (current != previous) {
				new ResultsPage().writeBuildingPage();
				exec(buildCommand + " > buildoutput.txt");		
				output = readContents("buildoutput.txt");
				boolean success = output.toLowerCase().contains("build successful");
				new ResultsPage().writeResultsPage(success);
				previous = current;
			}
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
			}
		}
	}

	// definitions of "exec" and "readContents" follow - you can guess roughly what they are ...

and ResultsPage:

public class ResultsPage {
	public void writeResultsPage(boolean success) throws IOException {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control results</title></head><body>");
		stringBuffer.append("< script>document.bgColor=\"");
		stringBuffer.append(success ? "#33ff33" : "#ff3333");
		stringBuffer.append("\";</script>");
		stringBuffer.append("Build " + (success ? "passed" : "failed"));
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}

	public void writeBuildingPage() throws IOException {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control build running</title></head><body>");
		stringBuffer.append("<iframe src=\"buildoutput.txt\" width=\"600\" height=\"180\"/>");
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}

	// definition of "write" follows - you can guess roughly what it is ...

Naming, getting more abstract and other improvements.

During the session, Chris Wallace suggested that instead of writing a web page, the code could usefully be made more abstract. Rather than having a results page, the missing domain concept could be thought of as "build results reporting". Maybe the interface to this would have a "buildStarted" and "buildFinished(boolean)" method - and maybe that is what the names of the methods of ResultsPage should be? Having created the ResultsPage class, it's clear now that it would be easy to introduce new forms of notification without having to significantly change the rest of the code. (Chris also pointed out that the way the code sets the background color is horrible, but for the purposes of the exercise it doesn't matter).

Maybe the magic values "results.html" and "buildoutput.txt" should be passed in on construction? Should TractionControl make one instance of the ResultsPage or is it OK to keep creating new instances? Which is more logical?

One of the benefits of creating ResultsPage is now it's clear that the helper method "write" is only used to write the result page and not for anything else.

Removing duplication

In this article, I won't bother taking ResultsPage any further; there is obvious duplication that could be removed but it'll make the article too long.

During the SPA session, one pair replaced ResultsPage creation with some sort of template thing, which seems sensible. ResultsPage as defined above is a bit horrible because of the duplication of code to do with HTML - so using some templating thing (like Velocity) would be a clean way of refactoring this code.

More procedural refactoring

There are three lines of code that I don't want to continue repeating in this article because they just give you a few more lines to look at without doing anything interesting - it's the Thread.sleep() code. So, I've also factored that out the new method "sleep" - you'll see that soon.

More domainification

The determination of whether the build was successful or not, and the running of the build command, are all a bit ugly. The domain concept missing from the code is the build command. Introducing that, the code becomes (with my little procedural refactoring introducing the sleep method too):

public class TractionControl {
	public static void main(String[] args) throws IOException {
		BuildCommand buildCommand = new BuildCommand(args[0]);
		int previous = -1;
		while (true) {
			exec("svn up");
			String output = exec("svnversion");
			int current = Integer.parseInt(output.trim());
			if (current != previous) {
				new ResultsPage().writeBuildingPage();
				boolean success = buildCommand.runTheBuildAndItPasses();
				new ResultsPage().writeResultsPage(success);
				previous = current;
			}
			sleep();
		}
	}

	private static void sleep() {
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
		}
	}
	
	// definition of "exec" only

and BuildCommand:

public class BuildCommand {
	private String buildCommand;
	
	public BuildCommand(String buildCommand) {
		this.buildCommand = buildCommand;
	}

	public boolean runTheBuildAndItPasses() throws IOException {
		TractionControl.exec(buildCommand + " > buildoutput.txt");		
		String output = readContents("buildoutput.txt");
		boolean success = output.toLowerCase().contains("build successful");
		return success;
	}
		
	// definition of "readContents" only

This is controversial because "runTheBuildAndItPasses" executes the build and returns whether it was successful or not. Many developers like methods that return a boolean to have no side effects, i.e. to separate the executing of the build command from the returning whether that was successful or not. I suspect this is a cargo cult similar to structured programming, but I'm undecided. Some people think I'm a crazy hacker; opinions may vary. Generally, I'll stick with the team style in a day job, and do things like "runTheBuildAndItPasses" when I only have to answer to myself (e.g. my open source projects). I think in this case it's better to do what I've done, but I know some people will absolutely hate it. Overall, I don't care about the details for this - the important thing is the introduction of the BuildCommand - details of it's implementation may vary depending on taste.

Yuk - but I won't fix it here

There is a magic string all around this code - "buildoutput.txt" - I'd want to sort that out before I feel like the code is OK - but I won't bother here - it would just make the article too long.

Last of the really important domain things

So - looking at the code, and thinking about what it's doing - the most important domain concept still missing from this is the stuff related to subversion. There is possibly more to it than one class - but for the time being, I'll introduce the concept of the "working copy" - which is where Traction Control has got the code checked out to. One of the participants, Robert Chatley had a class called "Checkout" (along with lots of other classes - he really got into the spirit of the session - thanks Robert).

public class TractionControl {
	public static void main(String[] args) throws IOException {
		BuildCommand buildCommand = new BuildCommand(args[0]);
		WorkingCopy workingCopy = new WorkingCopy();
		while (true) {
			if (workingCopy.doUpdateAndThereWereIncomingChanges()) {
				new ResultsPage().writeBuildingPage();
				boolean success = buildCommand.runTheBuildAndItPasses();
				new ResultsPage().writeResultsPage(success);
			}
			sleep();
		}
	}

	// definitions of "exec" and "sleep" follow ...

and the working copy is represented by:

public class WorkingCopy {
	private int previous = -1;
	
	public boolean doUpdateAndThereWereIncomingChanges() throws IOException{
		TractionControl.exec("svn up");
		String output = TractionControl.exec("svnversion");
		int current = Integer.parseInt(output.trim());
		boolean thereWereIncomingChanges = current != previous;
		previous = current;
		return thereWereIncomingChanges;
	}
}

Again - controversial use of a method which does something and returns a boolean result. I'd be prepared to do this differently, i.e. with the update and the checking to see if there were updates separately - it's not something I feel as strongly about as "structured programming" - which I think is definitely a cargo cult. Again, the important thing is introducing the concept of the working copy (or something else that encapsulates the use of subversion) rather than the exact details of it's implementation.

Is it any better?

There is more that could be done - the exceptions are nasty, the use of the static "exec" on class TractionControl is nasty. I'd maybe introduce a "CommandLine" class to do the "exec". I would probably make it such that there is one "ResultsPage" that is reused. Some of the names could be better - there are lots of things that could be better (if I was pairing, I'm sure it would be much better!).

I think this code is better than the original; I find it much easier to understand. Based on comments in the session, some people don't think it's worth the effort. Some don't think it's much better. Many think it isn't worth going this far.

I found it interesting that there was such a large variety of what people did in the workshop. Probably the largest group of similar results was where people refactored just within the class "TractionControl" without introducing any more classes - or maybe only one more. I had expected that the domain concepts of the build command, subversion stuff and the web pages would have been represented by classes, as in this article, by a larger number of people (but of course, done slightly differently). Maybe the reason people didn't do this is because each of these doesn't encapsulate much code? I think a lot of people were happy just with the benefits of doing a "procedural" style refactoring.

I'd like to hear from people what they think - but unfortunately, comments are switched off due to high levels of spam. If you are in London, come along to XTC some time when I'm there and talk to me, or email me at ivan at tadmad dot co-dot-uk.

Appendix - the code shown all together as it is the end of this article

public class TractionControl {
	public static void main(String[] args) throws IOException {
		BuildCommand buildCommand = new BuildCommand(args[0]);
		WorkingCopy workingCopy = new WorkingCopy();
		while (true) {
			if (workingCopy.doUpdateAndThereWereIncomingChanges()) {
				new ResultsPage().writeBuildingPage();
				boolean success = buildCommand.runTheBuildAndItPasses();
				new ResultsPage().writeResultsPage(success);
			}
			sleep();
		}
	}

	// definitions of "exec" and "sleep" follow ...
public class ResultsPage {
	public void writeResultsPage(boolean success) throws IOException {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control results</title></head><body>");
		stringBuffer.append("< script>document.bgColor=\"");
		stringBuffer.append(success ? "#33ff33" : "#ff3333");
		stringBuffer.append("\";</script>");
		stringBuffer.append("Build " + (success ? "passed" : "failed"));
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}

	public void writeBuildingPage() throws IOException {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("<html><head>");
		stringBuffer.append("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"5\">");
		stringBuffer.append("<title>Traction Control build running</title></head><body>");
		stringBuffer.append("<iframe src=\"buildoutput.txt\" width=\"600\" height=\"180\"/>");
		stringBuffer.append("</body></html>");
		write("results.html", stringBuffer.toString());
	}

	// definition of "write" follows - you can guess roughly what it is ...
public class BuildCommand {
	private String buildCommand;
	
	public BuildCommand(String buildCommand) {
		this.buildCommand = buildCommand;
	}

	public boolean runTheBuildAndItPasses() throws IOException {
		TractionControl.exec(buildCommand + " > buildoutput.txt");		
		String output = readContents("buildoutput.txt");
		boolean success = output.toLowerCase().contains("build successful");
		return success;
	}
		
	// definition of "readContents" only
public class WorkingCopy {
	private int previous = -1;
	
	public boolean doUpdateAndThereWereIncomingChanges() throws IOException{
		TractionControl.exec("svn up");
		String output = TractionControl.exec("svnversion");
		int current = Integer.parseInt(output.trim());
		boolean thereWereIncomingChanges = current != previous;
		previous = current;
		return thereWereIncomingChanges;
	}
}
Posted by ivan at March 21, 2008 11:36 AM
Copyright (c) 2004-2008 Ivan Moore