A Developer’s Quest for Cleaner Code
Have you ever read a piece of code and felt like you were traversing a labyrinth with no end in sight? Maybe you started to wonder if there was a better way to measure just how tangled that codebase really is. In software development, that mysterious “tangled-ness” often goes by the name complexity. And if you’re tired of fumbling through the jungle of complicated functions, this post is your treasure map.
Let’s set the scene: you’re an intrepid code explorer seeking to find (and tame) the “complexity beasts” lurking in your code. Your trusty tools on this journey include Clang-Tidy, a powerful static analysis tool, and Pulse, your watchtower for collecting metrics. So strap on your explorer gear, and let’s begin!
Act I: Understanding Our Goal
Cyclomatic Complexity vs. Cognitive Complexity
Before we dive into the technical adventure, let’s define what we’re measuring:
- Cyclomatic Complexity: Measures the number of linearly independent paths through your code. It’s often explained in terms of branching structures like
ifstatements, loops, etc. - Cognitive Complexity: A more modern measure that emphasizes how difficult code is for humans to read and comprehend. While cyclomatic complexity deals with logical paths, cognitive complexity deals with the mental overhead required to follow those paths.
Which one are we using? Although the script references “Cyclomatic Complexity” in the description, it leverages Clang-Tidy's readability-function-cognitive-complexity check. So effectively, we’re hunting the Cognitive Complexity Beast: the measure of how tough it is for developers to wrap their heads around the code.
Act II: Preparing Your Arsenal
Step 1: Installing Clang-Tidy and Build Tools (in Ubuntu WSL2)
Our journey takes place in a Linux-like environment (WSL2 Ubuntu). Here’s how to install and ready your tools:
apt update
apt install -y software-properties-common wget
add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic main'
wget https://apt.llvm.org/llvm-snapshot.gpg.key
apt-key add llvm-snapshot.gpg.key
apt update
apt install -y clang-tidy-13
apt install -y python3-pip build-essential g++ cmake gcc
These commands:
- Update your system’s package lists.
- Add the LLVM repository for Ubuntu Bionic (works for many WSL setups).
- Install clang-tidy-13 and some essential development tools for compiling C/C++ code.
Act III: Unveiling the Magic Script
Below is the script we’ll use to track down complexity:
- Clones your repository.
- Scans for C/C++ files.
- Generates a
compile_commands.json—a file that Clang-Tidy references to compile (virtually) each file. - Runs Clang-Tidy to measure complexity.
- Prints a helpful table to show which functions exceed your specified complexity threshold.
- Sends these metrics to Pulse, your monitoring fortress.
#!/bin/bash
# Configuring Pulse Variables
PULSE_API_KEY=<your Pulse API key>
PULSE_BASEURL="https://pulse-metrics.com"
PULSE_API="/api/v1/projects/22/repositories/37"
# Repository URL
REPOSITORY_URL="https://bitbucket.org/workspace/repo.git"
# Cognitive Complexity Threshold
THRESHOLD=0
# Create a new directory to clone the repository into.
mkdir -p cc_repository
cd cc_repository
# Cloning the repository
git clone $REPOSITORY_URL .
# Full path of the current script
SCRIPT_PATH=$(readlink -f "$0")
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
PROJECT_DIR="$SCRIPT_DIR"
# Temporary file for storing file paths
FILE_LIST="$SCRIPT_DIR/files.txt"
OUTPUT_JSON="$SCRIPT_DIR/compile_commands.json"
# Generate a list of C/C++ files
find $PROJECT_DIR -name "*.c" >> $FILE_LIST
find $PROJECT_DIR -name "*.cpp" >> $FILE_LIST
find $PROJECT_DIR -name "*.C" >> $FILE_LIST
find $PROJECT_DIR -name "*.cxx" >> $FILE_LIST
find $PROJECT_DIR -name "*.c++" >> $FILE_LIST
# Start compile_commands.json
echo "[" > $OUTPUT_JSON
# Loop through each found file and add compilation info
while read -r FILE; do
FILE_DIR=$(dirname "$FILE")
case "${FILE##*.}" in
c)
COMMAND="gcc -c -o output.o $FILE"
;;
*)
COMMAND="g++ -c -o output.o $FILE"
;;
esac
echo " {" >> $OUTPUT_JSON
echo " \"directory\": \"$FILE_DIR\"," >> $OUTPUT_JSON
echo " \"command\": \"$COMMAND\"," >> $OUTPUT_JSON
echo " \"file\": \"$FILE\"" >> $OUTPUT_JSON
echo " }," >> $OUTPUT_JSON
done < $FILE_LIST
# Tidy up JSON (remove trailing comma and close bracket)
sed -i '$ s/,$//' $OUTPUT_JSON
echo "]" >> $OUTPUT_JSON
rm $FILE_LIST
# Temporary file for clang-tidy output
TEMP_FILE=$(mktemp)
# Run clang-tidy
run-clang-tidy-13 \
-p $PROJECT_DIR \
-checks='readability-function-cognitive-complexity' \
-config="{CheckOptions: [{key: readability-function-cognitive-complexity.Threshold, value: $THRESHOLD}]}" \
-quiet > $TEMP_FILE
# Print a header
printf "\n%s\n" "---------------------------------------------------------------------------------------"
printf "%s (threshold: %s)\n" "Functions that have Cognitive Complexity value more than its threshold" "$THRESHOLD"
printf "%s\n" "---------------------------------------------------------------------------------------"
echo ""
printf " %-60s | %s \n" "clang-tidy:[filename]/[function_name]/[line-number]" "Cognitive Complexity"
printf " %-60s | %s \n" "---------------------------------------------------" "-------------------"
function_counter=0
# Read output line-by-line
while read -r line
do
if [[ $line == *"has cognitive complexity of"* ]]; then
function_counter=$((function_counter + 1))
filename=$(echo $line | cut -d':' -f1)
line_number=$(echo $line | cut -d':' -f2)
function_name=$(echo $line | cut -d"'" -f2)
cognitive_complexity=$(echo "$line" | sed -n -E 's/^.*has cognitive complexity of ([0-9]+).*$/\1/p')
printf " %-60s | %18s \n" "clang-tidy:${filename}/${function_name}/${line_number}" "${cognitive_complexity}"
fi
done < $TEMP_FILE
rm $TEMP_FILE
# Print summary
echo ""
printf "\n%s\n" "---------------------------------------------------------------------------------------"
printf "%s: %s\n" "The number of the functions with Cognitive Complexity above threshold" "${function_counter}"
printf "%s\n\n" "---------------------------------------------------------------------------------------"
# Record the date and sum in date_and_sum
echo "`date '+%Y/%-m/%-d'` $function_counter" > date_and_sum
DATE=`date '+%Y/%-m/%-d'`
METRICS=$function_counter
# Sending data to Pulse
curl -i -X POST $PULSE_BASEURL$PULSE_API/cognitive_complexity_snapshots/upsert?api_key=$PULSE_API_KEY \
-d "cognitive_complexity_snapshot[target_date]=$DATE&cognitive_complexity_snapshot[value]=$METRICS"
Act IV: Embarking on the Quest
-
Save the script as
cognitive-complexity.sh(or any.shyou like). -
Make it executable:
chmod +x cognitive-complexity.sh -
Run the script:
./cognitive-complexity.sh
What happens next is like shining a flashlight into the dark corners of your code:
- The script clones your specified Bitbucket repository into
cc_repository. - It hunts for all files ending with
.c,.cpp,.C,.cxx,.c++. - It creates a
compile_commands.jsonthat clang-tidy references to check each file’s complexity. - Then clang-tidy pounces, analyzing every function and measuring its cognitive complexity.
- Finally, a summary is printed to your screen, revealing the complexity ratings of each function that surpasses the threshold you set. If a function’s complexity is higher than you’d like, you’ve found your “beast” to tame!
Act V: Reporting Your Heroics
At the end of the run, you’ll see a table:
clang-tidy:[filename]/[function_name]/[line-number] | Cognitive Complexity
clang-tidy:...some_file.c/myComplicatedFunction/42 | 18
clang-tidy:...another_file.cpp/anotherFunction/100 | 12
...
And below the table, you’ll get a final count of how many functions soared past the threshold. This count is automatically sent to Pulse via the script’s final curl command, creating a cognitive complexity snapshot on the date you run it.
If you log into your Pulse dashboard, you’ll see metrics that show the number of complexity beasts found. Over time, you can watch that number shrink as you refactor for clarity and maintainability—a real sign you’re winning the battle against unwieldy code.
Epilogue: A Simpler Path
By now, you’ve successfully navigated the labyrinth and learned a handful of new buzzwords:
- Cognitive Complexity – the “readability overhead” that a piece of code imposes on a developer.
- Pulse – your metrics fortress, where you send daily snapshots of your code’s complexity.
- clang-tidy – the watchful sentinel that analyzes your code, highlighting potential trouble spots.
Embracing these tools transforms your code cleanup from a guesswork chore into a data-driven triumph. You’ve taken the first steps toward understanding and taming the complexity monster that might have once lurked in your repository. With every run of this script and every error pruned, your codebase becomes more maintainable—and less beastly to read.
Happy exploring, and may your complexity stats trend ever downward!