Friday, August 15, 2008

Thinking about domain-specific languages (DSLs) ... Generally, it is easier to keep track of the role of each argument to some function/method in languages with keyword parameters (e.g. Python, Ruby). Names are easier to remember than positions in a parameter list. In a language without keyword parameters, how do you make it easy to remember which parameter is what (putting aside for the moment the usefulness of IDEs in displaying the function/method signature for you)? Here's an example of how to do that in Java. Take this function:

public void validateState(PBXConference conference, int added, int connecting, int connected, int disconnecting, int disconected) {
assertEquals(conference.added(), added);
assertEquals(conference.connecting(), connecting);
assertEquals(conference.connected(), connected);
assertEquals(conference.disconnecting(), disconnecting);
assertEquals(conference.disconnected(), disconnecting);
}

A client would invoke it like:
validateState(conference, 2, 1, 1, 0, 0);

but that sequence of numbers doesn't help the readability of the test. So instead, while it's a bit more verbose, we can change the function definition to:
public class ConferenceStateValidator {
private PBXConference conference;

private ConferenceStateValidator(PBXConference conference) {
this.conference = conference;
}

public ConferenceStateValidator added(int n) {
assertEquals(conference.added(), n);
return this;
}

public ConferenceStateValidator connecting(int n) {
assertEquals(conference.connecting(), n);
return this;
}

public ConferenceStateValidator connected(int n) {
assertEquals(conference.connected(), n);
return this;
}

public ConferenceStateValidator disconnecting(int n) {
assertEquals(conference.disconnecting(), n);
return this;
}

public ConferenceStateValidator disconnected(int n) {
assertEquals(conference.disconnected(), n);
return this;
}

public static ConferenceStateValidator validateState(PBXConference conference) {
return new ConferenceStateValidator(conference);
}
}

And the client (assuming they've statically imported validateState), can do:
validateState(conference).added(2)
.connected(1).connecting(1)
.disconnected(0).disconnecting(0);

which is much cleaner.

No comments: