Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Shit Happens... (v1)

Shit Happens... (v1)

Errors are the nightmare of every programmer. Talking about them leads to passionate discussions (and maybe some trolls) and although programming languages date back to more than 60 years, nobody can tell you what is the best way to deal with those @!& errors. Return codes, exceptions, custom data types, warnings, function incompleteness status codes, ..., this talk present an aerial view of how various languages deal with these errors (C/Ruby/Python/Haskell and maybe Go). Most of the concepts I will be presenting will be applicable to many programming languages (as a lot of them share identical concepts) and code examples will be mainly written in python.

However, let's be realistic: this presentation will not fix your errors, but it will give you some insight on how to train them.

Olivier Hervieu

October 11, 2013
Tweet

More Decks by Olivier Hervieu

Other Decks in Programming

Transcript

  1. but don’t let it hit the fan ... shit is

    so common that a unicode character exists for it! Friday, October 11, 13
  2. otherwise... Ariane 5 inaugural flight, 1996/06/04 500 million dollars lost

    in flames because of .... one uncaught floating point exception SHIT! Friday, October 11, 13
  3. why do I care? • (freely) inspired by some talks

    • (deeply) inspired by real world examples • errare humanum est, perseverare diabolicum (to err is human, persist in error is diabolical) Friday, October 11, 13
  4. diabolical? • as humans we make mistakes. software we build

    is error prone (every single one!) • but everybody wants error-proof software! • bad error handling produces bad programs • hopefully, languages implement systems to manage these errors. how helpful are they? Friday, October 11, 13
  5. 3 of a kind • there is (mainly) three ways

    to manage errors: • return code • exception • monadic error control • every language has (at least) 2 of them Friday, October 11, 13
  6. once upon a C-time • no exceptions • errors are

    managed either by: • return codes • the errno special register • (... and also non-local goto) Friday, October 11, 13
  7. return codes • the good: easy to implement • the

    bad: • can be easily ignored • not universal - for instance operators and constructors (in C++) can’t return a return code • need to be translated Friday, October 11, 13
  8. return codes • developers rely on conventions (some of us

    say documentation ...) • safely managing possible errors returned by each function is painful Friday, October 11, 13
  9. errno, a solution? • Extract of the C99 reference: <errno.h>

    is a header invented to encapsulate the error handling mechanism used by many of the library routines in <math.h> and <stdlib.h>. The error reporting machinery centered about the setting of errno is generally regarded with tolerance at best. It requires a “pathological coupling” between library functions and makes use of a static writable memory cell, which interferes with the construction of shareable libraries. Nevertheless, the C89 Committee preferred to standardize this existing, however deficient, machinery rather than invent something more ambitious. Friday, October 11, 13
  10. safely using errno is hard #include <errno.h> #include <limits.h> #include

    <stdlib.h> void func(const char *c_str) { unsigned long number; char *endptr; number = strtoul(c_str, &endptr, 0); if (endptr == c_str || (number == ULONG_MAX && errno == ERANGE)) { /* Handle error */ } else { /* Computation succeeded */ } } #include <errno.h> #include <limits.h> #include <stdlib.h> void func(const char *c_str) { unsigned long number; char *endptr; errno = 0; number = strtoul(c_str, &endptr, 0); if (endptr == c_str || (number == ULONG_MAX && errno == ERANGE)) { /* Handle error */ } else { /* Computation succeeded */ } } errno should be reset manually Friday, October 11, 13
  11. safely using errno is hard #include <errno.h> #include <stdio.h> void

    func(const char *filename) { FILE *fileptr; errno = 0; fileptr = fopen(filename, "rb"); if (errno != 0) { /* Handle error */ } } fopen may set errno even if no error has occured #include <errno.h> #include <stdio.h> void func(const char *filename) { FILE *fileptr; errno = 0; fileptr = fopen(filename, "rb"); if (fileptr == NULL) { /* An error occurred in fopen(), now it's valid to examine errno. */ if (errno != 0) { /* Handle error */ } } } And so much more: • setlocale • signal Friday, October 11, 13
  12. wait, what? • In addition some methods could return a

    status which is not truly an error • We have all used one of these at least once (more to come soon ...) Friday, October 11, 13
  13. return codes in go • initially, there was no exception

    in go • return codes are a part of the method signature - example with os.Open func Open(name string) (file *File, err error) f, err := os.Open("filename.ext") if err != nil { log.Fatal(err)} go does not define tuple, thus you need to declare a variable to handle the error Friday, October 11, 13
  14. tale of exceptions • Jim Mitchell about exceptions: “termination is

    preferred over resumption; this is not a matter of opinion but a matter of years of experience. Resumption is seductive, but not valid” • It is admitted that exceptions should be used in exceptional cases (is this a truism?) Friday, October 11, 13
  15. exceptions’ summary • exceptions are great when everything goes wrong

    • sadly, they do not cross the process boundaries • in some languages you should take care of your memory • exceptions are typed, catching all of them leeds to many catch/except blocks Friday, October 11, 13
  16. go exception wording • Go implements exceptions, via keywords •

    panic • resume • I think these are carefully chosen terms Friday, October 11, 13
  17. are exceptions really exceptional? • dynamic languages love exceptions... even

    in non exceptional cases • in python, end of iteration is indicated by an exception • (but not end of file...) • novice (and non-novice) programmers abuse this language feature Friday, October 11, 13
  18. ignored(OSError) @contextmanager def ignored(*exceptions): """ >>> with ignored(IndexError): #doctest +ELLIPSIS

    ... ()[0] Ignored exception of type ... """ try: yield except exceptions as e: print("Ignored exception of type `{}` has been caught - msg: `{}`".format(e.__class__, e)) Friday, October 11, 13
  19. exc vs rc: a summary • = : knowing what

    kind of exception will be raised is difficult (most of the time)... • - : ...even if you know what this method could raise (sub-called method can also raise exceptions) • + : exceptions allow function composition Friday, October 11, 13
  20. checked exceptions • once again, it’s hard to know exceptions

    that will be raised in each method • Java introduced “checked exceptions” to solve this issue • I’m not a Java expert, but checked exceptions aren’t widely used, or so it seems! Friday, October 11, 13
  21. back to the real world what is the behavior of

    socket.send in python? Friday, October 11, 13
  22. socket.send socket.send(bytes[, flags])¶ Send data to the socket. The socket

    must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. For further information on this topic, consult the Socket Programming HOWTO. Friday, October 11, 13
  23. socket.send while total_bytes_sent < len(data): total_bytes_sent += sock.send(data[total_bytes_sent:]) • a

    not-so-correct pattern could be: • but what happen if socket is closed? • what happen if total_bytes_send stalled? Friday, October 11, 13
  24. digging ohloh • easy to find code that does not

    check the return value of socket.send • thankfully, it’s also easy to find code that checks this value • lesson learned: use socket.sendall ;-) Friday, October 11, 13
  25. fantasy monads! A monad is a monoid in the category

    of endofunctors, what’s the problem? – James Iry Friday, October 11, 13
  26. some definitions • high-order function: a function that does at

    least one of the following: • take one or more functions as an input • output a function • functor: a type T that obeys to: • composition: T(a∘b) == T(a)∘T(b) • identity: T(id(x)) == id(T(x)) Friday, October 11, 13
  27. monads! • A monad is essentially just a functor T

    with two extra methods: • join, of type: T (T a) -> T a • unit (sometimes called return, fork, or pure) of type a -> T a Friday, October 11, 13
  28. Monadic Error Control • in Haskell: monads Maybe or Either

    • in Scala: Option, Either (and Try and ...) Friday, October 11, 13
  29. use case (the classical div by 0) def divide(x:Int, y:Int):Option[Double]

    = if (y == 0) None else Some(x/y) println(divide(4, 2)) println(divide(4, 0)) Some(2.0) None def divide(i:Int) = outputs: Friday, October 11, 13
  30. use case map! def divide(x:Int, y:Int):Option[Double] = if (y ==

    0) None else Some(x/y) val l = List(1, 0, 2, 4, 0, 2) l.map(x => divide(42, x)) List[Option[Double]] = List(Some(42.0), None, Some(21.0), Some(10.0), None, Some(21.0)) def divide(i:Int) = outputs: Friday, October 11, 13
  31. use case flatMap! def divide(x:Int, y:Int):Option[Double] = if (y ==

    0) None else Some(x/y) val l = List(1, 0, 2, 4, 0, 2) l.flatMap(x => divide(42, x)) List[Option[Double]] = List(42.0, 21.0, 10.0, 21.0) def divide(i:Int) = outputs: Friday, October 11, 13
  32. use case (again) def divide(x:Double, y:Double):Either[String, Double] = if (y

    == 0) Left("Can't divide by 0") else Right(x/y) def test(n:Int) = divide(42, n) match { case Left(why) => why case Right(result) => result } println(test(0)) println(test(1)) outputs: Can't divide by 0 42.0 Friday, October 11, 13
  33. use case (composition) def add(x:Either[String, Double], y:Either[String, Double]) = if

    (x.isRight && y.isRight) {Right(x.right.get + y.right.get)} else if (x.isLeft) {x} else {y} def add2(eX:Either[String, Double], eY:Either[String, Double]) = eX.right.flatMap { x => eY.right.map { y => x + y } } def add3(eX:Either[String, Double], eY:Either[String, Double]) = for { x <- eX.right y <- eY.right } yield (x+y) println(add3(divide(4, 2), divide(3, 4))) println(add3(divide(4, 2), divide(3, 0))) outputs: Right(2.75) Left(Can't divide by 0) Friday, October 11, 13
  34. advantages • “easy” composition allowed by monads • filter or

    preserve your errors without raising exceptions Friday, October 11, 13
  35. implementable in python? • some of us may argue that

    python is not a functional language and does not implement type constructor as in haskell • but it’s all about plumbing, and monad are nothing else than another design pattern Friday, October 11, 13
  36. either in python class Result(object): def __init__(self, result, fail=False): self.result

    = result self.fail = fail def __repr__(self): return "Result:`{}`".format(self.result) if self.result else "Fail:`{}`".format(self.fail) class Fail(Result): def __init__(self, reason=True): Result.__init__(self, None, reason) def f(n): return Result(n) if n > 10 else Fail("too small") l = [10, 20, 4, 0, 60, 8] print map(f, l) Friday, October 11, 13
  37. (my) rule of thumb • exceptions signal unexpected failures so

    you should throw exceptions only when you encounter an unexpected failure! • if a method potentially returns an error, use return codes • if a method potentially returns an error but composition is needed, use monadic error control Friday, October 11, 13
  38. (my) observations • linters (such as pylint) are more helpful

    when using return codes than exceptions • it’s fine to disagree with my rules but please be consistent in your own way of managing errors Friday, October 11, 13