Do not throw exceptions if you can possibly avoid it.
In particular, when:
Option
.Either
.Exception
, use Try
.Throwing an Exception
breaks referential transparency.
This can be demonstrated fairly easily. If throw
was referentially transparent, by definition, the two following methods would be equivalent:
def foo1() = if(false) throw new Exception else 2
def foo2() = {
val a = throw new Exception
if (false) a else 2
}
Turns out, however, that they aren’t.
foo1
terminates:
foo1()
// res0: Int = 2
foo2
fails with an exception:
foo2()
// java.lang.Exception
// at repl.Session$App$.foo2(avoid_throwing_exceptions.md:12)
// at repl.Session$App$$anonfun$2.apply$mcI$sp(avoid_throwing_exceptions.md:27)
// at repl.Session$App$$anonfun$2.apply(avoid_throwing_exceptions.md:27)
// at repl.Session$App$$anonfun$2.apply(avoid_throwing_exceptions.md:27)
Scala uses unchecked exceptions, which means that the compiler is not aware of them, and cannot check whether they’re dealt with properly. A function that throws is a bit of a lie: its type implies it’s total function when it’s not.
Let’s take a trivial example:
def foo(i: Int) = throw new Exception
As far as the type checker is concerned, this function is perfectly fine and it’ll happily accept the following:
foo(1)
// java.lang.Exception
// at repl.Session$App$.foo(avoid_throwing_exceptions.md:34)
// at repl.Session$App$$anonfun$3.apply(avoid_throwing_exceptions.md:41)
// at repl.Session$App$$anonfun$3.apply(avoid_throwing_exceptions.md:41)
This blows up at runtime, which is really something we’d like to avoid.
It’s perfectly fine to throw an exception for truly exceptional errors. CPU not found? Critical hard-drive failure? a required resource hasn’t been bundled with the binaries? throw away.
But scenarios such as parse this string into an int should never throw - a String
not being an Int
is not exceptional, it’s the normal case! There are many more String
s that aren’t valid Int
s than the converse.
Linter | Rule |
---|---|
WartRemover |