In Java they're not really known as GOTO
statements and are rather referred to as Branching Statements, but I find that the former term is a bit more indicative of what they actually do.
Regardless, I've been designing some code recently which has me in a bind. On the one hand, I've been taught to avoid avid use of such statements because they make code both non-linear and more difficult to follow. They of course have excellent uses, and I'm not one of those puritans who says that they should be discarded at any cost.
On the other hand, there's also a common tenet that says I ought to avoid deep nesting of conditions and loops if it's possible and reasonable to do so.
So here's my conundrum. I'm hoping someone can guide me and give me some solid reasoning and best practices advice, because apparently my brain can't handle the conflicting signals.
Several times through my code, I have a for()
loop which is something like the following (where lines
is a List<String>
and values
is a Map<String, String>
):
for(String line : lines) {
if(line.charAt(0) == '#') {
LOGGER.debug("Skipping commented line in file.");
continue;
}
line = line.trim().toLowerCase();
String[] pair = line.split("=");
if(pair.length != 2) {
LOGGER.error("Skipping malformed line in file: " + line);
continue;
}
pair[0] = pair[0].trim();
pair[1] = pair[1].trim();
if(values.containsKey(pair[0])) {
LOGGER.debug("Value is already assigned.");
continue;
}
values.put(pair[0], pair[1]);
//...and so on with still more processing
}
The reason I gave a little bit more in this code snippet than is strictly required to understand the use case is because I think the fact that the for
loop isn't really "tight" and that it performs a lot of processing is relevant to the style choice. To me, it makes the code feel somewhat sloppy and messy to have multiple areas scattered throughout it which bump the reader back to the beginning of the loop. In other words, it feels a lot like the good old GOTO
that everyone loves to hate on.
So my other design option is to write it with the following style:
for(String line : lines) {
if(!line.charAt(0) == '#') {
line = line.trim().toLowerCase();
String[] pair = line.split("=");
if(pair.length == 2) {
pair[0] = pair[0].trim();
pair[1] = pair[1].trim();
if(!values.containsKey(pair[0])) {
values.put(pair[0], pair[1]);
//...and so on with still more processing
}
else {
LOGGER.debug("Value is already assigned.");
}
}
else {
LOGGER.error("Skipping malformed line in file: " + line);
}
}
else {
LOGGER.debug("Skipping commented line in file.");
}
}
Both of these techniques/styles makes my eye twitch spastically for various reasons, and I keep rewriting my code to fit whichever rationalization wins out in the current moment. I also think that if you definitively choose one as "The Style" to always go with, it's trivial to extend the above code snippets to the point where it makes more sense to go with the other.
Does anyone have a more definitive statement of when to use one style over the other? Or even just some good reasoning to apply to a situation to make sure you are following the best rule and coding practice? Any general tips, tricks, and advice for dealing with these sorts of situations?