Explicit is better than Implicit

Explicit is better than Implicit

We spend much more time reading code than writing it, so it's better to aim for ease of comprehension, rather than attempt to abbreviate,  or hide complexity where it can't easily be found. While many frameworks available today do a very good job of taking care of the boring repetitive side of web development, sometimes they encourage hiding too much code behind magic, rather than just writing out explicitly what we want to happen.

Implicit behaviour in Rails

Rails has been criticised in the past for its magic - from magic finder methods to hidden dependencies in controllers and models. Many of these problems have been addressed in more recent versions (for example deprecating find_by_ methods), and there is a trade-off between a quick learning curve, and later problems caused by the subtle interactions with code you know nothing about.

The find_by_xx methods were implemented by catching calls with method_missing and dispatching to a newly created method, which wasn't great for performance, and meant that potentially any combination of columns could be allowed - a huge rise in complexity for not much gain as opposed to being explicit in calls to finders. They've now been taken out for Rails 5.

The acts_as pattern is another example of this - early in the development of Rails, plugins named acts_as_xx become popular. These gems have fallen out of favour somewhat, because  they abstract away so much functionality into a third party gem that it's harder to extend and harder to understand the flow of logic in your app. If your models acts_as a few different gems, which one includes the functionality involved in a certain method call?

Another example of implicit behaviour in rails are controller actions - before the action lots of setup occurs, modules are imported (not obvious in the controller file), context is set up, potentially several before actions are called (this is similar to the concept of middleware in Go), and then finally your controller action runs, at which point you don't even have to render, as rails will automatically render for you afterward. This makes it harder than it should be to trace the flow of execution in a rails app - if gems like cancan are requesting your models before you even hit your action in order to check authorisation, you might be puzzled by database calls you knew nothing about when an error occurs.

Middleware in Go

Middleware in Go suffers a similar problem, in that the behaviour is hidden by a string of function calls wrapping each other, and it can become hard to decipher exactly which code is involved in processing a request without visiting several different middleware packages to trace execution. Of course there are advantages to an implicit style - code is terser, actions which are performed before every request anyway no longer clutter up the handler code, but taken too far it can led to a series of handlers with little to nothing in them, wrapped in other handlers which respond to your request in different ways.

Taken too far, the notion of implicit behaviour and magic in frameworks can lead to application code which is hard to debug because all the complexity has moved out of the application and into supporting packages (third-party or otherwise), but it's still there, just hidden from view and scattered around all the dependencies of your app.  That's why in Fragmenta, although tasks like authentication, queries and view rendering are taken care of for your application, handler functions  explicitly query the database,  check authorisation, and render the view – using libraries as helpers, rather than letting them handle state completely. That's also why I prefer generating default code which can be adapted or replaced for simple CRUD actions - while generating code might seem anathema, it is often a good way to impose predictable structure on resources, while allowing for the inevitable requirement of customisation later on.


Discuss at Golang News

Read More

http Handlers in Go

http handlers in go

Go servers using the standard library tend to use the Handler interfaces and HandlerFunc type:

type Handler interface { 
    ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request) 

Context

The standard http HandlerFunc accepts a ResponseWriter interface, and a pointer to a Request type. This means if you nest handlers, they have no way of communicating with each other, and there is no simple way to store and pass other information to your handlers, for example a logged in user,  information on the matched route and parsed parameters from the url, the state of the app config, etc. Various suggestions have been made to handle this, from a global context to request-specific contexts. Other approaches (as in Martini) eschew this handler, and use reflection to allow you to use any function signature for handlers - unfortunately this throws out most of the benefits of the go type system, and makes your app more complex instead of using a simple defined handler type.

Errors

The standard HandleFunc doesn't have any return value and you are expected to handle errors within each handler as they come up. In middleware handlers you have no way of signalling that a request has been handled, and no further handlers should tackle it.

The treatment of errors in web development is often very similar, so you'll find yourself either repeating calls to http.Error then return or repeating calls to render an error template. Often you'll want to present errors in a standardised format, and send more information like the http status code, a stack trace, etc., which requires a bit more information than http.Error or the error interface provide. For this reason I think it's better to return an error type from HandlerFuncs so that the Handler can recognise that a given request has been handled successfully, or an error has occurred.

Another approach

Fragmenta offers another approach to both of these problems by creating a context for each request and passing it to a defined Handler function type, and requiring handlers to return an error value. Handlers in fragmenta are of the form:

type Handler func(router.Context) error

Where router.Context is an interface which stores the Writer and Request values for the request, and error is an error conforming to the error interface. The error may in fact be a more complex error of type router. StatusError, which stores a status and about the callsite for debug, and means we can display more detail for errors in development mode, and a friendlier message to users in production. This lets us serve requests with the minimum of boilerplate around fetching parameters etc, and return specific http errors with a status code and other details to be rendered by a user-defined error function which is common to all handlers.

    func HandleShow(context router.Context) error {

      // Find the tag 
      tag, err := tags.Find(context.ParamInt("id"))
      if err != nil {
        return router.NotFoundError(err)
      }

      // Authorize access to this tag 
      err = authorise.Resource(context, tag)
      if err != nil {
        return router.NotAuthorizedError(err)
      }

      // Serve template 
      view := view.New(context)
      view.AddKey("tag", tag)
      return view.Render()
    }

Discuss at Golang News

Read More