Exceptions Considered Harmful
By Gregg Hernadez and Casey Allred
Gregg Hernandez
- Lucid Software
- UVU, BS Computer Science
Casey Allred
- Fanzz
- UVU Adjunct Professor
- UVU, BS Computer Science
Exceptions == GOTO
- Potential for spaghetti code
- Jumps to mostly any location in code without warning
- Modern GOTO requires you to stay in current scope
Uncaught Exception: SlideNotFound
Message: "Should never happen ..."
Pitfalls
- Overuse
- Wildly specific
- Still probably okay for exceptional circumstances
Exceptions are the primary error-handling mechanism employed by many widely-used languages. They are also a side-effect that makes a liar of the type system, and makes local reasoning about code far more difficult. They represent an undeclared method result smuggled through a back-channel separate from its declared return type. Furthermore, they transitively become an undeclared result of anything that calls that method, and anything that calls that, and so on. Trying to reason about the correct behaviour of code becomes very difficult, since the return type can no longer give you enough information. Exceptions kill modularity and inhibit composition.
-Ken Scambler (The Abject Failure of Weak Typing)
http://bit.ly/1rTezGh
Alternatives
- Multiple Return
- Option/Maybe
- Either/Result
- Validation
Multiple Return (Go)
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
func Open(name string) (file *File, err error)
Signature
Use
Option (Rust)
fn main() {
let x = does_work(1)
.and_then(does_not_work)
.or_else(|| -> Option<i32> { Some(42) });
println!("{:?}", x);
println!("{}", x.unwrap_or(10));
}
fn does_work(i: i32) -> Option<i32> {
Some(i)
}
#[allow(unused_variables)]
fn does_not_work(i: i32) -> Option<i32> {
None
}
// Output
// Some(42)
// 42
Option (Scala)
def main(): Unit = {
val x = doesWork(1)
.flatMap(doesNotWork)
.orElse(Some(42))
println(x)
println(x.getOrElse(10))
}
def doesWork(i: Int): Option[Int] = {
Some(i)
}
def doesNotWork(i: Int): Option[Int] = {
None
}
// Output
// Some(42)
// 42
#[allow(unused_variables)]
fn main() {
let x = does_work(1)
.and_then(does_not_work)
.or_else(|e| -> Result<i32, &'static str> { Ok(42) });
println!("{:?}", x);
println!("{}", x.unwrap_or(10));
}
fn does_work(i: i32) -> Result<i32, &'static str> {
Ok(i)
}
#[allow(unused_variables)]
fn does_not_work(i: i32) -> Result<i32, &'static str> {
Err("didn't work")
}
// Output
// Ok(42)
// 42
Result (Rust)
def main(): Unit = {
val x: Either[String, Int] = doesWork(1).right
.flatMap(e => doesNotWork(e).right)
.fold(
successValue => Right(successValue), // on Right
errorValue => Right(42) // on Left
)
println(x)
println(x.right.getOrElse(10))
}
def doesWork(i: Int): Either[String, Int] = {
Right(i)
}
def doesNotWork(i: Int): Either[String, Int] = {
Left("didn't work")
}
// Output
// Right(42)
// 42
Either (Scala)
def main(): Unit = {
val f = fails()
val concatF = f(_+_+_)
println(concatF)
val s = succeeds()
val concatS = s(_+_+_)
println(concatS)
}
def fails(): Validation[String, Int] = {
1.success[String] |@| ":(".failure[Int] |@| ":(".failure[Int]
}
def succeeds(): Validation[String, Int] = {
1.success[String] |@| 2.success[String] |@| 3.success[String]
}
// Output
// Failure(":(:(")
// Success(6)
Validation (Scala with scalaz)
Composition
Exceptions ...
- Out Of Memory
- Third Party Libraries
- ???
Working with exceptions
def main(): Unit = {
val tried = Try(runsOutOfMemory())
val result = tried match {
case Success(v) => v * v
case Failure(e) => 42
}
println(tried)
println(result)
}
def runsOutOfMemory(): Int = {
throw new OutOfMemoryException()
}
// Output
// Failure(OutOfMemoryException)
// 42
Questions?
- Casey: sbditto85@gmail.com
- Gregg: gregg@lucidchart.com
www.fanzz.com
golucid.co
Exceptions Conciserded Harmful
By sbditto85
Exceptions Conciserded Harmful
- 1,022