FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Upgrade

A Computer Science Approach to Clear Intent in Code

Written by 
Thomas Bowen
,
Senior Software Engineer
A Computer Science Approach to Clear Intent in Code
blog post background
Behind the Scenes: React Hooks API
How DRM Affects Right to Repair Hardware / Software
How To Set Up a Productive Work- From-Home Development Office

Table of contents

In software development, developers leverage a bevy of toolsets to assist with work, such as an IDE (interactive development environment) like VSCode, replete with open-source plugins, or commercial apps like JetBrains’s Webstorm. Not only are there software tools, but there are also mental tools — concepts programmers reflect on in daily work, handy-dandy acronyms that reflect principles: KISS (“Keep it simple, stupid”),  SPOT (“Single Point of Truth”), YAGNI (“You Are Not Gonna Need It”). For the bigger picture, there are also patterns of writing code (builder, singleton, functional, etc).

But what about some principles from which those principles are derived? This article explores some concepts taken from the author’s studies of software design, formal logic, and software engineering research. With some concise explanation and simple examples, other programmers could reflect on some more tools that could illuminate underlying principles, or give some food for thought. 

Hoare Logic, or seeing the logic surrounding the code

As a developer, the following situation may sound familiar. There’s a problem with a pull request that another developer submitted, and as a reviewer, one can write a comment underneath the code in question and specify what the issue is — if it didn’t use a utility method or is merely too verbose, among other things. But what if, in this instance, the code itself is implemented correctly and syntactically looks okay, but the way it works is not correct in the “larger scope” of the system?

Consider the situation: There’s an intermittent failure to retrieve data from a request to a third-party API (403 unauthorized). It requires authentication headers that another part of the application handles (authenticating, storing, and so on). A method is available, getAPIHeaders, which will retrieve them. The code looks like:


const thirdPartyDataRetrieval = async () => {
 const requiredHeaders = await getAPIHeaders();
 const { error, data, message } = await fetch(path, options).then(res => res.json())
 return data;
}

Looking at the example above, while the code looks correct, an assumption is faulty. What if the auth data isn’t always available when this method is run, and returns null or undefined, rather than throws an error? It wouldn’t be fair to say the code’s implementation is wrong, only that one of the assumptions it makes is intermittently faulty. 

So to clearly communicate in a comment about the issue, there has to be some mention of the context surrounding the error. One could mention implicit coupling or context, but it’s clear there is a “bigger picture” surrounding the code, or what in formal terms is called an abstract state.

As it turns out, the computer scientist and all-around wizard Tony Hoare introduced a formal system in 1969 to describe this state. While developers are certainly familiar with logic statements of if’s and else’s, this kind of logic wraps “around the code” that describes the “state of the computation” before and after the code is executed:

{P} C {Q}

{P} 

 x = y * 3;

{Q}

The above line is a Hoare Triple, the foundation of Hoare logic. P (the precondition) and Q (the postcondition) are facts about the state of the program, or assertions before and after the code is executed. So if the P, or precondition, in this example is:

P: { y = 5 }

Then this condition of “y equal to 5” must be true before the program runs. (Unlike code, this is simply equality and not an assignment.) In the above Auth Data example, then, perhaps the precondition is something akin to auth data being defined before the code executes. And furthermore, a postcondition cements the formula:

Let’s say the Q (or postcondition) is:

Q: { x = 15, y = 5 }

For each assertion of the Hoare triple to be true, then after the code runs, the state of the program will be such that Q is true. (The code could do something in addition to that, but in order for the Hoare triple to hold it must fulfill the Q.)

{ y = 5 } x = y * 3; { x = 15, y = 5 }

While these examples are basic, Hoare Logic can extend in several ways, such as going over many lines or using intricate logical notation. The insight here is that Hoare Logic provides a provable logical formulation over code, a meaning that goes beyond code and spec, and into assumptions about what state the code is supposed to produce. If the behavior of the system is faulty, it could be because of assumptions of preconditions (or postconditions) that don’t hold or are missing. So if a developer ignored this implication in debugging an issue, it could take longer than necessary (lots of logging all over) instead of considering this logical layer.

