Skip to content

Commit 31038bb

Browse files
committed
feat: Add new documentation on Python code complexity checkers and measuring function complexity, and update index with links
1 parent a8a5c45 commit 31038bb

4 files changed

Lines changed: 917 additions & 24 deletions

File tree

.vitepress/config.mts

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,43 @@ export default defineConfig({
6868
{
6969
text: "Penify Blogs",
7070
items: [{
71+
text: "⚖️ Python Code Complexity Checkers: A Comprehensive Comparison",
72+
link: "/docs/python-code-complexity-checkers-comparison.md",
73+
},
74+
{
75+
text: "🔍 Measuring Function Complexity in Python: Tools and Techniques",
76+
link: "/docs/measuring-function-complexity-python.md",
77+
},
78+
{
7179
text: "🧠 How Much Docstring Is Enough? A Practical Guide for Python Developers",
7280
link: "/docs/how-much-docstring-is-enough.md",
7381
},
82+
{
83+
text: "📚 Automated Code Documentation: A Decade in Review",
84+
link: "/docs/automated-source-code-documentation.md",
85+
},{
86+
text: "🤖 Automate Code Documentation with Penify-CLI",
87+
link: "/docs/code-documentation-with-cli.md",
88+
},
89+
{
90+
text: "📩 Semantic Commit Messages: Elevating Your Code Quality and Collaboration",
91+
link: "/docs/semantic-commit-messages.md",
92+
},{
93+
text: "✍️ Simplify Git Commits with Penify-CLI's Summary Generator",
94+
link: "/docs/commit-summary-with-cli.md",
95+
},
96+
{
97+
text: "🐍 Four Common Docstring Formats in Python",
98+
link: "/docs/common-docstring-format-in-python.md",
99+
},{
100+
text: "🌱 Penify Genesis",
101+
link: "/docs/penify-genesis",
102+
}]
103+
},
104+
{
105+
text: "General",
106+
items: [
107+
74108
{
75109
text: "Amazon to Scrap Local Alexa Processing: All Voice Commands Moving to the Cloud",
76110
link: "/docs/Amazon-to-Scrap-Local-Alexa-Processing.md",
@@ -107,30 +141,7 @@ export default defineConfig({
107141
text: "🔍 A Comparative Overview of LangChain, Semantic Kernel, AutoGen",
108142
link: "/docs/comparative-anlaysis-of-langchain-semantic-kernel-autogen.md",
109143
},
110-
{
111-
text: "📚 Automated Code Documentation: A Decade in Review",
112-
link: "/docs/automated-source-code-documentation.md",
113-
},
114-
{
115-
text: "✍️ Simplify Git Commits with Penify-CLI's Summary Generator",
116-
link: "/docs/commit-summary-with-cli.md",
117-
},
118-
{
119-
text: "🤖 Automate Code Documentation with Penify-CLI",
120-
link: "/docs/code-documentation-with-cli.md",
121-
},
122-
{
123-
text: "📩 Semantic Commit Messages: Elevating Your Code Quality and Collaboration",
124-
link: "/docs/semantic-commit-messages.md",
125-
},
126-
{
127-
text: "🐍 Four Common Docstring Formats in Python",
128-
link: "/docs/common-docstring-format-in-python.md",
129-
},
130-
{
131-
text: "🌱 Penify Genesis",
132-
link: "/docs/penify-genesis",
133-
}]
144+
]
134145
},
135146
],
136147
socialLinks: [
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
---
2+
title: "🔍 Measuring Function Complexity in Python: Tools and Techniques"
3+
description: "Learn how to quantify Python function complexity using specialized libraries like Radon, McCabe, and Wily. Discover practical techniques for improving code quality and maintainability by identifying overly complex functions."
4+
publishedAt: "2025-05-01"
5+
updatedAt: "2025-05-01"
6+
isPublished: true
7+
tags:
8+
- Python
9+
- Code Quality
10+
- Static Analysis
11+
- Complexity
12+
- Best Practices
13+
layout: doc
14+
author: Suman Saurabh
15+
linkedInUrl: https://www.linkedin.com/in/ssumansaurabh/
16+
image: https://www.penify.dev/banner.png
17+
---
18+
19+
# 🔍 Measuring Function Complexity in Python: Tools and Techniques
20+
21+
> "Simple is better than complex. Complex is better than complicated." — The Zen of Python
22+
23+
Every Python developer has encountered that function—the one that spans hundreds of lines, with nested loops, multiple conditionals, and enough decision points to make your head spin. But how do you objectively determine when a function has become **too complex**?
24+
25+
In this post, we'll explore how to measure function complexity in Python using specialized libraries and tools, and how to interpret these metrics to improve your code quality.
26+
27+
---
28+
29+
## What is Code Complexity?
30+
31+
Before diving into tools, let's understand what we're measuring. **Cyclomatic complexity** is the most common metric, developed by Thomas McCabe in 1976. It quantifies the number of linearly independent paths through a program's source code.
32+
33+
In simpler terms, it measures:
34+
- How many decisions (if/else, loops, etc.) exist in your code
35+
- How difficult your code is to test completely
36+
- The cognitive load required to understand the function
37+
38+
A function with higher complexity is typically:
39+
- More prone to bugs
40+
- Harder to maintain
41+
- More difficult to test thoroughly
42+
- More challenging to understand
43+
44+
---
45+
46+
## 📊 The Complexity Scale
47+
48+
Here's a general guideline for interpreting cyclomatic complexity scores:
49+
50+
| Complexity Score | Risk Level | Interpretation |
51+
|-----------------|------------|----------------|
52+
| 1-5 | Low | Simple function, easy to maintain |
53+
| 6-10 | Moderate | Moderately complex, consider refactoring |
54+
| 11-20 | High | Complex function, refactoring recommended |
55+
| 21+ | Very High | Highly complex, immediate refactoring needed |
56+
57+
---
58+
59+
## 🛠️ Tools for Measuring Function Complexity
60+
61+
Let's explore some popular Python libraries that can help measure function complexity:
62+
63+
### 1. Radon: The Comprehensive Choice
64+
65+
[Radon](https://radon.readthedocs.io/) is a Python tool that computes various code metrics including cyclomatic complexity.
66+
67+
```bash
68+
# Install Radon
69+
pip install radon
70+
71+
# Analyze a file
72+
radon cc your_file.py
73+
74+
# Analyze a file with detailed output
75+
radon cc your_file.py -s
76+
```
77+
78+
Sample output:
79+
```
80+
your_file.py
81+
F 1:0 my_simple_function - A (2)
82+
F 10:0 my_complex_function - C (15)
83+
```
84+
85+
The letters (A, B, C...) correspond to complexity risk levels, with A being the lowest risk and F being the highest.
86+
87+
### 2. McCabe: The Focused Choice
88+
89+
The [McCabe](https://pypi.org/project/mccabe/) package specifically targets cyclomatic complexity.
90+
91+
```bash
92+
# Install McCabe
93+
pip install mccabe
94+
95+
# Create a Python script to use McCabe
96+
python -m mccabe --min 5 your_file.py
97+
```
98+
99+
This command will list functions with complexity of 5 or higher.
100+
101+
### 3. Wily: For Tracking Complexity Over Time
102+
103+
[Wily](https://wily.readthedocs.io/) is perfect for tracking how your code's complexity evolves over time:
104+
105+
```bash
106+
# Install Wily
107+
pip install wily
108+
109+
# Build the Wily cache for your project
110+
wily build your_directory/
111+
112+
# See complexity metrics
113+
wily report your_file.py
114+
115+
# Track changes in complexity over time
116+
wily graph your_file.py:my_complex_function -m cyclomatic
117+
```
118+
119+
### 4. Flake8 with McCabe Plugin: For CI/CD Integration
120+
121+
If you're already using Flake8, you can integrate complexity checking:
122+
123+
```bash
124+
# Install Flake8 with McCabe plugin
125+
pip install flake8
126+
127+
# Run Flake8 with complexity checking
128+
flake8 --max-complexity 10 your_file.py
129+
```
130+
131+
This flags functions with complexity higher than 10.
132+
133+
---
134+
135+
## 📝 Hands-On: Identifying Complex Functions
136+
137+
Let's analyze a real Python function and measure its complexity:
138+
139+
```python
140+
def process_user_data(users, filters=None, sort_by=None):
141+
"""Process user data with optional filtering and sorting."""
142+
result = []
143+
144+
# Apply filters
145+
if filters:
146+
filtered_users = []
147+
for user in users:
148+
include_user = True
149+
for key, value in filters.items():
150+
if key in user:
151+
if isinstance(value, list):
152+
if user[key] not in value:
153+
include_user = False
154+
break
155+
else:
156+
if user[key] != value:
157+
include_user = False
158+
break
159+
else:
160+
include_user = False
161+
break
162+
163+
if include_user:
164+
filtered_users.append(user)
165+
else:
166+
filtered_users = users.copy()
167+
168+
# Apply sorting
169+
if sort_by:
170+
if isinstance(sort_by, str):
171+
filtered_users.sort(key=lambda x: x.get(sort_by, None))
172+
else: # Assume it's a function
173+
filtered_users.sort(key=sort_by)
174+
175+
return filtered_users
176+
```
177+
178+
Let's measure this with Radon:
179+
180+
```bash
181+
$ radon cc -s example.py
182+
example.py
183+
F 1:0 process_user_data - E (21)
184+
```
185+
186+
A score of 21 indicates very high complexity! Let's refactor:
187+
188+
```python
189+
def filter_user_by_criteria(user, filters):
190+
"""Check if a user matches all filter criteria."""
191+
for key, value in filters.items():
192+
if key not in user:
193+
return False
194+
195+
if isinstance(value, list):
196+
if user[key] not in value:
197+
return False
198+
elif user[key] != value:
199+
return False
200+
201+
return True
202+
203+
def process_user_data(users, filters=None, sort_by=None):
204+
"""Process user data with optional filtering and sorting."""
205+
# Apply filters
206+
if filters:
207+
filtered_users = [user for user in users if filter_user_by_criteria(user, filters)]
208+
else:
209+
filtered_users = users.copy()
210+
211+
# Apply sorting
212+
if sort_by:
213+
sort_key = sort_by if callable(sort_by) else lambda x: x.get(sort_by, None)
214+
filtered_users.sort(key=sort_key)
215+
216+
return filtered_users
217+
```
218+
219+
Now let's measure again:
220+
221+
```bash
222+
$ radon cc -s refactored.py
223+
refactored.py
224+
F 1:0 filter_user_by_criteria - B (6)
225+
F 14:0 process_user_data - A (4)
226+
```
227+
228+
Much better! We've reduced our main function's complexity from 21 to 4 by extracting a helper function.
229+
230+
---
231+
232+
## 🔄 Integrating Complexity Checks into Your Workflow
233+
234+
For ongoing code quality, consider:
235+
236+
1. **Adding complexity checks to pre-commit hooks**:
237+
238+
```yaml
239+
# .pre-commit-config.yaml
240+
- repo: https://github.com/pycqa/flake8
241+
rev: 6.1.0
242+
hooks:
243+
- id: flake8
244+
args: ["--max-complexity=10"]
245+
```
246+
247+
2. **Including complexity in CI/CD pipelines**:
248+
249+
```yaml
250+
# GitHub Actions example
251+
jobs:
252+
code-quality:
253+
runs-on: ubuntu-latest
254+
steps:
255+
- uses: actions/checkout@v3
256+
- name: Set up Python
257+
uses: actions/setup-python@v4
258+
with:
259+
python-version: '3.10'
260+
- name: Install dependencies
261+
run: |
262+
python -m pip install --upgrade pip
263+
pip install radon
264+
- name: Check code complexity
265+
run: |
266+
radon cc --min C .
267+
```
268+
269+
3. **Setting complexity thresholds in your IDE**:
270+
271+
Most Python IDEs (like PyCharm) can be configured to highlight overly complex functions as you code.
272+
273+
---
274+
275+
## 🧠 Beyond Cyclomatic Complexity
276+
277+
While cyclomatic complexity is useful, consider these other metrics too:
278+
279+
1. **Cognitive Complexity**: Similar but focuses on how hard it is for humans to understand
280+
2. **Maintainability Index**: A composite metric including complexity, lines of code, etc.
281+
3. **Function Length**: Simple but effective - functions over 50-100 lines are usually too complex
282+
283+
---
284+
285+
## 💡 When to Refactor Complex Functions
286+
287+
High complexity doesn't always mean bad code. Consider refactoring when:
288+
289+
1. The function has both high complexity AND high churn (frequently changed)
290+
2. You're having trouble understanding your own code
291+
3. Tests are difficult to write or becoming unwieldy
292+
4. New team members struggle to understand the function
293+
294+
Remember the rule of thumb: **If it's hard to explain, it's probably hard to maintain.**
295+
296+
---
297+
298+
## 🌟 Tips for Reducing Function Complexity
299+
300+
1. **Extract Helper Functions**: Break large functions into smaller, focused ones
301+
2. **Early Returns**: Exit functions early for edge cases
302+
3. **Polymorphism**: Replace complex conditional logic with polymorphic classes
303+
4. **Function Composition**: Chain simple functions together instead of one complex one
304+
5. **Remove Redundant Conditions**: Simplify boolean logic where possible
305+
306+
---
307+
308+
## 📚 See Also
309+
310+
- [Common Docstring Format in Python](./docs/common-docstring-format-in-python.md)
311+
- [How Much Docstring is Enough?](./docs//how-much-docstring-is-enough.md)
312+
- [Automated Source Code Documentation](./docs//automated-source-code-documentation.md)
313+
314+
---
315+
316+
## 🔍 Final Thoughts
317+
318+
Measuring function complexity isn't just about chasing lower numbers—it's about writing code that's easier to understand, test, and maintain. By incorporating complexity metrics into your development workflow, you can identify problematic functions before they become maintenance nightmares.
319+
320+
Remember that these tools are guides, not rules. Use your judgment when deciding what needs refactoring, and focus on making your code more maintainable rather than optimizing for metrics alone.
321+
322+
> "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

0 commit comments

Comments
 (0)