Chasing Programming Wisdom

Ramblings by Tomaz Vieira

When UI becomes API

“Things that are made for humans” (UI) and “things that are made for machines” (API) have fundamentally opposite goals and requirements, and yet it is very common for UI to bleed into the world of API. I find that a developer who doesn't often ask themselves “is this for humans or machines?” is at great risk of falling into the trap of mixing them up. This happens most often with software whose users are also developers, making the distinction between the two worlds even harder.

Before going more in-depth, I'll motivate my point with a few examples of the phenomenon. Some are more obvious than others, but they are all ubiquitous in the industry:

To help prevent further contamination of APIs with UI concepts, I find it useful to explore the differences between them, highlighting their constraints and goals.

Argument Types

UI operates under the limitation that inputs and outputs must be human-readable and human “writable” (often just typable on a keyboard). An API, on the other hand, can take and produce any kind of data, and dealing with human readable data is often inefficient (as it requires parsing) or even damaging (as, say, floats are converted to and from decimal short ascii strings).

This can be seen in, say, an API that takes a hostname as a string instead of an IPV4 address as an array of 4 bytes. Interestingly, the former has multiple levels of UI convenience bleeding into the API; the hostname is easier to type and easier to remember, plus it has to be DNS-resolved, which further depends on global, error-prone configuration, making it even less appropriate as part of an API.

Argument Collecting

Humans can't be expected to produce all arguments required for an operation at once, so UI's will guide users, step-by-step, into producing multiple arguments, which are then collected and used to execute some functionality. Machines, on the other hand, can collect as many arguments as needed before calling a function, and in fact it is much clearer for a developer to have functions explicitly require all their parameters upfront instead of relying on implicit arguments coming from some other source.

An example of this kind of UI bleedover is authentication; It is natural to have authentication information as an implicit, global value when humans are operating a system. On the other hand, an API that does not explicitly specifies credentials as an argument is confusing and error prone, yet very common (e.g.: cookies, tokens in environment variables or config files)

Error Handling

Errors in UI usage happen in front of a human, and therefore can be immediately corrected and retried. In fact, errors in UI usage are expected, and good UIs will try very hard to either interpret the inputs permissively or help the human fix the error. Errors in API usage, on the other hand, happen far away from the developer (in time and space) and are software defects that can't be immediately corrected. Further, being too permissive with the correctness of API parameters might lead to ambiguities or de-facto standards that diverge from the official ones.

I wonder if failing to distinguish these modes of usage was one of the motivations behind “throwing exceptions” in some programming languages; uncaught exceptions don't necessarily crash a program (specially in a REPL) and are easy to fix when they happen in front of the developer (just like UI errors), but are unproductive as part of APIs as they make failure states that should have been prevented in development time less obvious. Similarly, APIs that return overly friendly error messages instead of enforcing correct usage through its arguments are behaving less like good APIs and more as an IDE to developers (so, UI again).

In Conclusion

It's more likely than not that your code will be interacting with other machines as API than directly with humans as UI so I'd say be very clear with what your program needs and very strict with what it will accept. If the word "parse" shows up anywhere, you might have already accidentally ventured into the world of UI.

P.S.: Greyzone: Configuration Files

I haven't had much time to think deeply about configuration files. On the one hand they feel like API; they will be interpreted by machines, they have special fields in special combinations that produce a valid file and it is easy to mess them up by writing them by hand, which leads me to think that they should rather be produced by a utility program (serializing a type-safe struct in code) instead of written by hand.

On the other hand, they could be seen as a DSL, meant to be edited multiple times by humans until they find the settings that achieve their goals. Plus, if the config file is always evaluated at load-time and verbose, polite errors are produced by the reading application, then it feels more and more like UI (at least as long as automatic config-file-generation is not part of the picture)

I'm still leaning towards config files being API rather than UI, but I do see the annoyance of having to fire a specific program every time one wants to modify some configuration.

P.S.: Greyzone 2: Build Scripts

This is an even bigger can of worms than configuration files, and I think it's worth thinking about. I haven't yet, but I'll post if ever I reach some good insight.