đź’ˇ Semantic API is the strict practice of using literature and various design methods to curate, guide, guard, and improve the understanding of the user of a programming interface such as a library, command line tool, etc.
I have been writing code for a good decade and what I have most struggled with (and convinced that the majority of developers are) was the opaqueness and obscurity of the libraries’ API and other interfaces, like command line tools. The user experience has been studied thoroughly on the user-facing software, but it is rarely a topic of serious discussion among developer communities when it comes to library, SDK, and tooling development.
I’m convinced that the common UX methods cannot be simply applied to developers’ experience (that’s likely why XCode has a poor review). After all, the implicit goal for developers is to learn about the inner workings of what they are building. UX methods commonly strive to hide and abstract away the details in favor of friendliness to increase user retention. We have been applying the same principles to development with “developer-friendly” APIs that glaze over the greasy parts and hide them away by providing opinionated defaults for the users. We have built a magical culture of sloppy development clobbering together bits and pieces of dependencies into software that barely stands up on its own, all to gain short-term productivity.
I believe the missing link to building sustainably is to Semantic APIs and toolings that encourage developers to learn more about the libraries they have downloaded from npm, pip, cargo, or any other package managers. Of course, this inevitably will require some sacrifice to the short-term gain in productivity, but the long-term gains will far outweigh the immediate friction. To design a semantic API, one has to put on a literature hat on top of an engineering one. Every names will have to effectively communicate the intention of itself.
Let me give an example by demonstrating a subset of UInt256
package I’m working on in Rust. It is a zero-dependency, from-the-ground-up implementation of a 256-bit integer type commonly used in Ethereum Virtual Machine. There are a few implementations out there, but they all do the same thing — Glazing over the potential knowledge of n-bit integers and ignoring the potential of users learning. Through their API and method designs it is clear the goal is to help experienced developers get things done without reinventing the wheel — In this case, implementing their very own 256-bit integer type. And thus, that was what I set out to do.
let bytes = [
0xff, 0x45, 0x67, 0x89, 0x0a, 0xbc, 0xde, 0xf1,
0x23, 0x45, 0x67, 0x89, 0x0a, 0xc2, 0x03, 0xd5,
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,
];
let num = UInt256Builder::new()
.with_endian(Endian::Big)
.from_bytes(bytes)
.build();
Note the following:
- Without callingÂ
with_endian(Endian)
 this operation will panic. There is no assumption of a default endianness. You are responsible to learn about and configure it. There is just no shortcut to not understand endianness or the user will quickly hit a roadblock converting between number types. -
from_ bytes([u8; 32])
 takes a bytes array of exactly 32 bytes, not vectors. Fixed-size bytes array ensure correctness of data, but more important, it ensures the user actually understand byte data. Vectors would have completely glazed over this for the sake of friendliness. But real friends expect the best from you and put you on the spot, not letting you off easy so you can have a beer early. - If you want to pass a vector, you will be required to callÂ
with_padding(Padding)
 to pad the "missing space" of the bytes vector provided. This check once again check the user’s understanding of what he’s doing. Check out the following example.
let num = UInt256Builder::new()
.with_padding(Padding::Left(0x00))
.with_endian(Endian::Big)
.from_partial_bytes(vec![0xcd, 0xef])
.build();
This code is equivalent to passing the following raw bytes array to build a UInt256
:
let bytes = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcd, 0xef,
];
However, it is really hard to miss what one is doing when they are required to call with_padding(Padding)
.
Hopefully this is enough to whet your appetite in designing a more semantic API and tooling for your users. They will surely hate you first, but the tough love is going to pay off and your users will appreciate you more as they gain long-term productivity and something immensely more powerful — knowledge — without having to rewrite something from scratch.
Top comments (0)