Learnability

sandal edited this page Dec 13, 2011 · 12 revisions

Learnable software makes it easier to gain a solid understanding of how things works without having to rely too heavily on documentation. While this aspect of software quality manifests itself in various different ways depending on the context, establishing clear and consistent patterns is a big part of designing learnable tools. To illustrate this point, we can look at a change that was made in Prawn to make its graphics API more learnable.

When Prawn was first developed, it did not have a consistent API for its primitive drawing operations. While the data involved with drawing a rectangle, circle, and ellipse were somewhat similar, the naming conventions and arguments for these methods varied, as shown below.

Prawn::Document.generate("drawing.pdf") do
  stroke do
    rectangle  [200,200], 100, 50
    ellipse_at [200,200], 30, 10
    circle_at  [200,200], :radius => 20
  end
end

In the process of cleaning up and stabilizing our API, we noticed that while each of these API calls looked reasonable on their own, they needed to be learned and memorized independently. Our naming conventions and arguments were not helping with this task, but actively working against it. The naming convention ellipse_at and circle_at nicely indicate that the first argument is a point, but make it easier for users to misremember the rectangle API as rectangle_at. Similarly, the width and height arguments between rectangle and ellipse matched up with each other nicely, but the circle_at method switched to keyword-like arguments, explicitly specifying a :radius value. The fact that these methods were sort of similar to one another but had subtle differences was a sign of a problem, and we decided to resolve it by making them all consistent. The code below demonstrates what we replaced the old API with:

Prawn::Document.generate("drawing.pdf") do
  stroke do
    rectangle [200,200], 100, 50
    ellipse   [200,200], 30, 10
    circle    [200,200], 20
  end
end

In this code, the naming convention is simply the name of the shape you want to build, removing the somewhat superfluous _at suffix. Additionally, all three methods ordered its arguments as a point followed by its dimensions. In the case of a circle, specifying two dimensional values does not make sense, so the third argument is simply omitted. This is a much smaller adjustment for the user to remember than it would be to ask them to memorize that circle takes hash arguments while ellipse does not.

Through this refactoring, we realized that reducing the number of special cases in our API design was a way to increase learnability. While it may result in individual API calls being slightly less aesthetically pleasing, the overall impact on the user is a net positive one.

The important thing to remember is that learnability and operability can occasionally be at odds with one another, and the relation between them is a balancing act. Don't forget the lesson that Clippy taught us.


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

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.