This is referred to as Level 3 Design/Logic by Computer Scientist James Koppel. Thinking about the state of the system before the code block is run and after, and whether those states are appropriate, is crucial, and implicates issues relevant not only to the software’s code but in this more abstract logical level.

Avoiding the trap of “Dark Knowledge”

So another way of looking at this information surrounding the code is that a developer’s mind contains these ideas, at least while the task is recent and the memories are fresh. But since programming is a team sport, the essentials of that info are relevant for the team or enterprise as they review code or refer back to it in the future. The Hoare logic codifies that thinking in the form of rigorous logic and puts it on screen for all to see.

But what about the system as a whole? Is using APIHeaders from one part of the system and plugging into another the right approach in terms of the overall system?


const items = [{apples: 3}, {bananas: 2.5}, {peaches: 5.32}];
const addMore = (newItem) => items.push(newItem);

Let’s assume this code also works. But as seen right away, it’s not quite clear what the structure is supposed to be — is the number value for each fruit a quantity? Or price? Or consider this other tidbit, as part of an email messaging system:


function writeMessage(messageContent) {
  message = new Message(messageContent);
  messageData = message.asBytes();
  messageData.decode(charset);
  stream.write(`${charset}\n`);
  stream.write('-'.repeat(79));
}

What are the characters written in the stream for the last lines meant to do? It appears contextual, so maybe another domain expert could understand what this writing refers to, but the clues for understanding are muddled. As mentioned earlier, some developer’s minds once understood why this data structure was written this way, or what method order is chosen, but it’s not present anymore — and if they’ve left the project, gone completely. 

In the 2013 conference paper titled “Dark Knowledge and Graph Grammars in Automated Software Design,” Don Batory and friends describe a problematic cycle of developers writing code to satisfy requirements, then other developers using that code to write according to new requirements, while in each iteration having to retrace the steps that the former developer took to re-examine their decision making. This “Dark Knowledge” is the missing ingredient between each hand-off, which developers new to the codebase have to re-trace and make educated guesses about. While their solution is to introduce the concept of “graph grammars” to automate the encoding of dark knowledge, they state the overall “goal is to encode design decisions explicitly in software…so these decisions can be revisited...” (Batory et al, 2013)

So, revisiting the example above, and adding types (going from JavaScript to TypeScript, highly recommended if a dev hasn’t tried it already!):


interface FruitProduct {
 name: string;
 cost: number;
}
 
const availableFruitItems: FruitProduct[] = [
 { name: 'apples', cost: 3 },
 { name: 'bananas', cost: 2.5 },
 { name: 'peaches', cost: 5.32 },
];

Or going back to the message method, and creating a utility that describes what the stream writes, and importing that:


import { messageTerminationSet } from 'messageRules';
 
function writeMessage(messageContent: AcceptedMessage) {
 message = new Message(messageContent);
 messageData = message.asBytes();
 messageData.decode(charset);
 stream.write(messageTerminationSet);
}


The code now better represents the design, shortening the time for comprehension.

Conclusion

While brief, these points illuminate deeper principles for writing software that can be helpful to reflect on during the process. With Hoare Logic, the existence of a higher level of logic surrounding code is explicated, and ignoring it misses the opportunity for clarification that could save time, if not improve the correctness of the code itself. By taking this a step further and considering the “mind of the programmer” as useful knowledge not just for development, but for persisting ideas into the future of the codebase, the encoded “Dark Knowledge” encoded can save time or even the ability to understand the code choices more than an educated guess. 

Thomas Bowen
Written by
Thomas Bowen
Thomas Bowen

As a Senior Software Engineer for FullStack Labs, I wield JavaScript and Python to create elegant, performant solutions for our clients. My primary tools for perfecting the user interface are React, React Native, AngularJS, jQuery and vanilla JS. On the backend side I’ve been known to write clean code with NodeJS, Django and Flask. I’m a stickler for proper SDLC practices  such as writing tests to ascertain complete code coverage and documenting and refactoring accordingly. While not mentally engaged in the coding arts, I love to exercise and then relax with some rare vinyls from the early space age.

FullStack Labs Icon

Let's Talk!

We’d love to learn more about your project.
Engagements start at $50,000.

company name
name
email
phone
Type of project
How did you hear about us?
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.