Software dev with more than 10 years of experience. I always gotten praise by colleagues and companies I worked for. But, there are always moments where, without help from a colleague, I wouldn't have found a solution to a problem.
When joining a new company, 2 months in, and facing a problem where code wouldn't compile, I dig deep into the codebase but for the love of me, can't find a solution.
I wonder, is that normal? Or how can I say: "I don't care, if I am alone, I can figure this out and solve it."
Did anyone here evolve from this state of reliance of others and turned themselves into a "I can do it myself in a good amount of time"?
* Split the problem space. Payment messages are not getting to the slack channel. Well, is it a bug in receiving payments, or a bug sending slack messages? Check if the payments are hitting the database. If no, you know it's on the input side. If yes, you know it's on the output side.
* Explain the problem to someone else; if you're alone write it up like a question you're going to post on a forum, with as much detail as possible. This must engage a different part of the brain, because often I'll figure out the issue while writing it up I'll reveal some clue while logging example output to add to the post.
Longer-term, teach a programming class! You'll get very good at debugging issues because you'll encounter a lot of other people's bugs, and you'll get a feel for what causes particular failures.
Also, don't feel any shame in asking others for help. Even senior devs get blocked, and they ask other seniors, or juniors, or if juniors are not available a rubber duck will suffice.
You'll also have a shortlist of functions that you can search for in stack overflow, and compare you example to theirs.
Most of the harder bugs I run into fall into this category. Great list of techniques to use. For when I'm really stumped I go into a more formal scientific method to the splitting the problem space approach. Formulate a hypothesis that if answered will reduce the search space, either by ruling in or ruling out things. Write it down - this is the important part, test it, repeat.
It's slow but usually helps me keep forward momentum. Worst case it at least creates a list of things that need better logging to figure out what's going on
I also can't agree more with no shame in asking others. I would go as far as to say if you aren't asking others, you are going down a dangerous path of assuming you can solve the world's problems by yourself.
Keep a debugging journal. Take notes on every step you take and its results. It's easy to go in circles because you forget what you tried or forgot some detail of its outcome. Seeing a summary of what you already know helps you rule out possibilities and inspires new ones.
I often forget to do this or feel like "I can handle this bug without a crutch". Yet every time I actually journal the process it's helpful.
I generally use Notepad++ and it's often just a set of notes about the value of important variables in certain files @ a particular line of code or at a particular time. I find that seeing that big picture at a glance can often give me immediate insight into what's happening. Something you can't always see when you're focused on a few lines of code in a method.
You can also refer back to your journal to remind yourself how you fixed that similar problem months ago.
Of course, a journal doesn't have to be limited to debugging. It's useful for development too.
- People look down on console/logging, but it's useful specifically when there are race conditions or too many variables. It's way better to have several outputs and logs rather than a "standstill" picture you can only look while using the debugger.
- "Learn to debug" - This advice is thrown too vaguely around, but I'll tell you that the 2 essential pieces that helped me debug are 1- using watches to keep an eye on variables, props that are relevant to the problem 2- Use conditional breakpoints - About the 90% of the people I've paired programmed with don't know about it or even if they know it exists they don't use it and when I put in place some conditional break points they look with awe at how it can make a change.
- A somewhat counter intuitive or controversial advice: read the code involved and look for inconsistencies, dumb scenarios, flags, awful named variables and clean them! - Sometimes my attention span and memory range is entangled with garbage code that makes it harder to reason about. By throwing away the pieces I don't like and improving on them I get the benefit that in the future it will be easier to maintain and reason.
(personal anecdote) I strongly agree with this. Writing it down is very important. Whenever I trace through a call stack, I tend to construct a visualization of it in my mind (in the form of a DAG + each root starting with the caller, if you will). But I could never hold that image especially when it gets too complex. Forcing myself to write it down helped me a lot - it doesn't matter if it's messy writings or drawings, somehow the _act of writing_ just helped me so much in reconciling concepts and connecting the dots.
It also works across every programming language and development stack. It can be setup across network and application boundaries. Debugging skills with logging is extremely portable.
Have a hypothesis and try to prove otherwise.
Start cordoning off parts of the codebase where the problem ISN'T.
Leave printf breadcrumbs to trace execution.
I find my less capable colleagues make the mistake of false assumptions, like some subroutine is executing or what state an object is in without proving it to themselves. That keeps your investigation from proceeding without luck on the larger problems.
"You need a few more years of study before you can fully understand. But! You have a 600 page textbook. Do every exercise in that book. When you can do that without assistance, get another 600 page textbook and repeat."
He was trying to get us to build an intuition for certain class problem solving, while at the same time saying "Shut up and calculate".
I find that problem-solving in the programming space is the same. Just keep doing things. You'll develop an intuition for it eventually.
What also helps is just shutting the computer off and going for a walk, generally the moment you step away you'll figure it out!
By the time you're done describing the problem and its symptoms, you'll have a solution.
I can also recommend going for a walk. I usually return from long motorcycle trips with the best ideas. Tea on the balcony is also effective.
Underrated. Diffused mode of brain.
The very first thing should probably be to carefully check which commit introduced the error, and then carefully study the code changes in that one for clcues.
After that, and possibly based on that, what probably most people will do is start their debugging process by running a debugger through some suspicious parts of the code, based on varying degrees of well-informed suggestions.
For problems that escape those first tries though, what you often need to do is to start a systematic process of ruling out possible sources for the bug. In many ways this resembles scientific studies where you try to control as many variables as possible, and also include control samples with known states, trying to zoom in on only the particular variable you are studying without noise from other things.
That can mean feeding the system or code under study with carefully set up data for which you know what the effect should be, and then carefully trying to change each part of it and observing the outcomes. Things like that.
In my experience, this type of effort will eventually most often lead to the solution. The main challenge I think though, is to realize how deeply you might need to go with the systematization and automation of things, to really rule out possible sources and start zooming in on the general area around where the bug is. You might need to take some real drastic measures, and this is where I see most people who fail, don't go far enough. Here you might need to really get away from the screen to get your thoughts flowing more freely ... but not in an undirected way, but rather trying to answer the question "How can I do this in an even more systematic way ... to rule out even more possible sources, or identify unexpected behavior".
Not super easy to put into words, but this is in my experience the way to go.
Finally, one caveat is that there are certain things you should probably check before even going the systematic path. Things that can totally screw things up so that whatever you do, you never get any systematic pattern of behavior or behavior change. These things often are related to caches in various form. Make sure to turn off any and all kinds of caches in the system. They will almost guaranteed drive you insane otherwise.
Git bisect is a very useful tool for this assuming you have an easily reproducible case for the problem. (So generally it is decidely less useful when the bug is the result of a race condition)
Debugging has been one of those for me.
A long time ago I helped my mother with a murder mystery by researching how doctors diagnose things (I was a professional information broker then, with Dialog, Lexus/Nexus, GratefulMed, etc.), as much as I could without going to medical school. I learned a lot about differential diagnosis. That got me hooked on medical shows, where I learned a little more. At some point after that I wondered if I could apply differential diagnosis methods to debugging. Because there are often multiple possible causes for a bug, I found the differential diagnosis approach to work amazingly well for me.
I call this process D3 (Differential Diagnosis Debugging).
Below is roughly how I apply it. I am working on a book including this as a couple chapters, but that won't be out for at least another year. The material in this post is in the book, so I am told I must copyright anything smacking of an excerpt.
First, capture all the relevant details of the expected behavior. Create a unit test (or tests) to confirm the expected behavior.
Next, capture all the differences between the observed behavior and the expected behavior (the 'symptoms').
Then, examine those differences to come up with possible hypothesis about the causes.
After that, use a concept similar to Karnaugh Maps [1] to determine a sequence of small discrete unit tests whose truth (if true hypothesis could be true) determines a T or F for each hypothesis. If you wind up with more than one T then you need more tests (diagnostic testing).
Once you have a confirmed hypothesis, apply a fix an rerun all your tests. Rinse and repeat as needed, if needed (treatment), until all of your expected behavior tests pass.
Unpublished Work © Copyright 2022 William A. Barnhill, Jr. Some rights reserved. You may apply the D3 process as described herein; you may not incorporate the D3 process into a written work, a web site, or an email; you may discuss the D3 process if full attribution to the author is given.
Please don't hate me for the above folks. Been told I need to include that if I want to get published.
[1] https://en.wikipedia.org/wiki/Karnaugh_map
Trademark applies to special identifiers of products and services.
Patents apply to inventions. It may apply to processes, given tangible form.
In short, you are taking bad advice. Get better lawyers.
In the alternative that you think your licensing terms mean anything:
1. By reading these words you agree, on behalf of yourself and your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that you believe I have entered into with you or your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges.
2. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.