(shoot 'em 'fore they run now)
Shotgun spots when feature or bugfixes require large or complex changes to the codebase. These are normally ok at the beginning or even the middle of a project but if they carry on to the end of a project then it indicates that the codebase is going to be difficult to maintain.
i.e. if a lot of fixes require changes all over the place, then your code is hard to maintain
This works by performing an analysis of the git history to determine the "shotgun" complexity (more on this below) of an application. This differs from McCabe's cyclomatic complexity insofar as it doesn't examine the graph of the underlying code so much as it examines the graph of how files get changed over time by examining the git history and deriving a score based on the inter-relatedness of the files.
Perhaps the easiest way to use this is with the shotgun-gradle-plugin
What does this produce?
NOTE: When deriving the heatmap for a given day, it is the median score for that day that is used.
Where:
Active Commit Sets
are those sets of files that are committed at the same time most often.Active Files
are those files that are committed most often.
The intention is to spot hotspots in the code that require a lot of attention
Each individual day can also be examined to see what commits went into the calculation of that day's score.
To build the commandline tool:
./gradlew build shadowJar
And to use it, just pass in -h
to the fatjar to see the options
java -jar build/libs/shotgun-${version}-all.jar -h
The only mandatory option is -i, --input-dir
Usage: <main class> [options]
Options:
-m, --commit-minimum
Files and sets with a commit size less than this are ignored
Default: 3
-cf, --commit-size-file
We want the files in the top N counts (this may result in more than N
results)
Default: 40
-cs, --commit-size-set
We want the sets in the top N counts (this may result in more than N
results)
Default: 10
-h, --help
Print this help message
* -i, --input-dir
The working directory, must be a git root directory
-ll, --legend-levels
The legend levels
Default: [15, 30, 45, 60, 90, 120, 150]
-o, --output-file
The output file location
Default: .shotgun/report.html
-s, --source-set
A source set to split by, defaults to a gradle standard
Default: [src/main/java, src/main/resources, src/main/webapp, src/test/java, src/test/resources]
A sample usage where we want to specify source sets, but otherwise accept the defaults might be:
java -jar build/libs/shotgun-all.jar \
-i ~/workspaces/billing-validation
There is also a gradle plugin for this: shotgun-gradle-plugin
The shotgun coherency score is a function of how close to each other the files in a given commit are, where:
- Files in the same directory are considered closer than files in different directories
- Files in directories directly above/below each other are considered closer than files in adjacent directories
The expected behaviour of a project is that, while there may be lots of changes all over the codebase at the start of the project, one would expect that as the architecture settles down that any changes that are added are in small commits, each centered on a small number of packages.
As a project gets to be more mature, however, if a bugfix or new feature still requires a commit in many different packages, then that points to a need to refactor the codebase to simplify things.
A commit's shotgun score is derived as follows:
- Ignore any commit that is a
merge
: we don't want to risk double-counting, or be at the mercy of whether or not the merge was fast-forwarded. - Ignore any
DELETE
entries - removing code doesn't add to the complexity ;) - Identify the source tree of each file, e.g.
src/main/java
src/main/resources
src/main/webapp
src/test/java
src/test/resources
- Then, for each of these source trees, determine the lowest common tree for
the commits, e.g.
- For Files
com/sonalake/application/service/BobService.java
com/sonalake/application/domain/BobRepository.java
com/sonalake/application/domain/Bob.java
com/sonalake/application/domain/BobType.java
- Because
com/sonalake//application
is common to all of these files, this gets simplified to:application/service/BobService.java
application/domain/BobRepository.java
application/domain/Bob.java
application/domain/BobType.java
- For Files
- The score for this source tree is then calculated as a simple count of the
edges
of this graph, or if there is only a single file present, the score will be1
. - The overall score for the commit is the sum of the scores for the source trees.
Should a given day have multiple commits, then it is the median of that day's scores that will be used.
Should a weekend have any commits then management owes the team an apology ;)