I have a hierarchy of objects, mostly connected by composition. I.e. (not showing the class methods for readability):
class A {}
class B {A a;}
class C {B b;}
etc...
class Z {Y y;}
class Z
provides the API for executing commands from another program – a control panel. I think this is called Chain of Responsibility, but might be at an error.
Now when an action on A needs to be performed, Z
calls a method of Y
, because Z
knows only about Y
. So on, until we reach A
, which has the proper members and methods to handle the request.
This has the fundamental problem that debugging becomes tedious – one has to step in
many times, and each time be careful not to miss the actual working method – otherwise the debugging session needs to be restarted.
How do I make debugging more straightforward? A change in the architecture of a method of using gcc
that is more suited to this situation?
3
When you split up big methods into smaller ones, and separate concerns to many classes, and write more functions with clearly separated levels of abstraction, you will end up with long call chains, that’s true. But when you choose your abstractions well your code becomes much more readable and understandable than the typical 200 lines-of-code-spagetthi function.
And readable code avoids a lot of bugs at first hand, so to my experience this outweighs the increased debugging efforts caused by the call chains by a multitude. So my advice is: live with it. Don’t use this “long call chains make debugging harder” argument as an excuse for not refactoring your code into smaller functions, such reasoning leads always to a fallacy.
As a hint for debugging when you accidentally missed the cause of an error: some debuggers (like gdb) allow you to “undo” your last steps in the middle of a debugging session.
3
Use logging. (I don’t know what are logging options in c++, but i would be surpirsed if there wasn’t something close to what is available in Java).
Log entering and exiting method and what is going in/out (and either do it in configurable automatic way, or only in interesting places). This way you know where it went and if you can formulate postconditions and preconditions, you can check them when going through logs to find where something strange is happening and then put breakpoint to the right place.
When same method is called repeatedly and only after log run it breaks down on border case, you can use conditional breakpoint, because you know (from logs) what data causes problem.
This will require multiple runs to examine problem, but log run will be without manual steping, so much faster, and in debug run you will start closer to actuall problem (or at least its closer to its manifestation.
3