Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

How do you keep everything straight as you trace deeply into a piece of software through multiple method calls, object constructors, object factories, and even spring wiring. I find that 4 or 5 method calls are easy to keep in my head, but once you are going to 8 or 9 calls deep it gets hard to keep track of everything. Are there strategies for keeping everything straight?

In particular, I might be looking for how to do task x, but then as I trace down (or up) I lose track of that goal, or I find multiple layers need changes, but then I lose track of which changes as I trace all the way down. Or I have tentative plans that I find out are not valid but then during the tracing I forget that the plan is invalid and try to consider the same plan all over again killing time....

Is there software that might be able to help out? grep and even eclipse can help me to do the actual tracing from a call to the definition but I'm more worried about keeping track of everything including the de-facto plan for what has to change (which might vary as you go down/up and realize the prior plan was poor).

In the past I have dealt with a few big methods that you trace and pretty much can figure out what is going on within a few calls. But now there are dozens of really tiny methods, many just a single call to another method/constructor and it is hard to keep track of them all.

share|improve this question
1  
You didn't tag this with any particular language, but this problem is definitely a lot worse with some languages and frameworks. In some debuggers (Eclipse) you can suppress stack frames by package. This helps a lot with dynamic JVM languages like Groovy, and frameworks like Spring, Hibernate, or legacy EJB containers. –  kevin cline Nov 24 '11 at 19:32
    
The language is in general Java but there are some modules that mix Java + Scala + Clojure. Really to be honest it looks like the extra indirection is not adding much really but it looks like they are huge fans of indirection and 1-2 line methods... –  Cervo Nov 25 '11 at 18:26
    
Great question! I would add that the maximum limit tends to vary from developer to developer. I work with some past VB devs who go nuts when they are debugging code that has a call stack more than 3-4 calls deep. –  Kevin McCormick Apr 1 '12 at 14:03
add comment

3 Answers 3

After talking with other people at work and digging around on my own, I have found that naming makes or breaks the approach of many small methods, and some trust that your co-worker is not crazy. The principle of least surprise is very important.

With more objects is more places to introduce code and side effects. If you want to surprise someone, more classes mean more constructors (possibly static initialization blocks and other similar structures as well) and more instance variables. If you want to hide code and obfuscate it, more objects certainly helps. While each individual method and even object might be simple, the grand sum of them tends to be more complex. Additionally if you have something like Spring that can also introduce additional logic/side effects which have to do with the code it is even worse......

Naming too. One problem I found is often a method says DoesX, but in reality the method does x, y, z, a, b, c. So I have difficulty reasoning what any code does. In a few giant methods, you would look at the methods and see a bunch of code doing unexpected things which your eyes would notice and be like WTF. You would probably spot the code for y, z, a, b, c. In reading split objects you would read code that says DoesX and first assume it does X. Then later when you still do not understand what is going on and start reading everything you eventually come to DoesX and see oh it is doing y,z,a,b,c. But if the methods are named correctly, then things work better because often you can understand what is going on without even reading the methods. If DoesX just does X, then you can get away without reading it unless X is broken.

I have noticed that though we use Java, and through Spring wiring any object can really be wired up to an interface, they tend to just jump between classes using eclipse navigation. If there is an interface, they tend to use eclipse to show all implementations (generally just 1) and then jump to it. In reality Spring could wire up an implementation from some other library, or do something you do not expect (which in very few places does happen) but in general they just rely on things to be done the expected way. I even found an Open Implementation extension to let me click on a method on an interface and auto jump to the implementing class's version. Still a few places are a mess and do not work as expected. But those are generally considered bad code and candidates for a rewrite. Also there are some design techniques that are common knowledge around the company, so recognizing these techniques also aids understanding. They are not documented anywhere, but after being like WTF and reading it, then I see the same technique again and instantly recognize it. Again...someone could do some unexpected stuff to mess with you.

Martin Vejmelka sort of mentioned unit tests as a way of knowing the code is performing well. I interviewed at a place which used unit tests like this, as ways to assert what code was doing. When debugging their technique was to think what assertions must be wrong for the behavior to occur, then to create unit tests with the expected behavior that fail, then to go fix them. In my current place they have unit tests but they do not cover all the code, and they also suffer from huge amounts of indirection making reading the unit tests as challenging as reading the code, and sometimes even harder.

share|improve this answer
add comment

If you have your project covered by unit tests and integration test, you shouldn't need to hold the whole picture in your mind.

Using test-driven development and XP practices - well named methods (semantic names), you should work easily at each level of abstraction.

In case you need add new functionality to your project, you simply write new test and try to get that test working as quickly as possible. If there is no possibility to add the functionality (for some structural reason - unaccessible extension points...), refactor your code (repeatedly check your changes don't brake the code running your test suite).

Try to give your test cases semantic names:

@Test
public void shouldReturnSumOfSuppliedNumbers() {
     // test code here
}

will give you more information than:

@Test
public testSum() {
     // test code here
}
share|improve this answer
add comment

I tend to have a sheet of paper (the reverse side before it goes in the recycler) partially held by my keyboard that I scribble notes on, and call stacks get scribbled down there too - in a shorthand form.

You could day that if you your call stacks go on for 10, 20 or even 30 method calls, then you've got one mess on your hands. You need to make the objects much clearer - so that you can skip pieces of the call stack as "obvious" and therefore unnecessary to remember. (eg, you don't really need to step into the assembly when you add 2 ints together, you trust it to work).

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.