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

Made with Slides.com