Skip to content

Attractiveness

sandal edited this page Dec 14, 2011 · 16 revisions

Aesthetic concerns are more than skin deep when it comes to developing high quality software. When used correctly, attractiveness can be used as a tool to influence the way users interact with the features available within a system. As an example, we can take a look at Vectorize, a proof-of-concept wrapper over the C-based Cairo graphics library.

Vectorize consists of two APIs: a low level wrapper that is very similar to Cairo's C API, and a high level API which is meant to look and feel like a native Ruby library.

Using the low-level API, here is how you'd use Vectorize to generate a simple 400x400 PNG file that contains a circle of radius 150 centered at the middle of the image.

surface = Vectorize::Cairo.image_surface_create(:ARGB32, 400, 400)
layer   = Vectorize::Cairo.create(surface)

Vectorize::Cairo.arc(layer, 200, 200, 150, 0, 360)
Vectorize::Cairo.stroke(layer)

Vectorize::Cairo.surface_write_to_png(surface, "circle.png")

Vectorize::Cairo.destroy(layer)
Vectorize::Cairo.surface_destroy(surface)

While this code isn't pretty, it is still somewhat understandable, even without documentation. You might need to look up things like what the significance of the :ARGB32 symbol is and also why you need to explicitly call destructor methods, but for the most part you should be able to get a rough idea of how this code works without digging any deeper.

While the low-level API can be used to get the job done, it does feel pretty noisy for high level tasks. This is why Vectorize provides a layer of syntactic sugar that can make the same work feel a whole lot less ugly. The code below shows how you'd render the same image via the Ruby-inspired API.

drawing_options = { :basename => "circle", :formats => [:png], 
                    :width    => 400,      :height  => 400 }

Vectorize.draw(drawing_options) do |v|
  v.circle(:center => Vectorize.point(200, 200), :radius => 150)
  v.stroke
end

Using this interface, there are fewer questions to ask about what is going on. Every bit of code seems directly related to the task at hand, and some low level operations have been hidden from the user. In the previous example, we had actually been passing around thinly wrapped C pointers, and setting very low level things such as the image's pixel format. In this example, those details are hidden from view because most users won't need to care about them.

Using the more attractive API is also safer for the user. Because Vectorize is a wrapper over a C library, it actually needs to handle things like memory management. In the low level API, forgetting to explicitly destroy the objects referenced by the layer and surface pointers could lead to memory leaks. However, the Vectorize.draw method which forms the high level API hides those details from the user and cleans things up after the code block is executed. Because most Ruby users aren't going to be thinking about manual memory management day to day, this presents them with the sort of interaction they're more familiar with, thus making it safer and more attractive.

While it is only a proof of concept tool, Vectorize provides a good example of striking a balance between flexibility and aesthetically pleasing syntax. It builds its own syntactic sugar on top of a simple low level API, but then makes that same low level API available to users who want to break away from the "happy path". This frees up the high level API to choose sensible defaults and hide certain features that won't be commonly used, resulting in a more cohesive tool.

Whenever there is a tension arising from wanting to expose low level bits of functionality while simultaneously keeping easy things easy for your users, this general pattern can be applied.


Turn the page if you're taking the linear tour, or feel free to jump around via the sidebar.