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?
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.
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?
We have a great start: a failing (or erroring) test.
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.
If not, we may be able to use our text editor’s “go to file” or “go to definition” features.
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
:tag method_name command to jump to method definition, or
grep really fast.
:help gf :help :find :help 'tags'
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.
This lets us stop the program and examine or change the data to better understand control flow through the code.
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.
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.
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.
If you enjoyed this post, read our Back to Basics series.