This article argues for the value of example code for software projects. The primary audience are software engineers working on libraries and open-source code.

Executable example code is probably the most valuable artifact one can add to projects ranging from single-purpose libraries to larger frameworks. This applies from the API design phase of a library to maintaining them in the long term. The repositories of the Android libraries that I maintain(ed) typically include dedicated sample apps. For example, the sample app for the Sloth library or the sample app for the Spectrum library.

Early in the project

When starting a new library or module, I often start by writing a sample client or call-site. This helps to understand how the API should look like and what external constraints need to be considered. Should it be synchronous or asynchronous? What input and output types are most convenient to use? How should errors be propagated? Do we need to pass in context such as coroutine dispatchers or Android context? If so, where is the best place to do this? Do we need to manage state or can we have pure functions?

While this works well as an individual exercise, it really shines when it comes to team discussions. This is because once abstract ideas are written down, we can discuss, amend, and compare them effectively. Tangible code often the best communication tool at this stage. After all, writing software is a creative process and the text editor is our canvas. This goes into the same direction as the famous dictum “code wins arguments” which is popular at large tech companies.

During active development and maintenance

Example code also provides great value while the project is being actively developed and maintained. By building the example code or sample app in the CI pipeline, we avoid the most critical regressions of the public API. For any such change to pass, they would need to be accompanied by changes to the example code which are then easy to spot in code review.

Ideally, the CI pipeline not only builds the code, but also executes a few high-level integration tests on it. For instance, we might implement a UI test for our Android sample app. This way we also pin the output of our library and ensure we do not accidentally change output formats and values between releases. Doing so via example apps is inherently economical since the sample integration will naturally focus on the most important APIs and error cases.

The sample app also helps with manual testing of changes and investigating bugs. Especially for mobile libraries it’s critical to both test them on a variety of real-world devices and when integrating them in a release build. Afterall the release build enables many optimizations and post-processing that can cause regressions (e.g. broken JNI bindings).

Finally, sample apps are a great tool for actionable bug reports. If the bug can be repdroduced using the sample app, there is little ambiguity about the surrounding code and build settings. It also allows the reporter to do an efficient bisect over the commit history and narrow down a regression.

The value of example codes is present in other places as well. Consider for instance Rust’s documentation tests or the Python doctest module that allow binding the documentation firmly with the actual implementation code.

From the integrating developer’s perspective

Libraries and modules are used by developers in other teams and from around the world. Naturally, their on-boarding experience is key to a project’s success. Example code and apps that are included in the CI pipeline give the integrating developer a reliable starting point that is known to work. Importantly, they can then debug build issues themself by comparing their local setup to what is done in the CI actions. It also lends itself to hypothesis-driven “what if” changes as described in my post on stack diving.

Example code is also a critical part of the project documentation. Since it is executable by a machine, it does, by definition, not contain any ambiguities. This is especially true for dynamically typed languages where exploring method signatures can be quite cumbersome and error prone. In addition, it also documents the build setup which can be a massive time sink if it is not clearly specified.

Tutorials are a common alternative. However, I believe they have their strength elsewhere and, in general, they provide less return on investment. Typically, tutorials are not executable which allows them to quietly drift from the current implementation. By focusing on small steps, it is easy for the writer to miss important information such as the build setup and integrating with the outer frameworks. Also, tutorials usually focus on the happy path, leading the reader around inconvenient rocks such as error cases. However, those are important for real-world integration and libraries should provide a best-practise way to handle them.

Conclusion

Example code is often the most economical choice to unlock a large number of benefits for projects that would otherwise require a lot of individual attention and engineering effort. It documents the API and build configuration, it ensures API stability across releases, and it provides coverage of the whole artifact through (almost for free) CI integration tests. Most importantly, it binds all of those together avoiding drift between components as the projects evolves.

Credits: cover photo by 2y.kang on Unsplash.