Refactoring Tip: Take Advantage of Propagating Exceptions

Alonso Del Arte
Nov 14, 2020 · 5 min read
Photo by Thalia Ruiz on Unsplash

Sooner or later, any student learning Java learns that Java exceptions are propagated until reaching a handler. What are the consequences of that?

One consequence is that sometimes an exception only needs to be thrown from one place, and, by refactoring, you can make your program do the same thing and pass all the tests with fewer lines.

The word “propagate” suggests a spreading out in all directions. For example, if you throw a pebble in the middle of a pond, there are going to be ripples, roughly circular, radiating out. A duck swimming in a pond also causes ripples in the water.

But an exception propagates in only one direction through the call stack, up or down, depending on your visualization preference.

For example, suppose you’re running a Java program on the command line. Suppose main() calls subroutineA() which calls subroutineB() which calls subroutineC(), which throws an exception.

As it happens, subroutineC() doesn’t handle the exception. Maybe subroutineB() does. Or if not, maybe subroutineA() can handle the exception. Or if not, how about main()?

If none of those can handle the exception, the program terminates, the Java Virtual Machine (JVM) displays the “stack trace” and shuts down, and you’re back at the command prompt.

Most likely, subroutineC() doesn’t always throw an exception. It probably depends on some condition being true or not. Maybe subroutineB() can check that condition before calling subroutineC().

But it probably doesn’t do us any good for both subroutineA() and subroutineB() to check for that condition if all they’re going to do is throw the same exception as subroutineC().

Of course this is a toy example, and you might be thinking that you would never write such an obvious redundancy in your own Java programs.

A better example is the Fraction class. That class is useful for so many examples. Even if you have no use for fractions in your Java programs, the example should feel more realistic.

A fraction is of course a number like a/b, where a is any integer and b is any nonzero integer. Examples of fractions include −1/2 and 4099/537. The Fraction constructor takes two integer parameters: one for the numerator and the other for the denominator.

Let’s say we’ve already written the constructor, the equals() override, the toString() override, and the addition, subtraction and multiplication functions, and they all work correctly and pass all the tests.

So now it’s time to work on division. The pertinent formula is (a/b)/(c/d) = ad/bc. For example, (1/2)/(3/7) = 7/6. But c/d must not be 0. If it is, the division function should throw an exception.

Let’s say we have a test that requires ArithmeticException for division by zero, with a non-null message, to pass. So we write:

    public Fraction divides(Fraction divisor) {
if (divisor.numerator == 0) {
String excMsg = "Can't divide " + this.toString()
+ " by 0";
throw new ArithmeticException(excMsg);
}
long numer = this.numerator * divisor.denominator;
long denom = this.denominator * divisor.numerator;
return new Fraction(numer, denom);
}

That works well enough, passes the tests. There’s apparently no opportunity for refactoring here.

When division by zero occurs, the stack trace should look something like this:

java.lang.IllegalArgumentException: Can’t divide 1/7 by 0
at fractions.Fraction.divides(Fraction.java:159)
… 28 elided

Another very useful arithmetic function is the reciprocal function. The reciprocal of a/b is b/a. For example, the reciprocal of 3/8 is 8/3. It should be quite easy to implement in the Fraction class.

But watch out: 0 has no reciprocal. Trying to take the reciprocal of 0 should cause an ArithmeticException. So we write:

    public Fraction reciprocal() {
if (this.numerator == 0) {
String excMsg = "Can't take reciprocal of 0";
throw new ArithmeticException(excMsg);
}
return new Fraction(this.denominator, this.numerator);
}

It works, it passes the tests, and there’s no opportunity for refactoring here.

The stack trace for trying to take the reciprocal of zero looks like this:

java.lang.ArithmeticException: Can’t take reciprocal of 0
at fractions.Fraction.reciprocal(Fraction.java:209)
… 28 elided

Let’s review the formula for multiplication: a/b × c/d = ac/bd. As it turns out, division is just multiplying the dividend by the reciprocal of the divisor. We can refactor the division function this way:

    public Fraction divides(Fraction divisor) {
if (divisor.numerator == 0) {
String excMsg = "Can't divide " + this.toString()
+ " by 0";
throw new ArithmeticException(excMsg);
}
return this.times(divisor.reciprocal());
}

All the tests pass. But doesn’t our Fraction constructor also check that the denominator is not zero? It turns out that we have a redundancy here just like in the subroutineC() toy example.

We can delete the zero check from the division function and from the reciprocal function, and rely instead on the constructor to throw an exception in either the case of trying to divide by zero or take the reciprocal of zero.

Of course the exception message will be a lot less specific. But who’s the exception message for and what does it need to say?

Whether the exception occurs in the context of working in an integrated development environment like NetBeans or while using the Fraction class in a REPL (like the local Scala REPL), you’re not going to have any trouble figuring out what the dividend is.

I think that the important thing about the exception message in this case is that it indicate that the problem was division by zero. Any other detail in the exception message is just noise.

So I think that the benefit of fewer lines in the Fraction class outweighs the loss of the more specific exception messages. If you disagree, you should write tests that check more of the exception message besides that it’s not null nor an empty String.

So we rewrite divides() thus:

    public Fraction divides(Fraction divisor) {
return this.times(divisor.reciprocal());
}

And reciprocal() thus:

    public Fraction reciprocal() {
return new Fraction(this.denominator, this.numerator);
}

For both of these we rely on the Fraction constructor to reject zero as a denominator. The stack trace for division by zero is accordingly longer.

java.lang.ArithmeticException: Denominator 0 is invalid or unavailable
at fractions.Fraction.<init>(Fraction.java:482)
at fractions.Fraction.reciprocal(Fraction.java:209)
at fractions.Fraction.divides(Fraction.java:166)
… 28 elided

I got these stack trace quotes from my local Scala REPL, which omits its specific “frames.” Since neither reciprocal() nor divides() handles the ArithmeticException “propagated” from the Fraction constructor, the JVM looks to the Scala REPL to handle the exception, which it does by displaying the lines of the stack trace you presumably have the power to change.

We should keep all our tests that check whether the division and reciprocal functions are throwing exceptions for division by zero. In general, we should tolerate a lot more redundancy in our test classes than in our source classes.

I hope this article has given you a better understanding of what it means for an exception to “propagate” through the call stack. With that understanding, you’ll be better able to recognize refactoring opportunities that involve relying on exception propagation to reduce duplication.

The Startup

Get smarter at building your thing. Join The Startup’s +788K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Alonso Del Arte

Written by

is a composer and photographer from Detroit, Michigan. He has been working on a Java program to display certain mathematical diagrams.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +788K followers.

Alonso Del Arte

Written by

is a composer and photographer from Detroit, Michigan. He has been working on a Java program to display certain mathematical diagrams.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +788K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store