Spaghetti code is a pejorative word used by readers that cannot figure out the code structure. The mental image of who does what is just too hard to get. We will see here how you can use Call graphs to deal with the refactoring of entangled spaghetti. (Si, questi sono bucatini…)
The call graph is a log of the different routines calling each other. It traces what the reader would look if he were to perform an exhaustive read in the chronological order.
A call graph generated for a simple computer program in Python.
In python there are several utilities for this task:
At CERFACS most of the user will get it on OSX using
brew install graphviz
(Beware, brew can mess up your Python installation by upgrading it). This tutorial will use
pycallgraph3, which we install this way:
pip install pycallgraph3
We will create a small script to investigate a target main function, for the example run_cli().
We will use function
call_graph_filtered() as a wrapper for
In the arguments we find the target function
cli_run - Note that there is no
() since we pass the function, not its result-.
custom_include is narrowing the callgraph to a limited set of packages. It is highly recommended to limit the depth of the packages you want to trace. Indeed, without any limitation, python callgraph can become extremely large.
#!/usr/bin/env python (...) from oms.plan40.run_plan40 import cli_run call_graph_filtered( cli_run, custom_include=["oms.*", "pyavbp.*"],# "kokiy.*", "ms_thermo.*", "arnica.*"], )
The wrapper function
call_graph_filtered() is defined as the following.
from pycallgraph3 import PyCallGraph from pycallgraph3 import Config from pycallgraph3 import GlobbingFilter from pycallgraph3.output import GraphvizOutput def call_graph_filtered( function_, output_png="call_graph_png", custom_include=None ): """A call graph generator filtered""" config = Config() config.trace_filter = GlobbingFilter( include=custom_include) graphviz = GraphvizOutput(output_file=output_png) with PyCallGraph(output=graphviz, config=config): function_()
The output here is rather wide due to the structure of this specific cli_run, but this is a real-life example.
The call graph shows clearly
oms, the package of the function, heavily relying on a second package
You can get deeper graphs by including more packages, or investigate only one portion of the graph.
The spaghetti can be visible from the call graph. The following picture focuses on an earlier version of the code. You can spot 6-8 methods are calling each other, and it is hard to figure out who called who -which is a code smell to me- . This is where we had to refactor things a lot.
Among the many ways to print a large image, this is a low tech version:
- Open the image in an Excel spreadsheet
- Zoom out to see the printing zones lines
- paste your image into Excel
- resize the image to fit on the printing grid
- preview then print (BW, single page)