Series index
We’ve covered the Clean Start protocol and how to make for Easy Recovery already, and it’s time for us to discuss Fast Feedback.
When someone in engineering talks about quality, they usually mean that the item in question
- performs a useful function
- does so reliably
- has low cost
- has easy maintainability
We have many arguments in social media where people take the word “quality” in a different sense, to imply aesthetic purity, opulence, luxury, and outrageous lists of features.
When we talk about feedback, we take the engineering sense of quality. We want the product that we deliver to fulfill useful functions reliably and affordably, with easy maintenance.
In order the product we produce fulfills our intentions we need to measure our effectiveness with feedback.
We don’t want to operate from unchecked self-confidence alone (although apparently some do).
To this end, we need both in-session feedback and external feedback. Let’s take an inside-out tour of this important topic.
In-Session Feedback
Programmers work within ambitious schedules, even when they set their own. This often motivates them to work in big batches and huge steps when a smaller, more measured approach is preferable.
Working in small steps isn’t a virtue unto itself, but rather enables us to have better feedback loops to maintain and improve the product’s quality and our productivity.
The tight “inner loop” of software development is the interaction of the programmer(s) and the IDE when creating code.
Consider the life cycle of a defect:
A defect is injected when some programming decision is made in the codebase or its dependencies that does not pan out for all the uses of that code.
Some defects are “shallow”; they are easy to fix now, and will still be easy to fix a year from now.
Some defects are “deep”; they are hard to reproduce, and understand, and occasionally hard to fix or work around. It may take many hours or days to figure out what behavior in the code causes defective behavior, which is then remedied in a few lines or characters of code. Some of us have had the unpleasant job of explaining why our multi-day struggle resulted in changing such little code1.
In general, period A governs period B.
If (after a clean start) I’ve written only one line of code before the alarm bells go off, I know I have only one line of code to examine. The error can not be anywhere else.
If I undo the change immediately, everything returns to its original function with no errors or failing tests.
The larger the set of changes before the defect is found, the more work it takes to discover what is wrong.
This is painfully true if changes from multiple authors are all merged and only then the newly composed application is found to have a logical error.
I want to know whether every change is valid, and I want to know within seconds.
This is the goal of what we call continuous testing. We want some element of our process to surface errors as soon as they occur.
To support continuous testing, some IDEs provide a feature to rerun the tests after every code change.
Sometimes auto-testers are included with a language’s unit testing framework, running the tests whenever a file has been saved. Often these run in a separate window. Most programmers have multiple screens or at least enough screen real estate to have multiple windows open and visible at once, so this is “good enough.”
Feedback loops in the editing session can be either one of two things:
- Fast2
- Ineffective.
There is no room for slow checks and tests in the tight inner loop of coding.
Developers will run slow tests less often and do more work between checks. This increases period A, and period B ( searching for causes of defects) naturally expands.
If the tests are too slow, hurried developers will disable them.
We can do better than that.
We have several tools at our disposal to help:
- We work together to produce code, which not only helps us spot any mistyped code and logical errors, but we spot and correct thinking errors before we even type the code – preemptively. That’s faster than “immediate.”
- Any good IDE has syntax highlighting to spot common spelling and punctuation errors. This is immediate. I use a lot of JetBrains editors, and I respond to warnings (shown as a yellow triangle in the upper-right corner). I try to have zero warnings and zero errors.
- Most editors support an error checker (“linter”) that will spot common code issues (“code smells”), security vulnerabilities, and potential errors. I use SonarLint in most of my editors and recommend doing likewise.
- Microtests ensure that the various functions and classes still work as we intended. This feedback allows us to refactor the code freely into any other form that passes the same tests3. The period of these tests is sub-one-minute, often less than 10 seconds, so we use these with an “auto tester” that runs the tests whenever the code changes or a file is saved.
- We often have suites of integration, component, or large unit tests. These take longer to run, generally but sometimes fast enough is fast enough. If they are too slow for the continuous testing part of our workflow, we can still run them periodically, at least every 20 or 30 minutes.
-
Pre-commit hooks are often used to run slower tests before a change is allowed to be made permanent.
The faster the tests are, the more useful they are for continuous testing and refactoring.
We use TDD as a way to establish microtests so that when we write any line of code, we already have test coverage and can immediately refactor it safely.
Teaming, continuous testing, TDD, and a well-configured IDE give us the safety we need to apply CI & CD.
Of course, all of this is in service of writing and refactoring the code so we deliver a solution with low maintenance costs and high code virtues.
External Feedback
I listed the in-session feedback first not because it’s the most important for the success of a project, but because it’s the feedback that most of the readership will engage in the most often. It’s important to build the thing right and to move quickly with changes so that our external feedback can be fast, too.
Imagine that you’re rolling up a yoga mat. The inner diameter of your first roll has a huge effect on the outer diameter of the fully-rolled mat.
In the same way, if development takes weeks or months, we can’t get user feedback very often. Quick deployments lead to faster feedback for companies willing to capitalize on the opportunity.
So there are a few different things to offer here.
Quality and speed won’t necessarily lead to success. Products tend to die either for reasons of internal politics or because they fail to achieve market fit.
User Experience disciplines have goal-seeking feedback mechanisms. Some can be applied prior to beginning development. Interviews, research, user persona, MVPs, – it’s an important skill set for ensuring that you are creating a solution that might have a market fit.
The idea of “plan it all up front” has a decidedly poor history due to its “all or nothing” bests on the first product design chosen. Incremental efforts have about a 40% higher chance of success.
It is not enough to be “right to begin with.” We have to adjust on-the-fly to the changes in needs and expectations if we wish to remain viable and competitive.
Not only are software solutions dealing with wicked problems (multiple stakeholders, different intentions and needs, different perspectives, etc) but also providing a solution can change the nature of the problem.
Given a good way to collect payments, the shop suddenly finds that they need financial analysis, costing based on materials and labor, and business forecasting based on the payment data. In this way, an available solution uncovers or creates new needs.
When touchscreen devices became commonplace people suddenly “needed” mobile apps to do things they used to do on desktop computers. This need was generated by changes in the larger consumer electronics market not from any direct forces.
Thankfully, UX doesn’t end at initial analysis and planning, but continues with the Build-Measure-Learn cycles and the whole toolbox of product evolution techniques.
We need the development teams to provide rapid build/release cycles to support the UX efforts to find and improve our products’ market fit.
This relates back to our defect lifecycle diagram (above): how can we shorten the period between making a product decision and finding out if it makes a positive difference?
Does it matter?
Not every company needs or wants to be nimble and experimental. You might have a job that doesn’t have any interest in the speed, quality, or market fit for your work. That might actually be quite nice.
If you work for a company that is not interested in requesting feedback or acting upon it, evidenced by making no move to enable it, then this may be wasted effort.
On the other hand, the defect life cycle is affecting you. If you have to spend time remediating bugs via debugging, or loading the application with print/log statements, or bisecting commit histories and various other “extreme sports” of the programming field then you may find immediate relief and value in using various technical feedback techniques like the ones described here.
It might make your work easier and more fun. Who doesn’t like that?
Notes
-
My colleague had cast a C “long” through a “short” integer in one case. It was easy to understand, hard to find. ↩
-
See Unit Tests Are First for more on this topic. ↩
-
I won’t say that TDD improves design. it lets me change design freely, so improvement is always a possible choice. ↩