computer science, programming and other ideas
Hi guys, it has been a while since the last post.
I write this short post to tell you about a small script I coded recently. You can find it here on my github account.
Its goal is to draw the “include” dependencies between classes in a C++ project. In particular, it allows to detect circular dependencies very easily or to check the architecture of a project.
You can see the output of the script on a project of mine:
I really like this visual representation which allows to see how classes interact.
However, the true reason why I created this tool is not because I like to see beautiful graphs but because I hate circular dependencies (note that there is none in the graph above). I consider circular dependencies as design flaws. But sometimes in a large project, it could happen that accidentally I create circular dependencies …
Fistly, let us be clear about what is a circular dependency.
Suppose that you have two classes A and B. If A uses B and conversely then there is a circular dependency. However, the circular dependency maybe subtler. For instance, it may be A that uses B that uses C that uses A.
In C++, if a file “A.h” includes “B.h” then “B.h” cannot include “A.h”. The only way for B to use A is to forward declare A, use pointers or references on A in the header and finally include “A.h” in “B.cpp”.
For example, these three files should compile successfully.
A.h:
#pragma once
#include "B.h"
class A
{
private:
B mB;
};
B.h:
#pragma once
class A; // forward declaration
class B
{
private:
A* mA;
};
B.cpp:
#include "B.h"
#include "A.h" // We include "A.h" in the cpp file
This works well and is often the simpler solution for small projects.
Why so much hate toward circular dependencies after all?
I will list several reasons against circular dependencies:
There are many ways to avoid circular dependencies. The most obvious one is to design well the project with independent modules or even to break a big project in smaller libraries. Moreover, it may be okay to have a small circular dependency inside a small module/library but not through the whole project.
Design patterns like Dependency injection, Observer or Event Queue can also be of great help. By the way, I recommend you to have a look at Game Programming Patterns by Robert Nystrom if you have not read this book already.
Finally, you could use my little script to monitor the architecture of your project.
The script is pretty simple. I use the re
module of Python to find the #include
s in source files.
Then the scripts creates a graph and uses a port to Python of graphviz to draw the graph. Graphviz creates very beautiful, this amazing tool is also used, for instance, by Doxygen to generate its diagram.
That’s all for this short post. See you!
If you are interested in my adventures during the development of Vagabond, you can follow me on Twitter.