Windows API design is samples-driven. This means that when a team proposes an API, we want them to demonstrate how to use that API with a sample code fragment. We do this so that we can evaluate the ergonomics of the API and see how well it fits with related features.
The code in the API sample often exposes hidden assumptions like “this always happens on the same thread” or “these two things never happen simultaneously.” Sometimes these hidden assumptions are dependencies on implementation details of the API that are not explicitly mentioned in the documentation, at which point the team has to decide whether to make them contractual (by documenting them) or to leave them as implementation details (and the sample cannot rely on them). Sometimes these hidden assumptions are actually bugs in the sample, and they end up highlighting ways that the API could inadvertently be used incorrectly.¹
Comments in the sample are often used to describe what each step is doing. But sometimes the samples will use comments to describe code that has been elided. For example, we might have this:
class Sample { void RegisterEvents() { // Register a handler to run when a message is received. widget.MessageReceived += (_, e) => { // Is this the sender we are waiting for? if (e.SenderId == expectedSenderId) { // Process the message. e.Handled = true; } else { // Ignore messages from unexpected senders. } }; } }
The comment “Process the message” is intended to mean “Here is where you write code the process the message.” But it could easily be misinterpreted to mean “The way you process the message is by setting the Handled
property to true
.”
The convention for API samples is that comments are for describing what the code does, or better, why the code is doing what it’s doing.
If you want to say “And here is where you would insert your app-specific business logic”, then don’t use a comment. Call out to a method that is not implemented in the sample.
class Sample { void RegisterEvents() { // Register a handler to run when a message is received. widget.MessageReceived += (_, e) => { // Is this the sender we are waiting for>? if (e.SenderId == expectedSenderId) { ProcessMessage(e.MessageKind, e.MessageData); e.Handled = true; } else { // Ignore messages from unexpected senders. } }; } void ProcessMessage(WidgetMessageKind kind, byte[] data) { // Business logic goes here. } }
¹ Sometimes the team says, “Well, if we added to the sample all the code needed for dealing with edge cases and proper error handling, then the sample would have been too complicated.” This tells us that your API is already too complicated because the only way to use it correctly is to write code that is so complex, not even the team that wrote the API wants to do it! (In extreme cases, the API is so complex that there is no way to use it correctly.)
Amusingly, there are also times where the team does something in the sample that their own documentation says that you shouldn’t do.
Maybe the other API teams as MS should follow this guideline as well, or it isn't enforced anymore. Most of the REST APIs coming out from MS are simplistic HTTP client calls with not even contextual information about the steps needed to get there. Authentication, paging and error handling are left out because they distract from the couple of lines that demo the actual call. Yet using that in a realistic application won't work reliably. If MS truly built samples that demonstrated the real-world code that has to be written to work with them then they'd realize how complicated they...