Behaving deterministically is almost always a desirable trait when it comes to APIs. The obvious
exception to this rule is when the API is for an inherently random process (e.g.,
Beyond that obvious case, a more interesting exception to this rule is when non-deterministic
behavior is deliberately added to the API contract to prevent unwanted dependency.
Thou shalt not depend on me!
With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.`
That is Hyrum’s Law. It was originally described by Hyrum Wright based on his experience working on Google C++ core libraries.
These type of dependencies are undesirable. They create a dependency to what is meant to be an internal implementation details of your API. They make changing that behavior backward incompatible and making it more difficult to evolving the implementation. Often the API implementor may not even be aware of such implicit dependencies. This means accidental breakage if or when they change the underlying behavior. This is also known as an implicit interface.
A useful technique to combat implicit interfaces is to introduce randomness in behavior of and API. The randomness ensure the underlying behavior cannot reliably be depended upon which prevents implicit interfaces.
Hyrum’s law applies generally to any API surface however it is most relevant to platforms and core shared libraries. The much larger number of API consumers exacerbates this issue. The Web Platform is particularly susceptible to it given its immense popularity (in terms of number of developers and reach), the sheer size of its API surface, and its age (20+ years). Below we look at two example of this technique being employed by the Web Platform.
User Agent Client Hints
User Agent strings are a pack of lie!`.
As Jacob Rossi, program manager for Microsoft Edge, succinctly puts it. 1
User Agent (UA) strings are a notorious example of unintended dependency. UA string was meant to be
a way to detect a browser and its version. However due to the fact that popular features were only
available in specific browsers, UA string quickly became the defacto approach for detecting such
features. This meant that even when other browsers would start supporting the same features website
would not use them. To overcome this, browsers had to start pretending to be each other in their UA
string] to signal their feature parity. And
today we have such fun-looking UA string:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36.
So it is not a surprise that the designers of User Agent Client Hints, the successor for UA strings, have introduced randomness to the API. In particular browsers are expected to add a non-existent random browser brand to their brand list client hint.
Try it yourself. Open Chrome devtools and see the Client Hints in the request header. Mine shows
"(Not;Browser"; v="12" as part of
"Chrome"; v="73", "(Not;Browser"; v="12", "Chromium"; v="73".
When a user site uses this client hint, it can no longer hard code a specific list of browser
brands. This means modifying an existing user agent or even adding a new real browser to the mix
will not break such sites.
GREASE is application of the same technique on TLS headers.
Statelessness in Worklets
Here we are dealing with a different flavor of the same issue. The unwanted dependency that is being addressed is the user code’s ability to access shared state.
These pipelines have strict latency and throughput requirements and are carefully designed to leverage caching and concurrency to meet these requirement. In such and environments having stateless and idempotent code is highly desirable as it enables parallelism, aggressive caching and pre-computation.
and implicit ways for sharing state across function calls (e.g., global scope,
prototype chain,…). This makes it very difficult, if not impossible, to restrict user provided
Worklets use randomness
to enforce the idempotency requirement for user provided code. The basic idea is that the code that
is runs inside a worklet is assigned to to be executed in a randomly selected worklet global scope
(at least two should exist). This prevents storing data on
self. Furthermore the object instance
is also randomly selected which prevents storing state on
this or the prototype chain. This means
that even if user code tries to share state across its several runs those states is not reliably
available and cannot be depended on.
In conclusion, non-determinisms is a powerful tool in your API design toolbox. Use it to combat implicit interfaces and prevent unwanted dependencies. But remember that the best time to take advantage of this tool is before your API is shipped.
From Microsoft’s engineering team presentation on their work to modernize Microsoft’s browser from IE to Edge. UA Strings comes up starting at 9 minutes in the video ↩︎