Creating Directories With Fluent Interface
A short story about refactoring of a simple utility method - from boolean parameters to the fluent interface. All in the name of making the API perfect. :)
The Original Function
Once upon a time there was this method in TestUtils class (Javadocs omitted but we really had one!):
public static String createTemporaryNonWritableDir(String dirName)
throws IOException {
String destDir = TMP_DIR + FILE_SEPARATOR + dirName;
File nonWritableDir = new File(destDir);
// remove it if existed (this happens in /tmp so it is not cleaned by mvn clean)
nonWritableDir.delete();
// create and make NOT writable
nonWritableDir.createNewFile();
nonWritableDir.setWritable(false);
return destDir;
}
The function worked fine, and you used it like this:
String destDir = TestUtils.createTemporaryNonWritableDir("non_writable_dir");
All Your Booleans Are Belong To Us!
Today when I came to work I noticed the function has evolved into this:
public static String createTemporaryDir
(String dirName, boolean readable, boolean writable) throws IOException {
String destDir = TMP_DIR + FILE_SEPARATOR + dirName;
File directory = new File(destDir);
// remove it if existed (this happens in /tmp so it is not cleaned by mvn clean)
directory.delete();
// create
directory.createNewFile();
// and make it NON readable if flag set
directory.setReadable(readable);
// and make it NON writable if flag set
directory.setWritable(writable);
return destDir;
}
I have to say I do not like this version. When using it, it is not clear what kind of directory will be created (which boolean relates to writing permission?)
String destDir = TestUtils.createTemporaryDir("non_writable_dir", true, false);
You will find much more about writing good tests in my book
"Practical Unit Testing
with TestNG and Mockito"
P.S. An additional downside was, that the original Javadoc was left as it was... eh.... as we all know: "documentation lies, tests never do" :)
Fluent Interface
I was thinking if I could improve this. An obvious way would be to create multiple methods like:
createTemporaryNonReadableDir(String dirName);
createTemporaryNonWritableDir(String dirName);
createTemporaryNonReadableNonWritableDir(String dirName);
but this would bloat the API, which I do not like.
So I gave it another try by using the builder pattern, like this:
public class TmpDirBuilder {
private String dirName;
private boolean readable = true;
private boolean writable = true;
public TmpDirBuilder withDirName(String dirName) {
this.dirName = dirName;
return this;
}
public TmpDirBuilder notReadable() {
this.readable = false;
return this;
}
public TmpDirBuilder notWritable() {
this.writable = false;
return this;
}
public String create() throws IOException {
String destDir = TMP_DIR + FILE_SEPARATOR + dirName;
File directory = new File(destDir);
// remove it if existed (this happens in /tmp so it is not cleaned by mvn clean)
directory.delete();
// now create and set flags
directory.createNewFile();
directory.setReadable(readable);
directory.setWritable(writable);
return destDir;
}
}
You can use it now like this:
String destDir = new TmpDirBuilder()
.withDirName("non_writable_dir").notWritable().create();
Of course other combinations are also possible (e.g. notWritable().notReadable()
etc.), and if one day we want to enhance this functionality with new features, it will be still possible.
The Question
So the question is if I improved the code or made it worse? What do you think?
Resources
Below few links to get you started with DSLs:
- http://www.infoq.com/articles/internal-dsls-java - a great introduction by Alex Ruiz (author of FEST Fluent Assertions http://fest.easytesting.org/) and Jeff Bay
- http://martinfowler.com/bliki/FluentInterface.html and http://martinfowler.com/bliki/DomainSpecificLanguage.html by Martin Fowler (not much about Java, but this is a must-read)
- http://code.google.com/p/make-it-easy/ an utility library which helps to use Test Data Builder pattern
BTW. Damn it, I have to spent additional 20 minutes at work now that have written this post! :) lol
This used to be my blog. I moved to http://tomek.kaczanowscy.pl long time ago.
What about an Enum
Hi Tomek, how is it going?
I totally agree about ugliness of String,boolean,boolean version.
Your refinement using bulider pattern is certianly better and definitely interesting, however seems a little bit overkill in this case.
How about introducing an Enum named AccesMode in place of boolean,boolean flags?
PS. I just love those comments on createNewFile(), setReadable() and setWritable() method invocations. They certainly boost the quality of code at least three times :)
Jakub Głuszecki
Hi, good idea
But, I don't like your design. The interface is included in the TestUtils which is a util tool set. So it is better to use the interface directly, not like your TmpDirBuilder. I think the best way is to create your own tool set which use the createTemporaryDir interface to implement createTemporaryNonReadableDir, createTemporaryNonWritableDir and createTemporaryNonReadableNonWritableDir. How do you think? - low blood pressure | stress and blood pressure | high blood pressure remedies
i'm lost :)
not sure I follow: paraphrasing Linus Torvalds: "enough talk - show me the code!" :)
so, could you please write some (pseudo)code so we can discuss it?
--
Cheers,
Tomek
Hi, I understand his meaning.
Hi, I understand his meaning. What he said is not to modify JDK's interface. That is in the JDK, there's one general interface, while in you own software, if you want to be easier to use the interface, you could implement three different interfaces based on the JDK's general interface. I think this is what he said. - throat cancer pictures | strep throat | throat cancer
Hi Tomek, of course
Hi Tomek,
of course FluentInterfaces make code more friendly for everyone and this is real essence of this practice.
You can improve your interfaces with some minor refactors and removing create() method (code will be more readable)
- how to achieve this?
I don't tell you but follow this source: http://www.infoq.com/articles/internal-dsls-java
thank you
Thank you, I will definitely read the article you suggest.