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.
Conclusion
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.