Towards zero bugs

Jonathan on March 17, 2019

I am on a personal and professional mission to write bug-free code. Software with zero bugs may seem like an ambitious goal. Over time, defects in... [Read Full]
markdown guide
 

You made a logic error in your code:

if (hidden) { show(); } else { hide(); }.

I think you meant the opposite.

Also software that is decently complex will always have bugs. Don't get me wrong, you get better at identifying common causalities over time as you have shown here, but given sufficient complexity, you will eventually have bugs. It's just entropy at work.

 

Thanks for your very insightful comment!

Software, by default, grows complex and chaotic as it develops and evolves. Like a living organism, I think software requires constant action to maintain it and keep it functioning well.

One of my hopes is that we as developers will continue to build upon a body of wisdom and expertise grounded in real-world experience. This shared knowledge will boost every developer's ability to manage the complexity, reduce the chaos and (perhaps) one day eliminate all the bugs!

 

Hewwo, pwease change

- if (hidden) { show(); } else { hide(); }
+ if (hidden) { hide(); } else { show(); }`.

if hidden is the current state.
OR, if hidden is desired state, change

- if (!hidden) { show(); } else { hide(); }
+ if (!hidden) { hide(); } else { show(); }

You didn't flip the cases when negating the predicate.

Stylistically I would ofc prefer one of:

if (hidden) show()
else hide()

or

hidden ? show() : hide()

for a better signal/noise ratio (which makes bugs more evident)

 

Off by one errors: The loop that you indicated (for (let i = 0; i < 10; i++)) actually does run 10 iterations (index 0 to 9).

Values vs. references: This was a huge issue for me for the first couple years of programming. Languages like JavaScript where the pass-by-value for PoD/pass-by-reference for objects is not obvious to a beginner doesn't help either. If you have a reference and you need a unique copy, but your object is made up of a bunch of references itself and those all need to be copies, things get hairy really quickly. I prefer "dumb" languages like C for this reason. Everything is a copy unless otherwise specified as a pointer, so you don't have to waste any brainpower trying to figure out whether something is a reference or a copy.

 

Thanks for identifying the error in the ‘off by one’ code. I have corrected the error.

 

Nice post Jonathan!

Linting and static type checking definitely remove a chunk of these issues

I'm trying to remember for C#, there was a tool that would check for potential memory leaks that wasn't the memory profiler. The name is escaping me at the moment.

As well for C# async/await, setting ConfigureAwait(false). Where I used to work we create a rule using the Roslyn compiler to check for this.

Anyways, looking forward to your next post!

 

Great post, Jonathan. Zero-defect software is a worthwhile goal (one that I've been pursuing for the last couple of years).

Here's my code review checklist.

We've been using this checklist at my work for a couple of years and it really works for us. We find a only a handful of minor errors in production each year (which is a fraction of what we found before we used this checklist).

Watts Humphrey published extensively zero-defect software. I wrote a summary here.

But, even if you don't want anything to do with PSP (and most people don't), chapter 8 of PSP: A Self-Improvement Process for Software Engineers lays out the economic case for producing high quality software. Spoiler: it's almost always worth the effort for software that's going to be around for a long time.

 

Thanks for sharing, Blaine! That writing by Humphrey looks very interesting. I'll certainly read up on it.

 

I have a problem with the way Subtle logic errors section is phrased.

if (hidden) { show(); } else { hide(); }

is not a clearer way to write

if (!hidden) { show(); } else { hide(); }

It's different code. Did you mean that a bug was fixed, and not a refactor occured?

Same for the next one:

for (let i = 0; i < 10; i++) { ... }

is not clearer than

for (let i = 0; i <= 10; i++) { ... }

It is different code.

for (let i = 0; i < 11; i++) { ... }

could be the clearer rewrite of the same code. (Is it though? What's 11?)

The bigger issue there is probably the magic number 10.
Better than any comment you could leave would be extracting it to a named constant. A constant semantically named for the reason it's used there.

These are both good code:

for (let i = 0; i < 10; i++) { ... } // BAD
for (let i = 0; i <= 9; i++) { ... } // BAD

const len = 10
const lastIndex = len - 1

for (let i = 0; i < len; i++) { ... } // GOOD
for (let i = 0; i <= lastIndex; i++) { ... } // GOOD
 

Some stuff I'd like to add:

  1. Write regression tests for all sorts of bugs you find. The first step if you find a bug through production use should be to write a failing test for it, even if the software isn't being developed through TTD.
  2. Write UI tests.
  3. Use fuzzers.
  4. Use mutation testing.
  5. Use TypeScript/mypy
  6. Use linters.
  7. Use formatters. Yes, formatters. Having your code consistently formatted will instantly reveal when you mistyped something parseable but different from what you meant. And it will minimize git diffs, making them easier to review.
 

Imagine bug free code, how would we improve that? If I change your checklist to include screen readers in multiple languages, would I have just created more bugs? It's a moving target, great to aim for but imperfection is reality.

 

Yes! Someone referred to accessibility! It is certainly critical, as you mentioned, for those who rely on screen-readers and other assistive technology to be able to access and use applications.

I have added a short section on accessibility, but as a mere paragraph wouldn't do the topic justice, I will publish a separate blog post on the topic and/or reference the best sources I can find.

Agreed that imperfection is the default state, and we often can't cover all bases. I think that as we push forward and aiming higher, for more reliable and bug-free code, we will build up tools and expertise for dealing with a large number of concerns, including accessibility, efficiently and effectively.\

Update: I have added a section on 'Accessibility'. Great idea – so thumbs up to you. 👍

 
 

Why on Earth for (let 0; i < 10; i++) {...} will iterate 9 times?

 

Apologies, my bad. Thanks for identifying this. I have corrected the error.

 

Actually, all you wrote is more or less fair, but usually you finally learn how to deal with all of them. But bugs might float up when you implement some really complicated logic - there, where behavior and result are hard to predict or manage.

 
 
 

Great one I just want to emphasize that requirements are always non bug free so even if code is bug free it's non bug free by definition

 

Great point. Certainly, requirements cannot be taken as gospel. Rather, they must be understood and integrated. Where there are contradictions and ambiguities, those must be questioned and either settled or the requirements changed. Thanks for your comment.

 

A good list for any new or junior developer to review.

 
code of conduct - report abuse