Saturday, June 15, 2013

What's dirtier than comments? Exceptions!

I postulate that comments are a code smell.

Craig Buchek suggests a possible counterexample:
# Let this raise its exception if the fields don't exist as expected.
user = c.get_setting('username')
How else could one express the intention here, he asks?

I say, the output of a function should be expressed in its return value. Exceptions are cheating -- they bust out of normal flow control. That's a surprise, and surprises are to be avoided. No surprises, no comments needed.

Why is it throwing an exception? get_setting must be mucking around in the environment. The fetching of a setting from outside a program is a side effect, a dependency on the world around program execution, a bummer. It's necessary, so let's do this carefully.

Instead of throwing an exception, get_setting could return the failure information to its caller. This is a classic use for Either. In Scala:

def get_setting(settingName: String): Either[Error, String]

This method signature reports that you will get back the setting you want OR information about the error. (The convention is that Either contains the desired value on the right or the failure information on the left. In the type parameter list, that puts the failure type first, which is unfortunate.)
Then what does the calling function do with that Either it gets back? We don't like a whole bunch of "if it worked, do this" statements; that's not much cleaner than try/catching. Let's fill in some hypothetical code around Craig's snippet.

def getAuthorizationLevel(c: SettingGetter, 
                 authorizer: Authorizer): AuthLevel = {
    # Let this raise its exception if the fields don't exist as expected.
    user = c.get_setting('username')
    authorizer.getAuthorizationLevelForUser(user)
}

Here, the authorizer method can also throw an exception. If we replace exception-throwing with explicit error handling using Either, we get:

def getAuthorizationLevel(c: SettingGetter, 
                 authorizer: Authorizer): Either[ErrorAuthLevel] = {
    val possibleUser = c.get_setting('username')
    val possibleAuthpossibleUser.right.map{ user => 
      authorizer.getAuthorizationLevelForUser(user) }
    possibleAuth.joinRight
}

OK. This is cryptic if you aren't used to working with Either. (I had to look up joinRight.) What's happening?

right.map says, "If we have a string, replace it with the output of this function." We take that string (user) and turn it into... what? Here's the signature for the next method call:

def getAuthorizationLevelForUser(user: String) : Either[Error, AuthLevel]

This output will replace the user string. We took possibleUser, which was either an error or a string, and now in possibleAuth we have either an error (from user) or an error (from auth) or an AuthLevel. It's Either[Error, Either[Error, AuthLevel]]. Yuck!

joinRight says "If we have an Either on the right, return that instead of this one." We get an Either[ErrorAuthLevel]; the Error could be from getting the user setting or from getting the authorization level. joinRight scrunched the two errors into the same place.

That feels like a lot of work to avoid throwing an exception. Yet, the comment isn't necessary because the possibility of failure is explicit in the return values. There's no if/else or try/catch blocks; instead, processing based on the user setting is moved into the context of the Either, so that we fetch the auth level only if we got that user string.

Now the possibility of failure is returned explicitly to the caller of getAuthorizationLevel. If they want to report an error back up the chain, great. If they log the error and then use a default authorization level, even better. In case you're curious, that would look like this:

val possibleAuth = getAuthLevel(settingGetter, authorizer)
possibleAuth.left.foreach(error => log(error))
val authLevel: AuthLevel = possibleAuth.right.getOrElse(AuthLevel.GUEST)

Errors are data, too. Return them to your caller in the same flow of control as the data you were hoping to supply.

-----------------------------------
Scala code for this post: https://gist.github.com/jessitron/5788968

@marioaquino agrees with me: http://marioaquino.blogspot.com/2013/06/styles-of-handling-conditional-logic.html

So does @heathborders: http://heath-tech.blogspot.com/2013/06/all-smart-kids-are-writing-blog-posts.html

and @Adkron, although he loves OO more: http://dirtyinformation.com/blog/2013/06/15/exceptional-comments/

... now I hope @CraigBuchek will chime in with an opposing point of view because we're running out of argument here.

and here's a timely one from @josefusbarnabus that agrees comments are evil: http://heath-tech.blogspot.com/2013/06/all-smart-kids-are-writing-blog-posts.html

and alonzophoenix says types are better than comments: "type annotations—like pretty much everything else in programming language source code—are best thought of as more for human consumption than for the compiler." http://archontophoenix.blogspot.com/2013/04/no-comment.html

Saturday, June 1, 2013

What FP taught me about OO: Liskov Substitution Principle explained

TL;DR - functional programming taught me that LSP is a special case of PLS: Principle of Least Surprise.

One thing that bugs me while reading Java: I'm reading along, come to a method call, ctrl-click on the method name, and get a list of implementations of the interface. IntelliJ can't tell me exactly what will run at this point in the code. Java has dynamic dispatch: it decides at runtime which implementation to run.

Functional style has a different concept to offer instead of Java's interface. It's called "typeclass," which is incredibly confusing. Please accept that this idea has little to do with "type" or "class."

While an interface says, "This class can perform this method," a typeclass says, "There exists something that can perform this method on this class." (using 'class' loosely) An interface is baked into a class, while a typeclass instance is provided separately.

With typeclasses, the method implementation isn't attached to the runtime class. Instead, the compiler digs up the typeclass instance, based on a declared type instead of a concrete runtime type. This isn't flexible in the same way as OO's dynamic dispatch, but it has the advantage that we can tell by looking at the code exactly which implementation of the method will execute.

This gives us better ability to read the code and figure out what will happen in all circumstances, without using the debugger. Functional programmers call this "reasoning about code."

In Java, how can we make statements about what the code will do when we don't know exactly what code will run? One answer is: Liskov Substitution Principle. This OO design principle says, If you inherit from some other class or implement an interface, then be sure your class meets all expectations people have of the superclass or interface. The compiler enforces that our methods have the right signatures, while LSP demands that our implementation have the same invariants, postconditions, and other properties implied the documentation of the superclass or interface.

For instance, if you create an implementation of List with a .length() method that sometimes returns -1, you violate the expectations people have for Lists. This surprises people. Surprises are bad. Surprises stop us from accurately predicting how code will execute.

Therefore, the Liskov Substition Principle says, "Don't surprise people." If the documentation of List implies that length() will return a nonnegative integer, always do that. Also make sure that your List is 0-indexed like everyone else's. To implement an interface is to take on all the implicit and explicit promises made by that interface. That's LSP.

In Haskell or Scala, we can use typeclasses and be certain what the code will do at runtime. In Java, we can have the flexibility of dynamic dispatch and still have some knowledge of what will happen at runtime as long as we respect the Liskov Substitution Principle.