This excellent book by Steve Krug was a real eye opener when I chanced upon it a few years ago.
I’m not a UI/UX designer by trade or by training, and as a backend developer my appreciation for UI/UX has very little outlet in my day-to-day job either. But still I find plenty of symmetries between UI/UX design and API/library design.
Don’t Make Me Think
When it comes to API design, I always cast my mind back to this talk by Joshua Bloch.
In this talk, Joshua outlined a set of characteristics of a good API:
- easy to learn
- easy to use, even without documentation
- hard to misuse
- easy to read and maintain code that uses it
- sufficiently powerful to satisfy requirements
- easy to evolve
- appropriate to audience
Reading between the lines, the first 4 points essentially boil down to a simple rule – that we should appeal to human intuition, and closely resembles the usability study concept of affordance.
An affordance is a quality of an object, or an environment, which allows an individual to perform an action. For example, a knob affords twisting, and perhaps pushing, whilst a cord affords pulling.
or in other words, the easiest and most obvious way to do something should also be the right way to do it.
Just as a great UI guides you towards your goal – be it buying a flight ticket or finding a hotel – a great API design should also guide you towards meeting your data needs.
However, just as in the real world, Software is full of failure examples…
and some advices in Software are just as applicable in UX too.
Explicit is better than implicit.
Flat is better than nested.
- Zen of Python
Just as UX experts can conduct UX studies by watching users interact with a UI and tracking their eye movements, etc. Perhaps as an industry, we should also conduct usability studies on our API offerings? We can measure how quickly developers are able to successfully perform a set of tasks that require them to interact with the API in various ways. For instance, how many attempts it takes them to correctly retrieve the data they’re after.
Type Directed Development
In a statically typed language, good use of types goes a long way towards expressing and communicating the assumptions and limitations we as API designers have made. Many functional languages such as F# are very well suited in this regard thanks to their power type systems.
For inspirations on what’s possible and how to apply them in practice, see the following talks.
Scott Wlaschin on DDD with the F# type system
Edwin Brady – State, Side-effects and Communication in Idris
Edwin Brady – Verifying Stateful and Side-Effecting programs using Dependent Types
Joshua also talked at length on a number of principles you should follow to help you design a good API. Whilst the talk is based on Java, many of these principles would be familiar to any functional programmer too.
- API should do one thing and do it well
- API should be as small as possible but no smaller
- Implementation should not impact API
- Minimize accessibility of everything, maximize info hiding
- Minimize mutability
- Subclass only where it makes sense
- Design and document for inheritance or else prohibit it
- Don’t violate the principle of least astonishment
- Fail fast – report error ASAP after they occur
- Use appropriate parameter and return types
- use most specific possible input parameter type
- don’t use strings if a better type exists
- Use consistent parameter ordering across methods
- Avoid long parameter lists
- Avoid return values that demand exceptional processing
- zero-length array, not null
Many others have said before that good OO is very similar to FP, but the problem remains that mainstream OO languages such as C# and Java doesn’t do a good job in guiding you towards writing good OO.
As software developers, we’ve all been taught that maintainability is important but so often I find it difficult to think about maintainability in clear, unambiguous ways since complexity itself is in the eye of the beholder. We have invented many tools and metrics to help us measure complexity, but none gives us a complete and accurate view of it.
Furthermore, software engineering is not only an engineering activity (in fact, Alan Kay argued that our standards and practices fall way short of those expected of an engineering discipline) but also a social activity.
Organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations
- M. Conway
Adam Tornhill also demonstrated a number of techniques that use our commit histories to unveil hidden complexities that arise from the way people work together.
If you have dependency between software components developed by different people, then you essentially have dependency on people. Just as the communication between software components is a source of complexity, so too is the communication between people responsible for these software components.
Complexities can creep into your API in any number of ways, e.g.
- complexities with the underlying domain leaking through
- impedance mismatch between the API’s designed use case and users’ actual needs
Having worked with no less than 25 of AWS’s APIs, we have encountered a number of such complexities. And since we are not in control of the APIs themselves, so we do the best we could to manage and mitigate these complexities with the use of DSLs.
Take a look at the following slides on some of these complexities and the approaches we took.