Our lives as programmers are a never ending series of one problem after another.
If we are lucky, our daily slog at the keyboard is occasionally punctuated with brief glorious moments where it all "just works" and we feel like gods.
If we are really, really lucky these serendipitous interludes also happen to coincide with events like "product demos" and "production releases".
In between these glorious moments we toil through the frustration of one problem after another. We are not programmers, we are just hacking out the first solution that works.
This post will describe a bunch of different problem solving strategies. You might not have time to read it all now, so bookmark it for later.
Share this post with your team if you find it useful.
The Prime Directive
1. Don't write defects in the first place. The best defect to fix is the one that you never wrote to start with.
- Nicely unit tested code
- Enforce database constraints
- Use an input validation framework
- Avoid unimplemented "else" conditions
- Understand how to use something in isolation before you use it in the main application
- Pepper your code with exceptions for situations you don't expect to happen
2. Print Statements. The ye-olde print statement is still as useful after thirty years of programming as the first time I used one in high-school. Think carefully about the problem and often one or two lines of extra output will help to isolate the problem.
3. Switch to Fine Logging. Sometimes when an underlying library or product is not telling you why it is failing then increasing the verbosity of the logging can uncover additional clues.
4. Search the Logs. If there’s too much logging to read easily, search through the log files for keywords or error codes.
5. Wrap on, Wrap off. Controlling the logging viewed with word wrap on or off can also be helpful. Sometimes you want it on and sometime you want it off.
6. Search different logs. The main server log might not be the only useful log to search through. Java application servers often produce other log files that might be useful.
7. Windows Event Log. Another source of log files might be from the operating system itself.
8. Craft useful logging. Sometimes if you are not getting any useful logging then you might need to write it yourself. For example if you have a complicated data structure to deal with you might include carefully crafted “dump” statements at strategic points in order to get the visibility you need to solve the problem. This can also be quite handy when dealing with multi-thread problems. Sometimes half the problem is just “seeing” what’s actually happening.
Talk to Someone
9. Ask someone who might know. If the problem is something that you think someone else on the team might know about then ask them. When you join a new project or team there are going to be dozens of things that you just won’t know about and someone else on the team will. Don’t be afraid about looking dumb. It’s far better for the team as a whole to get the work done efficiently and get you up to speed as quickly as possible. The only sin is to keep asking the same question over and over, so when someone tells you the answer that you make a note of it somewhere and don’t ask the same question twice.
10. Ask dumb questions. You might think they’re dumb questions but they probably are not. If you don’t know the answer then it probably isn’t a dumb question.
11. Explain the problem to a teammate. They might know the answer or be able to suggest things that you haven’t thought about.
12. Explain the problem to your dog. It really doesn’t matter who you explain the problem to it’s the act of explaining that gets your brain to analyse the problem from different angles.
13. Write a problem statement. Write the most accurate and precise problem statement you can. By being precise you are challenging your brain to accurately describe the problem which in turn helps you to think of possible solutions.
14. Write a problem diary. Create a text file with a bunch of notes to yourself about the different things you have tried. Include snippets of code or configuration settings and also any errors produced
15. Keep a record of your problems and solutions. Have you ever had a problem where you know you've solved it once before but you can't remember how? Once you have a problem and then solved the problem, document the solution somewhere that's easy to search (preferably across your team) like a wiki, defect tracker, or just email it to yourself.
16. Read the FAQ. Check the Frequently Asked Questions before submitting your support request if there is one.
17. Submit a support request. If you have support available for a product or library then use it. Often the support desk is in a different timezone so when you get to the end of the day put together a support request and let someone else work on it overnight for you.
18. Before you Click Send. What you will also find is that the act of writing the support request will prompt you to think about the problem again and you often solve the problem or come up with new things to try before you even hit the send button.
19. Support. Breakthrough 1st and 2nd level support and talk to the real developers directly and preferably in real-time, chat/skype/screen sharing.
Move Away from the Keyboard
20. Go for a walk. Taking a break from solving a problem is a good way of generating fresh ideas of things to try. Walking by yourself is a good way to put your brain into neutral and let it swirl around the problem.
21. Sleep on it. Likewise sleeping on the problem is also good for letting your brain come to grips with the key aspects of a problem.
22. Reset the Priority. The other good thing about taking break from a problem is that it lets your reevaluate how important a problem is. The issue might be a CSS/Layout issue that just simply isn’t worth spending 16 hours on solving, so be careful that you are spending your time effectively.
23. Ignore the problem. If you ignore the problem what you find happens is that you will be hypersensitive to related keywords for the next week or so. What will happen is when you are reading something like JavaLobby all of these keyword related articles will suddenly jump out you and might contain useful information.
24. Which line of code. Are you sure you know which line of code is causing the problem. Sometimes it helps to temporarily insert additional print statements between each line of code with nothing more than a “line x” statement. Watch carefully and you might be surprised that sometimes the flow through the code isn’t what you thought it was.
25. Break out into an example program. If you are having problems with a library or product then sometimes it helps to break out the related code into a program separated from the main application. This might take a little time to setup but often dealing with an isolated example program is easier to deal with than your project’s overall build process. Once you’ve got “something” working then it gives you a basis for comparison with the main program.
Change the Code
Even if you don’t know how to solve the problem changing the code anyway can be an effective technique for problem solving.
26. Write New Unit Tests. Since you have to spend time with this code anyway, write some new unit tests. It’s helpful to get your brain thinking about the code in the same way that the computer is.
27. Refactor it. Problem code is often messy code. Tidy-up the code by applying simple refactorings like renaming variables, or unwrapping nested if/then/else blocks.
28. Find Bugs. Another code tidy-up technique is to look at any “Find Bugs” reports you have for the related code and start working with those first. Get the code into a tidy state first because;
It’s an easy cost-effective way of getting your brain focused on the code The problem may become more apparent
29. Rewrite It. One technique is to dump all of the related code and rewrite it from scratch. A fresh perspective may avoid the problem altogether.
30. Comment out unnecessary code - or at least what you "think" is unnecessary. You may discover that your understanding of the flow through the code isn't quite what you thought it was.
31. Experiment. If you are not sure how the underlying product or library is working then it can useful to introduce little experiments particularly around boundary conditions.
32. Periodically go back to a clean state. If you have been making lots of different changes either in the code or with configuration settings it’s important to periodically go back to a clean slate. Otherwise side-effects from experiment number “3” might affect the “right” answer and so you never actually discover the right solution.
33. Switch Technologies. If you are having problems with one particular technology it might be worth dumping it and switching to a different technology.
Products and libraries can be great when they work and head-banging frustrating when they don’t. Here are some product specific strategies.
34. Upgrade to a Later Version. Maybe the problem you are dealing with has already be found and fixed. Try upgrading to a later version of the product.
35. Downgrade to an Earlier Version. Maybe the problem is being caused by incompatibilities with other products/libraries you are using and needs to be downgraded to something more compatible.
36. Patch the product. Have you been able to find a fault in the product, or maybe you just need it to behave differently for your application, patch over it with the problem. More useful for Open Source project, be careful with commercial products as you might violate the license or support agreements.
37. Download and attach source code. It’s a good investment of time at the beginning of the project to download all source code (that you can) and attach it to the classpath in your IDE. Since we all know how accurate the documentation is the best documentation you have is the source code itself.
38. Read the Manual. Most developers would probably consider this a low-probability strategy but hey, you never know maybe the answer is contained in the documentation.
39. Read the Right Version of the Manual. Be careful that you are reading the right version of the manual.
40. Is the Manual Right? Of course often even it’s the right version of the manual that might be the problem since the code has been updated and the manual hasn’t.
For an article that’s all about debugging, there’s not a lot to say about debuggers. Personally I find they have limited value since they are slow to use. In the time it takes to step through the debugger a couple of times I could have written a new unit test that will be an “asset” into the future rather than the “expense” of just using the debugger.
41. Learn the Keyboard shortcuts. Hands-up if you know what the keyboard shortcuts are for stepping through the code? Hmm... I thought so.
42. Step Backwards. If you do step over the wrong line of code then look for the debugger feature that lets you step back through the code. Of course this is typically called something useful like “Drop Stack Frame” rather than “step backwards”.
43. Write code for breakpoints. Debuggers will let you have conditional breakpoints but I tend to find it easier to just drop in a few lines of temporary code to catch the condition I want to break into the code at.
44. Break on Exception. One somewhat useful feature of debuggers is the ability to catch a particular exception class from wherever it’s thrown.
45. Use a specialized tool. There are lots of other kinds of debugging tools available now. For example;
46. Defect Number Hooks. Have you ever had a problem where something gets fixed one way and then a few weeks later becomes a bug that gets fixed a different way (by someone else). Sometimes the issue might flip-flop between two “right answers” until eventually someone has fixed it often enough that they actually remember something is happening here. The solution to this problem is to tag each source code change with the related defect number and keep a more detailed description of why something was changed and who was involved in the decision.
47. Blame. To aide in find out who changed a particular line Subversion has a great little feature called “Blame” which tells you the username of the person to last change a particular line of code.
48. Git bisect. Git has an interesting command called “bisect” that will automagically perform a binary search through your commit history looking for the first commit that introduce a fault.
Searching for Answers
49. Search Google. Searching Google can be effective, the trick is to know when, and also to not spend too much time on it. If you have some nice juicy keywords like an unusual Exception message or error code it can be quite effective to run a quick search. Open the first five or so links that look related and quickly scan them to see if they are on topic or not. But don’t spend hours digging through Google until you have tried some of the other strategies first.
50. Google Bundles Forums Posts. One thing to watch out for with Google is that it tends to bundle multiple posts from a forum into a single search result. If you only click on the first link you might not know that there are other posts in the forum that might be more useful.
51. Search product specific forum. Google can’t see into all forums, so sometimes you need to search through a product specific forum directly.
52. Search Stack Exchange. Google tends to search stack exchange anyway, but sometimes it’s better to search it directly.
53. Create a Stack Exchange Question. If you can’t find the answer then maybe you can ask it.
54. Hire an expert. It might be cost effective to bring in an expert for a short period of time. For example if the bulk of the team are not web designers then hire a Web designer for a short period and have them work on multiple CSS issues at the same time.
55. Give it to an Intern. The opposite of hiring an expert is to hire a newbie. Sometimes beginners full of enthusiasm can solve problems just because they come at it from a different angle.
56. Change the requirement. If you can’t fix the defect then change the requirement. Your client might change their mind about fixing IE6 defects when you explain how much it’s going to cost them.
57. Change an upstream/downstream system. If you can’t find why a particular input file is causing a problem maybe you don’t allow that kind of data into the system. Maybe the defect becomes a “business validation” error before it’s accepted into the system.
58. Don't run before you can walk. When adopting a new technology understand the basics before jumping into the advanced stuff. Practice makes Perfect.
59. Check your configuration by making it break. Change key configuration values and make sure it breaks in the way that you think it should because that way you can be more confident (though not absolutely certain) that the configuration settings you do have, are possibly right.
60. Be methodical. Sometimes you have to get three or four combination of things right at the same time. If you find yourself dealing with multiple variations of something at the same time then keep a careful note of which combination of things that you’ve tried and be sure to try every combination (if you think it’s required).