0

Is this a good practice?

I know I can verify input with an if statement, but I'm trying to understand how exceptions work...

    public static void main(String[] args)  {
            
    int num1, num2;
    boolean ok = false;
    
    while(!ok) {
        try {
            
            num1 = Integer.parseInt(JOptionPane.showInputDialog("Ingresar 1er numero"));
            num2 = Integer.parseInt(JOptionPane.showInputDialog("Ingresar 2do numero"));
            System.out.println(String.format("%.2f",((double)num1/num2)));
            ok = true;
        }
        catch (NumberFormatException e) {
            System.out.println("Error: " + e.getMessage());
        }
        catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
        }
        catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
        }           
    }
  • Java and C# are different languages – Cid Apr 15 '22 at 11:47
  • 1
    [codereview.se] is probably a better place for your question. – 001 Apr 15 '22 at 11:52
  • 1
    Since all catch blocks are the same, and you are already catching Exception, catch only Exception (delete the other two catches) – Bohemian Apr 15 '22 at 11:56
  • Ok, thanks. Also when I try to divide by zero, exception is not catched, "ok" gets true and the loop ends. Console prints Infinity... – Román G-R Apr 15 '22 at 12:03
  • 1
    @RománG-R [Java division by zero doesnt throw an ArithmeticException - why?](https://stackoverflow.com/a/33798761) – 001 Apr 15 '22 at 12:05

1 Answers1

0

No. For many reasons.

Code is pointless

Exception is the supertype of both other exceptions, but the code is the same. You've written:

  • If the animal is a Pomeranian, say "Woof".
  • If the animal is a german shepherd, say "Woof".
  • If the animal is a dog of any kind, say "Woof".

You can just delete the first two lines in that strategy for the exact same effect. When an exception occurs, all 'catch' blocks are checked, in order (except more efficiently than literally checking one by one) - the first catch block whose exception type is what was thrown or a supertype of it, is triggered.

Exception messages aren't reliable

An exception is just an object. It has a type (such as ArithmeticException, and note that the exception type tree is very hierarchical. For example, FileNotFoundException extends IOException extends Exception extends Throwable. (Which is the root type of all things you can throw and catch).

They 'communicate' what the problem is, and do so in ways that are intended for the programmer, and in ways that are intended for the user, but mostly the programmer. All exceptions have the following information available in them, which gives information about what happened:

  • The type (example: FileNotFoundException)
  • The message (example: "Access denied: foo.txt")
  • The stack trace (this shows the call stack and lets you know where in the code it happened)
  • The 'causal chain' - Often an exception occurs because of another exception, and often that other 'underlying' exception is the more interesting one. For example, a SaveGameException occurs, but the underlying reason for that is an IOException that contains the text 'disk full' which is much more useful to fix the problem. Given that a cause can have a cause which can have a cause, and so on - it's a chain.
  • The suppressed list. This is a list of exceptions that also occurred but which are likely less important. It's rarely interesting to look at these, and they occur when using try-with-resources.
  • Custom stuff. As I said, exceptions are just types, and therefore you are free to add more stuff. SQLException in particular has plenty of extremely useful extra's, such as the SQL DB engine's error code.

Of all that data, the only one that's potentially useful to relay directly to a user is .getMessage(), but most exception types (it's just code - its up to the person who writes that code to decide how it works!) do not work that way - their message contains the specifics, meant for the programmer, not the user. They are the parameterized data that 'goes with' the type. In other words, the message makes no sense without the context of what type the exception was, and in general isn't sensible to just verbatim relay to the user.

For example, a FileNotFoundException will have as message just a file name. Telling the user: Error: foo.txt is mostly useless; they have no idea what the heck happened or what that means. Showing them FileNotFoundException: foo.txt, now we're getting somewhere.

Separate issue: Dividing by 0

(double)num1/num2 is parsed and compiled as this:

  • First, take num1, and convert its value to a double value.
  • Then, execute the operation someDouble / someInt (where someDouble is the converted-to-a-double-already num1 variable, and the someInt is the num2 variable). This isn't possible, of course (in pretty much all programming languages, both numeric types need to be identical before you can do any math on em), and the java spec dictates how to resolve that problem: By 'widening' the 'narrower' type. In the spec, int is the 'narrower' type, so...
  • The compiler also adds a separate 'convert this to a double' instruction for num2. We now have 2 doubles.
  • Now the compiler executes the math operation.
  • ... and 0.0 / 0.0 does not throw any exceptions. Unlike in integer math, doubles have magic values for NaN (not a number), Infinity, and negative Infinity. 5.0/0.0 is simply Infinity. 0.0/0.0 is simply NaN. This, your double value is now NaN. This is then passed to the String.format code which renders it as, I think, literally the text "NaN". No exception occurs.

To fix this, you have 2 options: Either first do integer math (so, just num1 / num2, and THEN cast to a double: (double) (num1 / num2)) - but I don't think you want that. In integer math, e.g. 5/2 is just 2 (not 2.5! That would take a double and we're doing all integer math), and THEN you turn that into a double, which is 2.0, and not what you want.

The other option is simply to accept that exceptions is just not how it works.

If you want to print Error: then either check if the double is nan/positive infinity/negative infinity, or perhaps simply check if num2 is 0:

if (num2 == 0) {
  System.out.println("Error: Divisor cannot be 0");
} else {
  // do the math stuff
}

No try/catch needed at all.

So how do I show exceptions to a user?

You either know what the exception is caused by and what the 'message' part of it means and you translate it all into something to show to your user, OR, you just show the entire thing verbatim. Yes, this will look like scary programmerese to the user. There is no generic 'hey, error, can you render yourself into a form that looks friendly to a near computer illiterate user for me'. It just does not exist.

Hiding information from users sucks, so don't be tempted by this. Just show them the scary screenful, but feel free to couch it in a warning first. Something like this:

} catch (IOException e) {
  System.err.println("Saving your game failed!");
  System.err.println("The problem appears to be related to the file system. Perhaps this will help you in figuring out the problem: ");
  e.printStackTrace(); // see note
}

And note that the printStackTrace method is a lie. It actually prints the type, the message, and the stack trace, and the causal chain. If you just want to print the type and the message, System.err.println(e.toString()) does that, but given that this hides the causal chain, that is somewhat likely to hide the actual useful info.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72