I’ve recently been working on the talking puffin project, particularly on the lower level twitter API. At the plumbing level, we want a class that allows us to fetch XML from a URL. In Java doing this sort of stuff usually involves quite a few checked exceptions. The interwebs are flakey, and ignoring that fact usually leads to really bad things happening. However Scala doesn’t have checked exceptions… and that’s a nice thing for flexibility, conciseness, etc. So what to do?
My two basic approaches were
- Throw exceptions on errors. this allows more concise code when you don’t care about the errors (maybe you’re dealing with them higher up)
- Use case classes with subclasses indicating successful and unsuccessful execution. This forces you to deal with error conditions right away, which may lead to more stable code
Let’s take a look at implementations of both. First we set up a simple base class to deal with HTTP plumbing. This just provides a way to open a URL and a helper method to read the error stream into a string.
class HttpBase{ /** * Open the specified URL and return a tuple containing the corresponding * HTTP response code and HttpURLConnection */ def openConnection(urlStr:String) = { val url = new URL(urlStr) val conn = (url.openConnection).asInstanceOf[HttpURLConnection] val responseCode = conn.getResponseCode() (responseCode,conn) } /** * Utility to open a connection's error stream and read it into a string' */ def getErrMsg(conn:HttpURLConnection) = { var errMsg = "" val reader = new BufferedReader( new InputStreamReader(conn.getErrorStream())) var line = reader.readLine() while(line != null){ errMsg += line line = reader.readLine() } errMsg } }
Now let’s try to implement a class that actually fetches XML, and throws an exception if errors are encountered.
case class HttpException(val msg:String, val code:Int) extends Exception /** * Provides a getXML method that fetches XML from a URL. * Throws an exception if any errors are encountered */ class HttpXMLExceptions extends HttpBase{ def getXML(urlStr:String):Node = { try{ openConnection(urlStr) match { case (200,conn) => XML.load(conn.getInputStream()) case (code,conn) => throw HttpException(getErrMsg(conn),code) } }catch{ case e => throw HttpException(e.toString,-1) } } }
And here’s a usage sample. This is concise, but (by design) we aren’t forced to think about nasty things like 404s, 401s, dropped connections, broken pipes, and all sorts of other networky hobgoblins.
val excs = new HttpXMLExceptions() val url = "http://twitter.com/statuses/public_timeline.xml" val content = excs.getXML(url)
Let’s try to force people to think about them. Instead of throwing an exception, we can define a hierarchy of case classes for our responses, like so.
case class Response case class Success(val content:Node) extends Response case class Error(val msg:String,val code:Int) extends Response
Our method will return a type of Response. If our request is successful we’ll get a Success object that has a content field. If we run into any errors we’ll instead get an Error object back with a response code and error message from the response body. This is similar to using Scala’s Option type, however this allows us to provide information on failure instead of simple returning the None instance. Here we go…
/** * Proveds a getXML method that fetches XML from a URL. * Always returns a Response object, regardless of success/failure */ class HttpXMLMatches extends HttpBase{ def getXML(urlStr:String):Response = { try{ openConnection(urlStr) match { case (200,conn) => Success(XML.load(conn.getInputStream())) case (code,conn) => Error(getErrMsg(conn),code) } }catch{ case e => Error(e.toString,-1) } } }
This is pleasantly similar in implementation… However usage looks quite a bit different…
val matches = new HttpXMLMatches() val url = "http://twitter.com/statuses/public_timeline.xml" val content = matches.getXML(url) match { case Success(node) => node case Error(msg,code) => <error><msg>{msg}</msg><code>{code}</code></error> }
This is quite a bit more verbose. However it does send a message that developers need to be conscious of the potential error case. Let’s look at side by side usage when we add error handling for an unknown host
val excs = new HttpXMLExceptions() val badUrl = "http://nohost.twitter.com/statuses/public_timeline.xml" val noContent = try{ excs.getXML(badUrl) }catch{ case HttpException(msg,code) => <error><msg>{msg}</msg><code>{code}</code></error> } val matches = new HttpXMLMatches() val noMatchContent = matches.getXML(badUrl) match { case Success(node) => node case Error(msg,code) => <error><msg>{msg}</msg><code>{code}</code></error> }
This usage actually looks pretty similar too. This is due to the fact that a try/catch evaluates like a pattern match. In the exception case your error conditions are separated from the main logic, which some may like and some may not.
The primary difference is that using the Response class hierarchy forces the user to consider the the unsunny day scenarios. I don’t think there’s a black and white rule on when to use either approach, but case classes provide you a way to expose error conditions more visibly than unchecked exceptions. In the talking puffin project I think we’ll stick with exceptions, but it’s nice to have options.