Writing Your Last For-Loop - Beautiful Code
Writing Your Last For-Loop - Beautiful Code:
I was sitting with some friends the other week and a question came up. Someone asked, ???so, when did you last write a for-loop????
I remember reading this post the other day nodding to myself in agreement about how using closures and Java's new enhanced for loop makes the code a lot cleaner and easier to write, and then this evening a hit a bug in the TestNG IDEA plugin triggered by using this exact "improvement".
The problem with these closure/iterator-less loops in that when you use them you loose state information about the progress of your loop. The problem I hit with the plugin was with adding customer listeners to a test profile - the command line to launch TestNG was being generated as such:
-listener com.theoryinpractise.testng.requirements.RequirementsTransformer;
The problem? The trailing ; delimiter. When TestNG splits the list of listeners two items are returned: "com.theoryinpractise.testng.requirements.RequirementsTransformer" and "" of which the empty string triggers a ClassNotFoundException (one could argue this is a bug in three places - the split returning an empty string, TestNG not even attempting to load a class named "", and me for appending too many delimiters - we're all at fault imho).
But I digress, the code which triggered the problem:
if (data.TEST_LISTENERS != null && !data.TEST_LISTENERS.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (String listenerClassName : data.TEST_LISTENERS) {
if (listenerClassName != null && !"".equals(listenerClassName)) {
sb.append(listenerClassName).append(";");
}
}
javaParameters.getProgramParametersList().add(
TestNGCommandLineArgs.LISTENER_COMMAND_OPT, sb.toString());
}The code is simply appending ";" after the listener class name on each loop without considering we don't want a trailing separate.
The new enhanced for construct in Java 5 hides from the code any means to ask "is there anything to do after this iteration?" or even "what iteration am I on?". Common solutions to this would be to add code to trim the trailing ";", or increment a count variable on each iteration, all ways to reclaim some lost functionality.
So it seems there's still life in the old for loop yet.
Technorati Tags: development, java, testing
Comments (4)
Well, the "enhanced" for loop for iterating through Collections is not supposed to be a replacement for the already existing loop, it's just an add-on.
As you say, sometimes you need a counter, sometimes you need to explicitely make the exit based on a condition, etc... It all depends on what you need :) So the classic for is definitly not going to the void yet ;) On my own, when I need to create a delimited list of Strings without the last delimiter included, i've created a join method, which takes a String array and a delimiter, I just append them to a StringBuilder, and then in the end, I remove the length of the delimiter. Fast enough for my needs ( don't know if you can make any faster ). Cheers KI use iterators in that case. It's easy just to ask iterator.hasNext() and only add the ";" if true.
It's better practice, IMO, to just create an array of strings when going through the loop. (I.e. just do "sb.append(listenerClassName);", without appending the ";".) Then you simply join all the strings up with your chosen delimiter. Less chance of bugs that way, expressly _because_ you don't have to treat the first and last items differently. (It all boils down to lisp in the end.) Something like the following Perl pseudo-code:
my @nonempty = grep { $_ } $data->TEST_LISTENERS; my $params = join(";", @nonempty); (I've made the assumption here that Java has a way of joining a list/array of strings using a delimiter in a fashion similar to the above.)
The Ruby-ish way of doing this is:
delimited_string = TEST_LISTENERS.reject(&:blank?).join(';') Closures allow you to approach iteration-type problems differently.