Key aspects of software are not expressible in code

Key aspects of software are not expressible in code

Introduction

If a developer joins a software product team but can only inspect assets such as code and tests, would they gain a complete understanding of the product? Without an existing team member to talk to, they would have difficulty understanding the motivation behind the code, even if they can decipher the logic itself. This phenomenon is intrinsic to software development and cannot be solved entirely by focusing on code quality, good tests, or comprehensive documentation. Non-code aspects are essential for evolving the software to fit real-world use, and ignoring them can lead to an unsuccessful product. However, there are tactics ensuring the successful evolution of a software product in real-world use.

Two aspects of software systems

To understand which parts of software are expressed in code and which are not, we divide the software system conceptually into two complementary aspects. The first exists at the code and tests level and is the software developers’ focus. We call this aspect Specifiable software. The other part relates to how the system is used and evolving in the real world. We call this aspect Evolvable software. We will see that all practical software has characteristics of both Specifiable and Evolvable software.

When developers are building an application, they use building blocks that ideally are automatically testable and could be derived from a specification or a requirement. These blocks form the Specifiable aspect of the system. It exists in a relatively static and abstract domain and is only indirectly related to the real world. Therefore, we can objectively assess the correctness of Specifiable software.

When the application is assembled, deployed and used by people, it is subject to constraints, uncertainty and changes in the real world. The “clean” Specifiable software becomes “messy” Evolvable software. The suitability (fitness) of the Evolvable software is subjective and constantly challenged by the real world. Thus, the software needs to evolve to remain relevant continually.

The two aspects are summarised below:

Feature

Specifiable

Evolvable

Domain

Abstract, idealised world

Real world

Answers the…

What?

Why?

Criteria for correctness

Objective

Subjective

Automatically testable

Yes

No

Degrades over time

No

Yes

Acceptance criteria

Does the code correctly implement an abstract contract?

Is the system useful in the real world?

Reason to change

A defect is found; Evolvable requirements change

Real world changes; Team’s perception of real world changes

Stakeholders

Developers

Users, developers, designers, operations, business owners

Assets

Code, tests

Running software, documentation

The key challenge for the success of a software product is the Evolvable aspect. Although writing correct Specifiable code and clear tests is certainly demanding, the system is only useful if it satisfies real-world requirements, and continues to do so if the environment changes. A Specifiable system may be fully correct, but serve no useful purpose.

Example: Selecting the next song for a music player

A music player has an option to select the next song from a playlist automatically. The Specifiable aspect involves writing or adopting an algorithm that selects an item from a collection. Such algorithms are found in software libraries and literature, and their correctness can be fully verified. When the algorithm is integrated into the Evolvable music player and deployed to the real world, its primary fitness criteria switches to whether the users like how it selects the next song. The Specifiable acceptance criteria remain relevant but are secondary to real-world constraints.

Identifying the Evolvable aspect

Well-written software ideally consists entirely of Specifiable building blocks and their integrations. We should write each building block clearly so its purpose can be understood even if there is no explicit specification. Automated tests verify that the building blocks work in isolation (unit tests) and combination (integration tests).

Since the Evolvable aspect is primarily absent from the code, we can infer that the most challenging parts of the software are not explicitly visible in code and tests. So then, where is the Evolvable aspect located and in what form?

  • Developers have a mental model of the Evolvable system and its environment, and use the model to develop the Specifiable software.

  • Users have a mental model of the final product, which may not correspond to the model used by developers.

  • At runtime, the system exists as an interaction between the application, the users, and the environment.

  • Some elements of the Evolvable aspect can be documented.

No single person has complete information about the Evolvable system. Developers do not accurately know users’ needs and mental models and may additionally not know technical details of Specifiable components they did not write themselves. Users may not know how the system is “supposed” to be used; for example, they may not know that a feature exists because it is challenging to discover.

Managing the Evolvable aspect

How can we take the Evolvable aspect of software into account in software projects? Focusing only on code and automated testing (Specifiable aspect) can result in a commercially unsuccessful application, whose maintenance further becomes problematic if the original team members leave the project. Below are some tactics for managing the Evolvable aspect.

Agile software development

Agile development obtains software requirements dynamically, without assuming that complete requirements exist at the start of a project. Thus, it is naturally suited for an evolving system. There should be feedback from users or internal stakeholders to the development team to take real-world insights into account during development.

User Experience (UX) design

During early UX design, we obtain insights from actual potential users. These include user interviews, needs, pain points, personas and scenarios. These insights have two benefits. First, early UX design helps connect the system to the real world. Second, the resulting documentation reveals the initial assumptions made by the team about the real world and remains useful later in the project for understanding the motivation for technical choices.

Product-oriented teams

Since Evolvable software exists primarily in people’s minds, it is vital to retain this knowledge. Therefore, product-oriented teams work on a specific product over a long period and become knowledgeable in both the technical and environmental aspects.

If a software vendor develops the software for a client in a project setting, it is helpful to have a long-term Product Owner (PO) from the client working with the vendor. This PO is then aware of the choices made during the project and retains the knowledge for further development.

Cross-functional teams

In a cross-functional team, developers don’t specialise in a specific part of the application (the Specifiable system) but rather mix roles and work together. The de-specialisation reduces the risk of a member leaving and taking unique knowledge with them.

Monitoring

In a live system, the users’ actual behaviour is more important than the initial hypothetical designs. Behaviour can be automatically monitored from web access logs or by conducting user testing. Automated monitoring is subject to privacy laws (GDPR).

Second, the runtime infrastructure is also part of the Evolvable software. Infrastructure often includes a cloud system and external APIs. They must be monitored constantly to ensure their fitness does not decrease.

Targeted documentation

The Evolvable aspect is difficult to document accurately because much of it is non-verbal and implicit. Producing large volumes of documentation is counter-productive because it is difficult to find the relevant information, and documentation frequently becomes outdated. Nevertheless, some forms of documentation are helpful:

  • Architecture Decision Record (ADR) is a logbook of major technical decisions made during the product’s lifetime. It records the date, type of decision, justification and alternatives considered for each major decision. ADR helps new team members understand the historical context.

  • In Domain Driven Design (DDD), a Domain Model is an informal model (such as a diagram) that combines the shared understanding of domain experts and developers. It helps in understanding software entities in their domain context. However, as a static document, the Domain Model may become outdated.

  • In DDD, a Ubiquitous Language is a vocabulary derived from the domain of the terms used throughout the system. It helps define the precise meanings of ambiguous terms.

Conclusions

The most challenging aspect of software development is related to the use and evolution of software in the real world, which is mostly invisible in physical assets such as code and automated tests. Ignoring this Evolvable aspect can result in an unsuccessful product poorly suited for its purpose. Tactics to manage the Evolvable aspect include focusing on team knowledge retention, UX design and testing, and targeted documentation. The most valuable person for a product may be a long-term Product Owner who knows the technical history of the product and obtains feedback from users.

References

Lehman, M. M. (1980). Programs, life cycles, and laws of software evolution. Proceedings of the IEEE, 68(9), 1060-1076.

Lehman, M. M., & Ramil, J. F. (2002). Software uncertainty. In Int. Conference on Soft Issues in the Design, Development, and Operation of Computing Systems. Springer.

 


This article is written by Senior Software Architect Kristian Ovaska.