How to Stop Voodoo Programming

Voodoo programming is when we write code that we don’t really understand. We know we shouldn’t do it. But how do we stop?

An example story

We are running tests and making changes to our code when we see a new error.

We decide we’ll upgrade all the Ruby gems (dependencies). Same error. We upgrade to the latest version of Ruby. We check Twitter and email while it installs. Same error.

We ask our teammates for help in a chat room. Most folks are unavailable but someone replies:

“Sorry, I haven’t seen that before.”

Finally, we copy the error and paste it into Google. The first result is a Stack Overflow question. The first answer to the question includes a code block but no explanation or links to reference material.

We exclaim:

That solution is so weird! Totally obscure behavior.

We don’t have time to dig deeper, though. So, we copy and paste the code into the appropriate place in our codebase. The error disappears and the tests pass.

Pleased, we check the code into version control, open a GitHub pull request, and go to lunch.

We get back from lunch expecting to see a “Looks good to me.” comment from a teammate congratulating us for solving a difficult problem but instead they commented:

Why does this solution work?

They smelled something funny and caught us. They’re worried about the code continuing to work and how those working on the codebase in the future will know how to maintain it.

Without fully understanding the problem, we can’t hope to solve it. We also wasted our teammates’ time in the chat room and code review, and our clients’ time by not using tracer bullets.

How do we stop voodoo programming?

Write a failing test

We have a great start: a failing (or erroring) test.

Read the backtrace

The first step is to read the backtrace of the failing test. We can open the file and go to the line number that threw the error. We may immediately see the problem.

Explore the source code of our dependencies

If not, we may be able to use our text editor’s “go to file” or “go to definition” features.

Working with Ruby, JavaScript, CSS, and similar tools, we have access to the source of languages, frameworks, and libraries upon which we’ve written our application code. There’s no good reason not to explore the source.

In Vim’s normal mode, we could use the gf command, the 2gf command (jumps to bundled gems in Ruby files), the :find command (which is assisted by tab completion), the :tag method_name command to jump to method definition, or grep really fast.

:help gf
:help :find
:help 'tags'

Use debugging statements

When we’re in the source code at a line from the backtrace, we often need more context about our program’s data at runtime.

In those cases, we put debugging statements in our code and in the code of those underlying languages, frameworks, and libraries. We most commonly use debugger for JavaScript, byebug for Ruby, and byebug and pry-rails for Rails.

This lets us stop the program and examine or change the data to better understand control flow through the code.

Read the manual

Running our tests with debugging statements at various levels of our code is likely going to provide us with the understanding we need.

As we’re getting closer to solving the problem, we may want to also Read The Manual of a language, framework, or library. The documentation may give us explanations and examples of their appropriate use by client code.

Write good commit messages

Without a good commit message, our teammates reviewing our pull requests may not understand the change. Future maintainers of the codebase may not understand the reason for the code, adding further time and cost to the work to be done.

Our reading of the manual may provide us with URLs to documentation which we can reference in the commit message.

Contribute patches to open source

On occasion, we will find a bug not in our application code, but in one of the languages, frameworks, or libraries upon which we depend.

At a minimum, we can report an issue to the project with our tests and backtraces.

Even better, we may have been able to fix the bug while we were placing debugging statements in the source code. In that case, we may be able to contribute a full patch to the project, including tests and a good commit message with links to further documentation.


We can stop voodoo programming by writing failing tests, reading the backtrace, exploring the source code of our dependencies, using debugging statements, reading the manual, writing good commit messages, and contributing to open source.

What’s next

If you enjoyed this post, read our Back to Basics series.