4

I want to avoid this ugly pattern:

Long foo = null;
Double bar = null;

try {
  foo = Long.parseLong(args[0]);
  bar = Long.parseLong(args[0]);
} catch (NumberFormatException e) {
  System.out.println(USAGE);
  System.exit(1);
}

doSomethingWith(foo, bar);

I know I can move the initialisations and doSomethingWith into the try block, but imagine:

  • doSomethingWith is actually several lines of conditional logic, many method calls, statements etc.
  • I don't want to be nesting blocks unnecessarily deeply
  • I want to avoid accidentally catch unexpected NumberFormatExceptions thrown from doSomethingWith.

I find the = null and above quite ugly, as well as the use of classes instead of primitives. This ugliness makes me suspect there's a better way to write this code.

My first vision is of course

long foo;
double bar;

try {
  foo = Long.parseLong(args[0]);
  bar = Long.parseLong(args[0]);
} catch (NumberFormatException e) {
  System.out.println(USAGE);
  System.exit(1);
}

doSomethingWith(foo, bar);

but we all know Java throws a couple variable might not have been initialized errors when you try to build that.

What's a better design pattern for this kind of problem? "Better" meaning (for me) does all of this

  • avoids the weird = null
  • avoids unnecessary class wrappers around primitives
  • doesn't nest deeply into the try block
  • doesn't catch/hide exceptions it wasn't meant to

If this is not possible, or if my concept of "pretty" code is wrong, please convince me and show the best that can currently be used.

theonlygusti
  • 11,032
  • 11
  • 64
  • 119
  • why not just put `doSomethingWith(foo, bar);` in your try block? Edit: I guess this counts as "nesting" – sleepToken Dec 05 '19 at 17:16
  • @sleepToken did you read past the first code block? – theonlygusti Dec 05 '19 at 17:17
  • 1
    You could initialize your variables with a default value e.g. -1 and handle this case in your doSomethingWith. – AndiCover Dec 05 '19 at 17:20
  • as @sleepToken said, move `doSomethingWith(foo, bar)` to try block and divide your code in methods to avoid too many nesting – Jassiel Díaz Dec 05 '19 at 17:22
  • All you need to do is encapsulate those validations, and handle condition boundaries, if desired, inside the receiving method: `doSomethingWith`; basically, you receive something (the validations flow would become another method with its own logic) and output another something, or fail the flow. – x80486 Dec 05 '19 at 17:23
  • @JassielDíaz Yes but that would still be *nested* in the original try block, just because you make a method call doesn't mean it isn't nested in the try. My original comment was incorrect – sleepToken Dec 05 '19 at 17:24
  • @AndiCover thanks for the comment. It will be helpful to other readers. Unfortunately in my case `bar` can take on any double value. – theonlygusti Dec 05 '19 at 17:33

2 Answers2

10

The oddity here is that the compiler doesn't know that System.exit(1); will never return. If it knew that, it would be happy.

So all you need to do is give it something that it knows won't let you get from the catch block to after the try/catch. For example:

try {
  foo = Long.parseLong(args[0]);
  bar = Long.parseLong(args[0]);
} catch (NumberFormatException e) {
  System.out.println(USAGE);
  System.exit(1);
  throw new RuntimeError("Make sure the end of the catch block is unreachable");
}

If you need to do this often, you might want to write a helper method, and throw the result of it (which you'll never use). That way you still only have a single line of code for "I want to quit now".

try {
  foo = Long.parseLong(args[0]);
  bar = Long.parseLong(args[0]);
} catch (NumberFormatException e) {
  System.out.println(USAGE);
  throw HelperClass.systemExit(1);
}

...

public class HelperClass {
  public static RuntimeException systemExit(int exitCode) {
    System.exit(1);
    throw new RuntimeException("We won't get here");
  }
}

Another option I've used quite a bit is to define a sort of "User error" exception. You can then catch that at the top level (in main), print any message and possibly display the usage. That way:

  • You can unit test user errors (unit testing System.exit is at least more awkward)
  • You have centralized handling of "what do I want to do if the user made an error" rather than including System.out.println(USAGE) in multiple places
  • You don't run into this definite assignment issue

So your code would then be:

try {
  foo = Long.parseLong(args[0]);
  bar = Long.parseLong(args[0]);
} catch (NumberFormatException e) {
  throw new UserInputException("foo and bar must both be valid integers");
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Hoh I didn't know that. During my quick research I found [this question](https://stackoverflow.com/questions/18099320/the-local-variable-might-not-have-been-initialized-detect-unchecked-exception) which seems to still have this problem even when throwing a new error. However, I would like the `System.exit` behaviour (I'm writing a tool meant for the command-line). I will edit the question to specify that. – theonlygusti Dec 05 '19 at 17:20
  • @theonlygusti: I haven't changed the `System.exit` behaviour. I've just added a line *after* the call to `System.exit`. You'll never actually reach that line of code; it's just there to keep the compiler happy. – Jon Skeet Dec 05 '19 at 17:21
  • 1
    @theonlygusti the question you linked to, they still get this warning because they moved the `throw` to another method and they call that method inside the `catch` block. – Matt U Dec 05 '19 at 17:22
  • 2
    @theonlygust does the method containing your code expect a return value, or is it a `void` return type? If it's `void`, a simple `return;` inside the `catch` after the `System.exit(1)` would also work. – Matt U Dec 05 '19 at 17:24
  • @JonSkeet oh you're right, sorry I missed that. I'm reading too fast at the moment and my brain is too tired to keep up. I'll have a think about this, it doesn't seem very pretty to me either but it might be my favourite option as of yet. What do you think of it? – theonlygusti Dec 05 '19 at 17:25
  • @theonlygusti at the end of the day - isn't this only saving you one line of code? at most two? If we're minimizing ugliness we may as well just print "Hello, world!" and exit... – sleepToken Dec 05 '19 at 17:28
  • @theonlygusti: I'll add another option that you might prefer a bit... – Jon Skeet Dec 05 '19 at 17:34
  • @MattU thanks, I'm using that now. Jon's answer helped me see why it's necessary. – theonlygusti Dec 05 '19 at 18:53
0

I couldn't resist! This is another cleaner (but longer) solution.

// SomeModel(a: long, b: long)
public static Optional<SomeModel> validate(final String[] args) {
  try {
    // Probably some other null checks and things like that here also
    return Optional.of(new SomeModel(Long.parseLong(args[0]), Long.parseLong(args[1])));
  } catch (final NumberFormatException e) {
    System.out.println(USAGE);
    System.exit(1);
  }
  return Optional.empty(); // Or use some other "defaults" for that model
}

public static void doSomethingWith(final SomeModel input) { // ...signature changed!
  // Do your stuff here
}

public static void main(final String[] args) {
  validate(args).ifPresent(it -> doSomethingWith(it));
}

This flow is more clear but it's more verbose. The validations are separated and there is nothing else to do to please the compiler. Having to mode the return might be a drawback, but it pays off later on.

x80486
  • 6,627
  • 5
  • 52
  • 111