When working with junior developers and sometimes even senior developers, I’ll see them get stuck, frustrated and struggle to work out why something isn’t working how they expect. They’ll mutter under their breath “but it SHOULD work”, that they’ve checked everything and can’t see why the code isn’t doing what they want.
It happens to everyone at some point - frustration clouds your mind. Taking a step back, you need to think “If I have checked everything and everything looks right, what have I not considered?”
I feel like I’m pretty good at debugging, able to get to the root of a problem pretty quickly, and once you’re there, it’s generally very easy to work out the next step required to actually fix the problem.
So here are a few tips on how I debug that will hopefully save someone several hours of keyboard mashing and curse words muttering.
##1 - Write a test that fails
This should go without saying, but in the real world, not every piece of code has a test, and the tests don’t always cover every case.
If it’s a repeatable bug, write a test that fails. It’ll be quicker in the long run than tinkering with code and manually going through the steps to reproduce every time. With the added bonus that the bug can’t happen again without the test failing, and you won’t reintroduce the bug again in the future. yay.
##2 - Use a debugger or runtime REPL
If you’re working in Ruby, I find
pry to be excellent, it doesn’t give you full debugging capabilities
but combined with
pry-byebug, you have yourself one very powerful set of tools to help you.
binding.pry anywhere in the code will pause execution and let you use a more advanced version of
irb while still in the current state of the application. From here you can see all the current application state visible from the scope where you added the
When you run your failing test, you can step through the code and see at what point it stops doing exactly what you expect. You can execute any code you like to help you see what isn’t working.
##3 - Bugs in libraries you use
In ruby it is common to use many many gems in the development of a product. There is usually a lot of different ones to chose from that claim to do the same thing, but it’s always worth checking github repositories, paying attention to: How actively the library is developed - did someone write this 3 years ago and it hasn’t been updated since?
How many open issues are there - are people having problems with the way it works?
Is there a good test suite? - Is it robust, production ready code? It’s good to see tests to know that things aren’t going to be breaking now and every time there is an update
If you are careful with what libraries you use, this shouldn’t happen so often, but it does happen. Some code in another library is not working how you expect.
I’ve seen a lot of people hit a wall here; it’s not my code, I’ve never seen it before and I didn’t break it - what do I do?!
First thing is first, you can also debug inside your gems - they’re just code files that are loaded in your project, no different from any of your own.
Bundler makes it simple to find and open the gems code,
EDITOR=atom bundle open sidekiq will pop up atom (or whatever code editor you use) with the gems code base. You can add
binding.pry in this code too, it’ll stop within your application (just make sure you remove them when you’re done)
If you’ve identified a problem , fork the repository, get the tests passing, and write a failing one. You can contribute back to the community with a pull request and until that pull request is merged, you can update your Gemfile to reference your own forked repository of the gem.
I was attempting to use a configuration option in the
cloudinary gem recently, it just didn’t seem to be picking it up though.
I was setting the configuration option in a Rails initializer, running the rake task, which by looking at the code should be picking this up
Of course, I stuck a
binding.pry in there, ran the task again, indeed, the configuration wasn’t set.
I added a
binding.pry to my own initializer to check that the value was getting set, maybe its losing the configuration option some time later? I’m setting the value to a literal, so there shouldn’t be anything wrong here.
Running the task again, I noticed the it didn’t stop at my initializers
pry… The rails environment wasn’t getting initialized by the task (making the rake task depend on the
environment task). None of the initializers were getting called, so the configuration wasn’t picked up. Simple bug but quickly got the root cause of the problem.
##4 - No documentation, no problem (discovery through debugging)
This isn’t really debugging, but what I’m trying to get at is, don’t take documentation at face value. If you are trying to use a library at it doesn’t say it does what you want, go and look at the code! Developers love writing code, but generally they don’t love writing documentation, and if they do, its not always in sync with the reality of the code.
Take a look at the code, find the code that is responsible for whatever you are looking for, see if the functionality already exists, changes are, if you need it, somebody else has already done it. If not, you can always add it yourself and do a pull request.
A recent example I came across was when trying to use the
cloudinary gem to compile static assets, by default, it stores all your static assets in cloudinary, which I didn’t want. I just wanted it to do assets in a certain directory. Damn!
Taking a look at the code, I went right to the rake task in question
cloudinary:sync_static, which is defined in lib/tasks/cloudinary.rake.
This calls directly in to
Cloudinary::Static.sync, which is defined in lib/cloudinary/static.rb
The first thing I noticed was
STATIC_IMAGE_DIRS which is a bunch of standard rails image asset paths. Looking at where this is used, you can see right away:
relative_dirs = Cloudinary.config.static_image_dirs || STATIC_IMAGE_DIRS
Clearly, we can set
static_image_dirs configuration and it will use this, and if that isn’t set, it’ll use the default. Problem solved.
Hopefully this post has give you a better grasp on what to do when you hit that brick wall and get back do making awesome things.