Home

02 Code Quality in Practice: From Working Code to Professional Software

lecture code-quality pep8 ruff linting formatting best-practices python

1. Introduction: When Working Code Isn’t Enough

You’ve built your Road Profile Viewer application. The visualization is beautiful, the camera ray intersects perfectly with the road profile, and when you click “Run,” everything works. Congratulations! 🎉

But then… you push your code to GitHub, open a pull request, and automated checks fail. ❌

$ git push origin feature/road-profile-viewer

Running automated checks...
❌ Ruff check: 14 errors found
❌ Format check: 8 files need formatting
❌ Type check: 6 type errors detected

Pull request blocked - fix issues before merging

What happened? Your code works, but it doesn’t meet professional quality standards. This is the gap between hobbyist code and production-ready software.

This lecture is about understanding that gap and closing it—not with tedious manual reviews, but with automated tools that make quality enforcement effortless and consistent.


2. Learning Objectives

By the end of this lecture, you will:

  1. Understand the difference between code that works and code that meets professional standards
  2. Recognize common code quality issues through real examples from the Road Profile Viewer project
  3. Distinguish between overlapping quality concepts: style guides, linting, formatting, type hints, and type checking—understanding what each one does and how they complement each other
  4. Learn about Python’s style guide (PEP 8) and why it matters for professional development
  5. Master modern quality tools:
    • Ruff: The all-in-one linter and formatter that enforces PEP 8 and catches bugs
    • Pyright: The type checker that validates type annotations and prevents type-related errors
  6. Understand why we need both Ruff AND Pyright: They work on different layers (style/bugs vs. type safety)
  7. Apply automated checks to catch issues before human review
  8. Build quality into your workflow from day one, not as an afterthought
  9. Appreciate the business case for code quality: reduced costs, faster debugging, and better team collaboration

3. Part 1: The Reality Check - Our Code Under Scrutiny

Let’s look at real code from the Road Profile Viewer project and see what automated tools find.

3.1 The Code We Wrote

Here’s a snippet from main.py:

"""
Road Profile Viewer - Interactive 2D Visualization
"""

import numpy as np
from dash import Dash, html, dcc, Input, Output
import plotly.graph_objects as go
import sys,os  # Multiple imports on one line


def generate_road_profile(num_points=100,x_max=80):
    """Generate a road profile using a clothoid-like approximation."""
    x = np.linspace(0, x_max, num_points)
    x_norm = x / x_max
    
    # Clothoid approximation
    y=0.015 * x_norm**3 * x_max + 0.3 * np.sin(2 * np.pi * x_norm)
    y = y - y[0]
    
    return x,y


def HelperFunction(val):
    """Unused helper function that violates naming convention"""
    result=val*2
    return result


# Later in the code...
camera_x,camera_y = 0,2.0

marker=dict(size=12,color='red',symbol='circle')

# Add camera ray - This is a very long comment that exceeds the recommended 79 character line length limit specified in PEP8 style guide

First impression? Looks reasonable. It works. But let’s see what the tools say…

3.2 Running Automated Checks

$ uv run ruff check .

src/road_profile_viewer/main.py:16:1: E401 Multiple imports on one line
src/road_profile_viewer/main.py:16:8: F401 `sys` imported but unused
src/road_profile_viewer/main.py:16:12: F401 `os` imported but unused
src/road_profile_viewer/main.py:24:5: E701 Multiple statements on one line (colon)
src/road_profile_viewer/main.py:29:5: E225 Missing whitespace around operator
src/road_profile_viewer/main.py:32:12: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:36:1: N802 Function name should be lowercase
src/road_profile_viewer/main.py:36:1: F841 Local variable `HelperFunction` is assigned but never used
src/road_profile_viewer/main.py:38:11: E225 Missing whitespace around operator
src/road_profile_viewer/main.py:43:14: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:43:24: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:45:8: E225 Missing whitespace around operator
src/road_profile_viewer/main.py:45:13: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:47:1: E501 Line too long (149 > 88 characters)

Found 14 errors.

14 errors in code that works perfectly! This is the difference between “runs on my machine” and “meets professional standards.”

These automated tools are checking against Python’s established style guidelines and best practices. We’ll explore the foundation of these standards—Python’s official style guide—later in this lecture. For now, let’s understand what each issue means and why it matters.

Let’s break down each issue and understand why it matters.


4. Part 2: Understanding Code Quality Issues

4.1 Issue Category 1: Import Violations

4.1.1 The Problem

import sys,os  # BAD: Multiple imports on one line

What’s wrong?

Ruff reports:

4.1.2 Why This Matters

For Computers: Importing unused modules wastes memory and startup time. In this small example it’s negligible, but in large applications with dozens of unused imports, it accumulates.

For Humans: When you see import sys, you expect the code to use sys somewhere. Unused imports create confusion:

For Maintainability: Unused imports accumulate over time. Someone adds an import for a quick test, forgets to remove it, and it stays forever. After a few years, your file has 20 imports but only uses 10. Good luck figuring out which ones are safe to remove!

4.1.3 The Fix

# GOOD: Separate imports, only import what you use
import numpy as np
from dash import Dash, html, dcc, Input, Output
import plotly.graph_objects as go

Python Best Practice: Imports should usually be on separate lines for better readability and maintainability.

# Good
import os
import sys

# Bad
import sys, os

4.2 Issue Category 2: Missing Whitespace

4.2.1 The Problem

# BAD: Missing spaces everywhere
def generate_road_profile(num_points=100,x_max=80):
    y=0.015 * x_norm**3 * x_max
    return x,y

camera_x,camera_y = 0,2.0
result=val*2
marker=dict(size=12,color='red',symbol='circle')

Ruff reports:

4.2.2 Why This Matters

Readability is King

Compare these two versions:

# Hard to read - everything runs together
marker=dict(size=12,color='red',symbol='circle')

# Easy to read - clear separation
marker = dict(size=12, color='red', symbol='circle')

Which one can you parse faster? The second one, obviously.

The Science Behind It

Research in cognitive psychology shows that humans parse visual information in “chunks.” Whitespace creates natural boundaries between chunks, making code easier to scan and understand.

Your brain processes the second version ~30% faster because it doesn’t have to work as hard to segment the information.

Real-World Impact

Version Control Benefits

Consistent whitespace placement helps avoid unwanted diffs in future commits. Consider this scenario:

# Inconsistent spacing - changes create noisy diffs
marker=dict(size=12,color='red')  # First commit
marker = dict(size=12, color='red', symbol='circle')  # Later: added parameter

The diff shows the entire line changed, making it hard to see that only symbol='circle' was added. Compare to:

# Consistent spacing from the start - clean diffs
marker = dict(size=12, color='red')  # First commit
marker = dict(size=12, color='red', symbol='circle')  # Later: only new param visible

Key Principle: When everyone on the team follows the same whitespace rules, git diffs show actual logic changes instead of style changes. This makes code reviews faster and more effective.

4.2.3 The Fix

# GOOD: Proper spacing
def generate_road_profile(num_points=100, x_max=80):
    y = 0.015 * x_norm**3 * x_max
    return x, y

camera_x, camera_y = 0, 2.0
result = val * 2
marker = dict(size=12, color='red', symbol='circle')

Professional Python Style:


4.3 Issue Category 3: Naming Conventions

4.3.1 The Problem

def HelperFunction(val):  # BAD: PascalCase for function
    """Unused helper function"""
    result = val * 2
    return result

Ruff reports:

4.3.2 Why This Matters

Naming Conventions are a Shared Language

In Python, there are established naming patterns that signal different types of code elements:

These conventions are formalized in Python’s official style guide, which we’ll explore in detail later in this lecture.

When you see HelperFunction(), your brain thinks “class.” When you try to use it and discover it’s a function, you experience cognitive dissonance. This tiny moment of confusion happens hundreds of times while reading code.

The Principle: Least Astonishment

Code should behave as readers expect. Breaking conventions creates astonishment (and frustration).

Real Example from Production

# Someone wrote this in a real codebase
DatabaseConnection = connect_to_db()  # Looks like a class!

# Later, another developer tried:
conn = DatabaseConnection()  # TypeError: 'Connection' object is not callable

This cost 2 hours of debugging because the naming violated expectations.

4.3.3 The Fix

# GOOD: Clear naming convention
def helper_function(val):
    """Calculate double of input value."""
    result = val * 2
    return result

But wait! This function is also never used…


4.4 Issue Category 4: Dead Code

4.4.1 The Problem

def HelperFunction(val):
    """Unused helper function"""
    result = val * 2
    return result

# This function is never called anywhere in the code

Ruff reports:

4.4.2 Why This Matters

Dead Code is Technical Debt

Every line of code has a cost:

1. Maintenance Cost Someone has to maintain it, even if it’s not used:

2. Cognitive Cost Future developers must read and understand it:

3. Testing Cost Dead code often gets tested anyway:

Real-World Scenario

# You need to change how coordinates work
# You update all the coordinate functions...

def calculate_position(x, y, z):  # ✅ Updated!
    return Vector3D(x, y, z)

def transform_point(point):  # ✅ Updated!
    return point.to_3d()

def HelperFunction(val):  # ❌ Forgot to update this!
    # Still using old 2D logic
    return val * 2

The dead code doesn’t break anything (it’s not called), but it sits there like a ticking time bomb. If someone discovers it later and tries to use it, it will use outdated logic.

4.4.3 The Fix

# GOOD: Delete dead code
# If you need it later, that's what version control (Git) is for!

Key Principle: If code isn’t being used, remove it. Git remembers everything.


4.5 Issue Category 5: Line Length

4.5.1 The Problem

# Add camera ray - This is a very long comment that exceeds the recommended 79 character line length limit specified in PEP8 style guide

Ruff reports:

4.5.2 Why This Matters

Screen Real Estate is Limited

Consider these scenarios:

Scenario 1: Side-by-side Code Review

[Your Code File     ] | [Reviewer's Comments  ]
[                   ] | [                     ]

With 150-character lines, the reviewer must scroll horizontally constantly. This is exhausting and error-prone.

Scenario 2: Split-Screen Development

[Implementation File] | [Test File           ]
[Old Code Version  ] | [New Code Version   ]

Long lines force you to work full-screen, losing productivity benefits of split views.

Scenario 3: Code Diffs

- marker=dict(size=12,color='red',symbol='circle',shape='diamond',opacity=0.8,line=dict(width=2,color='black'))
+ marker=dict(size=12,color='blue',symbol='circle',shape='diamond',opacity=0.8,line=dict(width=2,color='black'))

Can you spot the change? It’s color='red'color='blue', but it’s buried in a 120-character line.

Compare to:

  marker = dict(
      size=12,
-     color='red',
+     color='blue',
      symbol='circle',
      shape='diamond',
  )

The change is immediately obvious.

The Research

Studies show comprehension drops significantly when reading lines longer than 60-80 characters. Your eyes must make longer saccades (jumps), and you lose your place more easily.

4.5.3 The Fix

# GOOD: Break long lines logically
marker = dict(
    size=12,
    color='red',
    symbol='circle'
)

# GOOD: Break long comments
# Add camera ray - This comment is broken into multiple lines
# to respect recommended line length limits for better readability

Professional Style Guideline:


5. Part 3: The Bigger Picture - Why Code Quality Matters

Now that we’ve seen specific issues, let’s zoom out and understand the principles.

5.1 Principle 1: Code is Read More Than Written

The 10:1 Rule

For every hour spent writing code, approximately 10 hours are spent reading it.

Think about your development process:

  1. You write a feature (1 hour)
  2. You debug it (read your code) (30 min)
  3. Your colleague reviews it (reads your code) (20 min)
  4. Future you adds a feature (reads old code) (1 hour)
  5. New team member learns the codebase (reads your code) (2 hours)
  6. Someone fixes a bug (reads your code) (1 hour)

Total: 5.5 hours reading, 1 hour writing.

Code quality optimizes for reading, not writing.

5.2 Principle 2: Consistency Reduces Cognitive Load

Your brain is a pattern-matching machine. When code is consistent:

Example:

# Inconsistent spacing - your brain must parse each line individually
x_road,y_road = generate_road_profile(num_points=100, x_max=80)
camera_x, camera_y = 0, 2.0
result= calculate_value(a,b)

# Consistent spacing - your brain recognizes the pattern
x_road, y_road = generate_road_profile(num_points=100, x_max=80)
camera_x, camera_y = 0, 2.0
result = calculate_value(a, b)

The second version feels “smoother” because your brain doesn’t context-switch for each line.

5.3 Principle 3: Early Detection Prevents Expensive Bugs

The Cost of Bugs Grows Exponentially

When Found Relative Cost Example
While writing 1x "Oops, typo!" (5 seconds to fix)
During code review 10x "Let me push a fix..." (5 minutes)
During QA testing 100x "Need to debug, fix, retest" (1 hour)
In production 1000x+ "Emergency hotfix, customer compensation, reputation damage" (days/weeks)

Real Example from Our Code

def find_intersection(x_road, y_road, angle_degrees, camera_x=0, camera_y=1.5):
    # ... code ...
    return x_intersect, y_intersect, distance

# Later:
x_intersect, y_intersect, distance = find_intersection(...)

This works fine. But imagine if someone changed it without type hints:

def find_intersection(x_road, y_road, angle_degrees, camera_x=0, camera_y=1.5):
    # ... code ...
    return distance  # Oops! Forgot to return x and y

# Later:
x_intersect, y_intersect, distance = find_intersection(...)  # BOOM! Runtime error

Without type checking: This bug reaches production, crashes when a user adjusts the angle.

With type checking:

def find_intersection(
    x_road: np.NDArray,
    y_road: np.NDArray,
    angle_degrees: float,
    camera_x: float = 0,
    camera_y: float = 1.5
) -> tuple[float, float, float] | tuple[None, None, None]:
    # ... code ...
    return distance  # Type checker: Error! Expected tuple[float, float, float]

Caught in 2 seconds, not 2 weeks.

5.4 Principle 4: Automation Scales, Humans Don’t

The Math

Imagine you’re a team lead with 10 developers. Without automation:

Manual code review per pull request: 30 minutes
Pull requests per week: 50
Total review time: 25 hours/week

You need to hire someone just for code reviews!

With automation:

Automated checks per pull request: 2 minutes
Pull requests per week: 50
Total automated time: 100 minutes/week
Human review time per PR: 10 minutes (focusing on logic, not style)
Total human time: 8.3 hours/week

Time saved: 16.7 hours/week = 867 hours/year per team

At $100/hour (typical developer cost), that’s $86,700 saved per year.


6. Part 4: PEP 8 - The Foundation of Python Code Quality

Throughout this lecture, we’ve seen automated tools catch issues like import formatting, spacing, naming conventions, and line length. You might be wondering: Where do these rules come from? Who decides what’s “correct”?

The answer is PEP 8—Python’s official style guide that forms the foundation of all these quality standards.

6.1 What is PEP 8?

PEP 8 is Python’s official style guide. PEP stands for “Python Enhancement Proposal”—it’s how Python evolves as a language.

PEP 8 is not suggestions—it’s the community-agreed standard.

6.2 Why Follow PEP 8?

6.2.1 Reason 1: It’s a Common Language

When you follow PEP 8:

It’s like using standard English spelling. You could spell “color” as “colur”, but nobody would understand you.

6.2.2 Reason 2: Tooling Expects It

All major Python tools assume PEP 8:

Fighting PEP 8 means fighting the entire ecosystem.

6.2.3 Reason 3: Hiring and Career

When companies hire Python developers, they expect PEP 8 knowledge. Code that doesn’t follow PEP 8 is a red flag in:

6.3 Key PEP 8 Rules (Quick Reference)

Indentation

# Use 4 spaces per indentation level (not tabs)
def example_function():
    if True:
        print("Four spaces")

Line Length

# Limit lines to 79 characters (or 88 with modern tools)
# Break long lines at logical points
result = some_function(
    argument1,
    argument2,
    argument3
)

Imports

# Imports at top of file, grouped in order:
# 1. Standard library
# 2. Third-party
# 3. Local application

import os
import sys

import numpy as np
from dash import Dash

from myapp import utils

Naming Conventions

# Functions and variables: snake_case
def calculate_average(numbers):
    total_sum = sum(numbers)
    return total_sum / len(numbers)

# Classes: PascalCase
class RoadProfile:
    pass

# Constants: UPPER_CASE
MAX_SPEED = 100
PI = 3.14159

Whitespace

# Yes: Spaces around operators and after commas
result = x + y
my_list = [1, 2, 3]
func(a, b, c)

# No: Missing spaces
result=x+y
my_list=[1,2,3]
func(a,b,c)

Comments

# Use inline comments sparingly
x = x + 1  # Compensate for border

# Use docstrings for functions
def calculate_average(numbers):
    """
    Calculate the arithmetic mean of a list of numbers.
    
    Args:
        numbers: List of numeric values
        
    Returns:
        float: The average value
    """
    return sum(numbers) / len(numbers)

7. Part 5: Modern Tools - The Ruff Revolution

7.1 The Evolution of Python Code Quality Tools

Traditional Approach (Pre-2023):

Each tool had its own configuration, ran separately, and took significant time.

Modern Approach (2024+):

Two tools. Blazingly fast. One configuration file.

7.2 What is Ruff?

Ruff is a modern Python linter and formatter written in Rust. It’s developed by Astral, the same company behind uv.

Key Features:

7.3 Installing Ruff - Understanding Dependency Management

Before we can use Ruff, we need to understand where and how to install it. This is crucial for making our commands work!

7.3.1 The Problem: uv run ruff Won’t Work (Yet)

If you try to run Ruff right now:

$ uv run ruff check .
error: program not found: ruff

Why? Because uv run looks for ruff in your project’s .venv directory, and we haven’t installed it there yet!

This leads us to an important question: Where should we install Ruff?

7.3.2 Three Ways to Use Python Packages

Understanding how to properly manage dependencies is crucial for:

Let’s explore the three contexts where Python packages can be installed:

1. Project Dependencies ([project.dependencies])

What: Packages your application NEEDS to run in production.

For our Road Profile Viewer, these are the packages that must be installed for the application to work:

[project]
dependencies = [
    "dash>=2.14.0",
    "plotly>=5.18.0",
    "numpy>=1.26.0",
]

Add with:

uv add dash plotly numpy

When to use: When the package is required for the application to run.


2. Development Dependencies ([dependency-groups.dev])

What: Tools you need for DEVELOPMENT but not for running the app (linters, formatters, test frameworks).

This is where Ruff belongs! Ruff is a development tool—we use it to check and format our code, but the Road Profile Viewer application doesn’t need Ruff to run.

[dependency-groups]
dev = [
    "ruff>=0.8.0",
    "pytest>=7.0.0",
]

Add with:

uv add --dev ruff pytest

When to use: When you need the tool for development, testing, or code quality but not for running the application.


3. Global Tools (uv tool install)

What: CLI tools you want available system-wide across ALL projects.

uv tool install ruff

When to use: When you want a tool available everywhere without adding it to each project’s dependencies.

Trade-off:


7.3.3 Decision Tree: Where Should Ruff Go?

Is Ruff needed to RUN the Road Profile Viewer application?
└─ NO → It's a development tool

Is Ruff specific to THIS project?
└─ YES → We want consistent versions across the team

Do we want it documented in version control?
└─ YES → Team members should see it in pyproject.toml

✅ Decision: Install as development dependency

7.3.4 Installing Ruff for Our Project

Step 1: Add Ruff as a development dependency

uv add --dev ruff

This modifies your pyproject.toml:

[project]
name = "road-profile-viewer"
version = "0.1.0"
dependencies = [
    "dash>=2.14.0",
    "plotly>=5.18.0",
    "numpy>=1.26.0",
]

[dependency-groups]
dev = [
    "ruff>=0.8.0",  # ← Added here!
]

Step 2: Install all dependencies (including dev)

uv sync --group dev

This installs:

Step 3: Verify Ruff is installed

uv run ruff --version
# Output: ruff 0.8.0

Now uv run ruff will work! 🎉

7.3.5 Understanding uv run Behavior

When you run uv run <command>, here’s what happens:

1. uv looks for pyproject.toml (walking up the directory tree)
2. uv checks if <command> exists in the project's .venv
3. If found: runs it with the project's Python environment
4. If not found: error "program not found"

Common Confusion:

This DOESN’T work (Ruff not in .venv yet):

uv run ruff check .
# Error: program not found

This WORKS (after uv add --dev ruff):

uv run ruff check .
# Runs successfully!

This WORKS (temporary installation, doesn’t modify project):

uv run --with ruff ruff check .
# Creates temporary environment with ruff and runs it

This WORKS (if installed globally):

uv tool install ruff
ruff check .  # Run directly, no uv run needed

7.3.6 Best Practice: Document Development Setup

Add a section to your README.md:

## 8. Development Setup {#development-setup}

### 8.1 Installing Dependencies {#installing-dependencies}

```bash
# Install all dependencies (including development tools)
uv sync --group dev

8.2 Available Development Commands

# Run linter
uv run ruff check .

# Auto-fix linting issues
uv run ruff check --fix .

# Format code
uv run ruff format .

# Run application
uv run road-profile-viewer

This ensures new team members know exactly how to set up their environment!

8.2.1 Summary: Dependency Management with uv

Command Purpose Modifies pyproject.toml?
uv add <package> Add production dependency ✅ Yes → [project.dependencies]
uv add --dev <package> Add development dependency ✅ Yes → [dependency-groups.dev]
uv sync Install production dependencies only ❌ No
uv sync --group dev Install all dependencies (prod + dev) ❌ No
uv run <command> Run command in project environment ❌ No
uv run --with <pkg> <cmd> Temporary package usage ❌ No
uv tool install <package> Install global CLI tool ❌ No

Key Takeaway: Development tools like Ruff should be installed with uv add --dev to:


8.3 Ruff Check - The Linter

Now that Ruff is properly installed as a development dependency, let’s explore what it can do!

What it does: Analyzes code for errors, bugs, style violations, and code smells.

Running Ruff Check:

# Check all files in current directory
uv run ruff check .

# Check specific file
uv run ruff check src/main.py

# Auto-fix issues where possible
uv run ruff check --fix .

# Show what would be fixed without changing files
uv run ruff check --fix --diff .

Example Output:

$ uv run ruff check .

src/main.py:16:1: E401 [*] Multiple imports on one line
src/main.py:16:8: F401 [*] `sys` imported but unused
src/main.py:29:5: E225 [*] Missing whitespace around operator
src/main.py:36:1: N802 Function name `HelperFunction` should be lowercase
src/main.py:47:1: E501 Line too long (149 > 88 characters)

Found 5 errors.
[*] 3 fixable with the `--fix` option.

The [*] indicates auto-fixable issues.

8.4 Ruff Format - The Code Formatter

What it does: Automatically reformats code to follow consistent style (compatible with Black).

Running Ruff Format:

# Format all files
uv run ruff format .

# Check if formatting is needed (doesn't modify files)
uv run ruff format --check .

# Show what would change (diff mode)
uv run ruff format --diff .

Before:

def generate_road_profile(num_points=100,x_max=80):
    y=0.015 * x_norm**3 * x_max
    return x,y

After:

def generate_road_profile(num_points=100, x_max=80):
    y = 0.015 * x_norm**3 * x_max
    return x, y

8.5 Configuring Ruff

8.5.1 Understanding pyproject.toml Sections

Remember that pyproject.toml has two main types of sections:

  1. Standardized sections (defined by Python PEPs):
    • [project] - Project metadata and dependencies
    • [dependency-groups] - Optional dependency groups like dev
  2. Tool-specific sections using [tool.*] namespace:
    • [tool.ruff] - Ruff configuration
    • [tool.pytest] - pytest configuration
    • [tool.mypy] - mypy configuration

This keeps all tool configurations in one file instead of scattered across .ruff.toml, .pytest.ini, etc.

8.5.2 Why Customize Ruff?

Ruff works great with its defaults, but you’ll want to customize it because:

1. Project-Specific Needs

2. Enable More Rules

3. Team Consistency

4. CI/CD Integration

8.5.3 Your Ruff Configuration

Using AI to Focus on Requirements, Not Syntax

Instead of memorizing TOML syntax and Ruff configuration options, let’s use AI to translate our requirements into configuration. This is modern development: focus on what you need, let AI handle the syntax details.

Step 1: Define Your Requirements

Think about what your project needs:

Step 2: Ask AI to Generate Configuration

Use a prompt like this in GitHub Copilot Chat or any AI assistant:

Create a [tool.ruff] configuration for pyproject.toml with:
- Line length: 88
- Target Python 3.12
- Exclude .git, .venv, __pycache__, build, dist
- Enable these rule groups: pycodestyle (E/W), Pyflakes (F), isort (I), 
  pep8-naming (N), flake8-bugbear (B), flake8-comprehensions (C4), pyupgrade (UP)
- Allow auto-fix for all rules
- Format with double quotes, Unix line endings, space indentation

Step 3: Review and Apply

The AI generates the configuration. Always review it - you’re the expert on your project requirements:

[project]
name = "road-profile-viewer"
version = "0.1.0"
description = "Interactive road profile visualization"
requires-python = ">=3.12"

[tool.ruff]
# Set maximum line length
line-length = 88

# Target Python version
target-version = "py312"

# Exclude certain directories
exclude = [
    ".git",
    ".venv",
    "__pycache__",
    "build",
    "dist",
]

[tool.ruff.lint]
# Select rules to enable
select = [
    "E",   # pycodestyle errors
    "W",   # pycodestyle warnings
    "F",   # Pyflakes
    "I",   # isort (import sorting)
    "N",   # pep8-naming
    "B",   # flake8-bugbear
    "C4",  # flake8-comprehensions
    "UP",  # pyupgrade
]

# Ignore specific rules if needed
ignore = []

# Allow auto-fix for these rules
fixable = ["ALL"]
unfixable = []

[tool.ruff.format]
# Use double quotes (like Black)
quote-style = "double"

# Use Unix line endings
line-ending = "lf"

# Indent with spaces
indent-style = "space"

Why This Approach Works:

Focus on Intent - You specify what you want, not how to write it
Faster Development - No looking up exact configuration keys
Learn by Reviewing - You see correct syntax and can modify it
Avoid Syntax Errors - AI knows the exact TOML structure

Remember: AI is your syntax assistant, but YOU decide the requirements based on your project needs!

8.6 Ruff in VS Code - Real-Time Feedback

While command-line Ruff is powerful, the Ruff VS Code extension brings code quality directly into your editor with instant feedback as you type.

8.6.1 Installing the Ruff Extension

Method 1: VS Code Extensions Panel

  1. Open VS Code
  2. Click the Extensions icon (or press Ctrl+Shift+X / Cmd+Shift+X)
  3. Search for “Ruff”
  4. Click Install on the official extension by Astral

Method 2: Command Line

code --install-extension charliermarsh.ruff

Method 3: Quick Open

8.6.2 Real-Time Linting - Instant Feedback

Once installed, Ruff automatically analyzes your code as you type, showing issues with colored underlines:

Red Squiggly Lines = Errors (must fix)

import sys,os  # Red underline: E401 Multiple imports on one line

Yellow Squiggly Lines = Warnings (should fix)

def HelperFunction(val):  # Yellow underline: N802 Function name should be lowercase

Hover to See Details:

Problems Panel:

Benefits:

8.6.3 Auto-Fix with Keyboard Shortcuts

The Ruff extension can automatically fix many issues without leaving your editor.

Quick Fix (Single Issue):

  1. Place cursor on underlined code
  2. Press Ctrl+. / Cmd+. (or click the lightbulb 💡 icon)
  3. Select “Ruff: Fix all auto-fixable problems”
  4. Issue is fixed instantly!

Example:

# Before (cursor on this line)
import sys,os,json  # E401 violation

# Press Ctrl+. and select fix

# After
import sys
import os
import json

Format Document (Auto-Format Everything):

Format Selection:

Fix All Auto-Fixable Issues:

8.6.4 Format on Save - Zero Effort

Configure VS Code to automatically format and fix issues when you save a file.

Enable Format on Save:

  1. Open Settings: Ctrl+, / Cmd+,
  2. Search for: format on save
  3. Check ✅ Editor: Format On Save

Configure Ruff as Default Formatter:

Add to your .vscode/settings.json (in your project):

{
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "explicit",
      "source.organizeImports.ruff": "explicit"
    }
  }
}

What this does:

Result: Press Ctrl+S / Cmd+S → Code is instantly formatted and cleaned! 🎉

8.6.5 Practical Workflow Example

Scenario: You’re writing new code with multiple PEP 8 violations.

Without VS Code Extension:

# You write this code
def ProcessData(input,threshold=10):
    result=[]
    for item in input:
        if item>threshold:result.append(item*2)
    return result

# Later, you run command line
$ uv run ruff check main.py
# See 8 violations
# Manually fix or use --fix
# Re-run to verify

With VS Code Extension:

# As you type, you see:
def ProcessData(input,threshold=10):  # Yellow underline: N802
    #         ^ Yellow underline: E231
    result=[]  # Yellow underline: E225
    #     ^
    for item in input:
        if item>threshold:result.append(item*2)
        #      ^E225    ^E701 compound statement

# You press Ctrl+. on function name → Quick fix "Use lowercase"
# You press Shift+Alt+F → Format entire document

# Result (instant):
def process_data(input, threshold=10):
    result = []
    for item in input:
        if item > threshold:
            result.append(item * 2)
    return result

# All violations fixed in 2 seconds! 🚀

8.6.6 Configuration Tips

Workspace Settings (Project-specific)

Create .vscode/settings.json in your project:

{
  "ruff.lint.args": ["--config=pyproject.toml"],
  "ruff.format.args": ["--config=pyproject.toml"],
  "ruff.organizeImports": true,
  "ruff.fixAll": true,
  "editor.rulers": [88],
  "files.trimTrailingWhitespace": true
}

User Settings (All projects)

Settings → Search “ruff” → Configure:

Disable Conflicting Extensions:

If you have these installed, disable them for Python files:

They conflict with Ruff and slow down your editor.

8.6.7 Troubleshooting

Issue: Ruff extension not working

Solution:

  1. Check extension is installed: Ctrl+Shift+X → Search “Ruff”
  2. Check extension is enabled for workspace
  3. Reload VS Code: Ctrl+Shift+P → “Reload Window”
  4. Check Output panel: View → Output → Select “Ruff”

Issue: Format on save not working

Solution:

  1. Verify settings.json configuration
  2. Check Ruff is default formatter: Ctrl+Shift+P → “Format Document With…” → Select “Ruff”
  3. Save file and check Problems panel for errors

Issue: Too many warnings

Solution:

  1. Configure pyproject.toml to ignore specific rules
  2. Adjust severity: Settings → Search “ruff” → Set severity level
  3. Use # noqa comments for specific lines (sparingly!)
import sys  # noqa: F401 - needed for debugging

8. Part 6: Type Checking with Pyright

8.1 Why Type Checking Matters

Python is dynamically typed, which is flexible but dangerous:

def calculate_discount(price, discount_percent):
    return price * (discount_percent / 100)

# This will crash at runtime!
result = calculate_discount("50", "10")

Type hints (Python 3.5+) let you add type information:

def calculate_discount(price: float, discount_percent: float) -> float:
    return price * (discount_percent / 100)

# Type checker catches this BEFORE runtime
result = calculate_discount("50", "10")  # Error: Expected float, got str

8.2 What is Pyright?

Pyright is a fast, feature-rich type checker for Python, developed by Microsoft.

Why Pyright over mypy?

Feature Pyright mypy
Speed 5-10x faster (TypeScript/Node.js) Slower (Python-based)
VS Code Integration Built into Pylance extension Separate extension needed
Modern Python Excellent 3.10+ support Good support, slower updates
Type Inference More intelligent Good but less sophisticated
Active Development Microsoft-backed, rapid updates Slower update cycle

For this course: We use Pyright because it matches your IDE (VS Code + Pylance), providing a consistent experience.

8.3 Installing Pyright

Just like Ruff, Pyright should be installed as a development dependency:

uv add --dev pyright

This adds Pyright to [dependency-groups.dev] in your pyproject.toml:

[dependency-groups]
dev = [
    "ruff",
    "pyright",
]

Sync and verify:

# Install dev dependencies
uv sync --group dev

# Test Pyright
uv run pyright --version

8.3.1 Windows Setup: Developer Mode Required

⚠️ Windows-Specific Issue:

Pyright is a TypeScript-based tool that requires Node.js. When you run uv run pyright for the first time, uv will automatically install Node.js (v25.0.0+) and create symlinks.

On Windows, you may see this error:

Error: You do not have sufficient permissions to perform this operation.

Why? Creating symlinks on Windows requires either:

Solution: Enable Windows Developer Mode

Run PowerShell as Administrator, then execute:

# Enable Developer Mode (requires admin)
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"

After running this command:

  1. Restart your terminal
  2. Try uv run pyright --version again
  3. Node.js will be installed automatically and Pyright will work

Alternative (not recommended): Run VS Code as Administrator each time. Developer Mode is better because it’s a one-time setup.

8.4 Configuring Pyright - From Lenient to Strict

8.4.1 The Default Problem

By default, Pyright runs in basic mode - it’s very lenient and won’t catch many type issues:

uv run pyright
# Output: 0 errors found in 5 files

This seems good, but it’s misleading! Pyright is ignoring:

Why so lenient? To work with existing Python codebases that have no type hints.

For new projects: We want stricter checking to catch issues early.

8.4.2 Using AI to Configure Strict Type Checking

Define Your Requirements:

For our course project, we want:

Prompt for AI:

Create a [tool.pyright] configuration for pyproject.toml with:
- Type checking mode: strict
- Python version: 3.12
- Include: src directory
- Report missing type stubs: true
- Report unknown member type: warning
- Report unknown parameter type: warning
- Report unknown variable type: warning

Generated Configuration:

Add this to your pyproject.toml:

[tool.pyright]
# Strict type checking
typeCheckingMode = "strict"

# Python version
pythonVersion = "3.12"

# Include only source files
include = ["src"]

# Exclude directories
exclude = [
    ".venv",
    "__pycache__",
    "build",
    "dist",
]

# Reporting options
reportMissingTypeStubs = true
reportUnknownMemberType = "warning"
reportUnknownParameterType = "warning"
reportUnknownVariableType = "warning"
reportUnknownArgumentType = "warning"

Now run Pyright again:

uv run pyright

You’ll likely see many errors now! This is good - these were hidden issues that Pyright is now catching.

Common errors you’ll see:

src/main.py:10:5 - error: Type of "x" is unknown (reportUnknownVariableType)
src/utils.py:25:15 - error: Return type is unknown (reportUnknownParameterType)

Type Checking Modes Explained:

Mode Strictness Use Case
off No checking Legacy code, prototypes
basic Minimal (default) Gradual typing adoption
standard Moderate Most projects
strict Maximum New projects, high quality

For this course: Use strict mode to learn proper type safety from the start.

8.5 Using Type Hints

Basic Types:

# Variables
name: str = "Alice"
age: int = 25
height: float = 1.75
is_student: bool = True

# Functions
def greet(name: str) -> str:
    return f"Hello, {name}"

def calculate_total(items: list[float]) -> float:
    return sum(items)

Optional Types (None Handling):

def find_user(user_id: int) -> User | None:
    """Returns User or None if not found."""
    ...

user = find_user(123)
# Pyright error: 'user' might be None
print(user.name)

# Correct way
if user is not None:
    print(user.name)

Complex Types:

from typing import Callable

# Function that takes a callback
def process(callback: Callable[[int], str]) -> None:
    result = callback(42)
    print(result)

# Correct callback
def my_callback(x: int) -> str:
    return f"Value: {x}"

process(my_callback)  # OK

# Wrong callback
def wrong_callback(x: str) -> str:
    return f"Value: {x}"

process(wrong_callback)  # Pyright error: Signature mismatch

Applying Types to Road Profile Viewer:

import numpy as np
from numpy.typing import NDArray

def generate_road_profile(
    num_points: int = 100,
    x_max: float = 80
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
    """
    Generate a road profile using a clothoid-like approximation.
    
    Args:
        num_points: Number of points to generate
        x_max: Maximum x-coordinate value
        
    Returns:
        Tuple of (x_coordinates, y_coordinates)
    """
    x = np.linspace(0, x_max, num_points)
    x_norm = x / x_max
    
    y = 0.015 * x_norm**3 * x_max + 0.3 * np.sin(2 * np.pi * x_norm)
    y = y - y[0]
    
    return x, y


def find_intersection(
    x_road: NDArray[np.float64],
    y_road: NDArray[np.float64],
    angle_degrees: float,
    camera_x: float = 0,
    camera_y: float = 1.5
) -> tuple[float, float, float] | tuple[None, None, None]:
    """
    Find intersection between camera ray and road profile.
    
    Args:
        x_road: X-coordinates of road profile
        y_road: Y-coordinates of road profile
        angle_degrees: Camera ray angle in degrees
        camera_x: Camera X position
        camera_y: Camera Y position
        
    Returns:
        Tuple of (x_intersect, y_intersect, distance) or (None, None, None)
    """
    # Implementation...
    pass

8.6 Running Pyright

After installation and configuration, check your code for type errors:

# Check all files (respects pyproject.toml configuration)
uv run pyright

# Check specific file
uv run pyright src/main.py

# Show detailed error information
uv run pyright --verbose

What to expect with strict mode:

Example workflow:

# 1. Run Pyright
uv run pyright

# 2. Fix reported type issues in your code
# 3. Run again to verify
uv run pyright

# All clear!
# ✓ 0 errors found in 5 files

9. Summary: From Tools to Practice

9.1 Working Code ≠ Quality Code

Code that runs is just the beginning. Professional software must be:

9.2 The Tools You Need

For Quality Enforcement:

Run checks locally:

uv run ruff check --fix .
uv run ruff format .
uv run pyright

9.3 Follow PEP 8

Python’s style guide is not optional:

9.4 Type Hints Save Time

Type hints catch bugs before they reach production:

9.5 What’s Next?

You now know what to check and how to check it locally. But how do you ensure these checks run automatically on every commit, preventing bad code from reaching your repository?

In Chapter 02 (Feature Development) (Thursday, October 16th), we’ll explore:

We’ll transform your local quality checks into an automated pipeline that enforces quality 24/7, ensuring every commit meets your standards whether you remember to run the checks or not.


10. Understanding the Quality Landscape: Concepts and Tools

Before we dive into the practical assignment, let’s clarify the different quality concepts we’ve discussed and how they relate to each other. This landscape can be confusing because some concepts overlap, and the tools we use (Ruff and Pyright) handle multiple aspects of quality.

10.1 The Quality Concepts

10.1.1 Style Guide (PEP 8)

What it is: A document that defines coding conventions—the “grammar rules” of Python code.

What it covers:

Python’s realization: PEP 8 is Python’s official style guide written by Guido van Rossum and the Python community.

Analogy: Like a language style guide (APA, MLA, Chicago) for writing papers—it tells you the “correct” way to format your code.

Example rule: “Function names should be lowercase with words separated by underscores” → calculate_total() not CalculateTotal()


10.1.2 Linting

What it is: Automated analysis of code to find errors, bugs, stylistic issues, and suspicious constructs.

What it covers:

Python’s realization with Ruff: ruff check analyzes your code against 800+ rules including PEP 8 style rules, common bug patterns, and best practices.

Analogy: Like a grammar checker in Microsoft Word—it highlights issues and suggests fixes.

Example checks:

Key insight: Linting is the process of checking code. PEP 8 is one of the standards being checked. Linters check for PEP 8 compliance AND much more.


10.1.3 Formatting

What it is: Automatically restructuring code to follow consistent style rules—a subset of what a linter checks.

What it covers:

Python’s realization with Ruff: ruff format automatically reformats your code to be PEP 8 compliant (Black-compatible style).

Analogy: Like “Auto-format” in Word—it fixes spacing and layout automatically.

Key distinction:

Example:

# Before
x=1+2

# Linter says: "E225: Missing whitespace around operator"
# Formatter fixes it automatically:
x = 1 + 2

10.1.4 Type Hints

What it is: Optional annotations that specify what types of values variables, parameters, and return values should have.

What it covers:

Python’s realization: Built into Python 3.5+ using the typing module. Added to code by developers.

Syntax:

def calculate_total(prices: list[float]) -> float:
    return sum(prices)

Analogy: Like declaring variable types in statically-typed languages (Java, C++), but optional in Python.

Key insight: Type hints are annotations you write. They don’t do anything by themselves—you need a type checker to validate them.


10.1.5 Type Checking

What it is: Automated analysis that verifies type hints are used consistently and correctly throughout your code.

What it covers:

Python’s realization with Pyright: pyright analyzes your type hints and infers types even where not explicitly annotated.

Analogy: Like a compiler’s type checker in Java or TypeScript—catches type errors before runtime.

Example:

def greet(name: str) -> str:
    return f"Hello, {name}"

result = greet(42)  # Pyright error: Argument of type 'int' cannot be assigned to parameter 'str'

Key distinction:


10.2 How the Concepts Overlap

Here’s where confusion often happens:

┌─────────────────────────────────────────────────────────────┐
│                        PEP 8                                │
│              (The Style Guide - The Rules)                  │
│                                                             │
│  ┌────────────────────────────────────────────────────┐     │
│  │              Linting (ruff check)                  │     │
│  │                                                    │     │
│  │  • Checks PEP 8 style rules ←───────────────────────────── Overlap!
│  │  • Checks for bugs (unused imports, undefined vars)│     │
│  │  • Checks best practices (anti-patterns)           │     │
│  │  • Checks code smells                              │     │
│  │                                                    │     │
│  │     ┌──────────────────────────────────────┐       │     │
│  │     │   Formatting (ruff format)           │       │     │
│  │     │                                      │       │     │
│  │     │  • Auto-fixes style issues ←─────────┼───────┼───── Subset!
│  │     │  • Whitespace, indentation, quotes   │       │     │
│  │     └──────────────────────────────────────┘       │     │
│  └────────────────────────────────────────────────────┘     │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                  Type System (Separate!)                    │
│                                                             │
│  You write:                 Tool validates:                 │
│  ┌─────────────────┐       ┌──────────────────────┐         │
│  │  Type Hints     │  ──→  │  Type Checking       │         │
│  │  (Annotations)  │       │  (pyright)           │         │
│  └─────────────────┘       └──────────────────────┘         │
└─────────────────────────────────────────────────────────────┘

Key Overlaps:

  1. Linting includes style checking
    • ruff check validates PEP 8 style rules
    • But it also checks for bugs and best practices that PEP 8 doesn’t cover
  2. Formatting is a subset of linting
    • Both deal with code style
    • Formatter auto-fixes, linter reports
    • Formatter only handles layout/whitespace, linter checks logic too
  3. Type hints are checked by linters AND type checkers
    • ruff check can find missing type hints (if configured)
    • pyright validates that type hints are used correctly
    • Different tools, complementary purposes

10.3 The Python Realizations: Ruff and Pyright

10.3.1 Ruff: The Multi-Tool

Ruff combines multiple quality checks:

Command What it does What concept Example
ruff check Linting Style guide + bug detection Finds E501 (line too long), F401 (unused import)
ruff format Formatting Style guide enforcement Fixes spacing: x=1x = 1
ruff check --select I Import sorting Style guide (PEP 8 imports) Organizes imports alphabetically

Why this is confusing: Ruff does linting AND formatting, which traditionally were separate tools (Flake8 + Black). Now it’s one tool with two modes.

Mental model:


10.3.2 Pyright: The Type Police

Pyright focuses on type correctness:

What it does What concept Example
Validates type hints Type checking Catches greet(42) when signature is greet(name: str)
Infers types Type checking Knows x = 5 means x: int even without annotation
Finds None issues Type checking Catches user.name when user might be None
Reports missing types Code quality Warns when functions lack type annotations

Why use both Ruff AND Pyright?

They complement each other—one doesn’t replace the other.


10.4 Your Quality Workflow

Step-by-step what happens:

# 1. You write code (with type hints)
def calculate_total(prices: list[float]) -> float:
    x=sum(prices)  # Oops, missing spaces
    return x

# 2. Run the formatter
$ uv run ruff format .
→ Fixes spacing automatically: x = sum(prices)

# 3. Run the linter
$ uv run ruff check .
→ Checks PEP 8 compliance ✓
→ Checks for bugs ✓
→ Checks best practices ✓

# 4. Run the type checker
$ uv run pyright
→ Validates type hints are used correctly ✓
→ Infers types and catches mismatches ✓

Each tool has a specific job:

  1. Formatter (ruff format): Makes code look consistent
  2. Linter (ruff check): Finds style issues + bugs + code smells
  3. Type Checker (pyright): Validates type safety

10.5 Common Confusion Clarified

Q: “Is linting the same as checking PEP 8?” A: No. Linting includes PEP 8 checks but also checks for bugs, best practices, and code smells. PEP 8 is just one standard that linters check against.

Q: “Do I need both ruff check and ruff format?” A: Yes! They serve different purposes:

Q: “Why do I need Pyright if Ruff checks code quality?” A: Ruff checks style and general bugs. Pyright checks type safety. Example:

def greet(name: str) -> str:
    return f"Hello, {name}"

result = greet(42)  # Ruff won't catch this, Pyright will!

Q: “Can Ruff fix everything automatically?” A: No. ruff format fixes style. ruff check --fix fixes some issues (like unused imports). But logical bugs and naming violations need manual fixes.

Q: “Are type hints required?” A: No, Python works fine without them. But they:

Q: “If I follow PEP 8, why do I need tools?” A: Humans make mistakes and miss things. Tools:


10.6 The Complete Picture

The quality stack for Python in 2025:

┌────────────────────────────────────────────┐
│         Developer writes code              │
│         (with type hints)                  │
└─────────────────┬──────────────────────────┘
                  │
        ┌─────────┴─────────┐
        │                   │
        ▼                   ▼
┌───────────────┐   ┌──────────────┐
│  Ruff         │   │  Pyright     │
│               │   │              │
│ • Check style │   │ • Check types│
│ • Format code │   │ • Infer types│
│ • Find bugs   │   │ • Find None  │
│ • Best pract. │   │   issues     │
└───────────────┘   └──────────────┘
        │                   │
        └─────────┬─────────┘
                  │
                  ▼
        ┌─────────────────┐
        │  Quality Code   │
        │                 │
        │ ✓ PEP 8         │
        │ ✓ Less bugs     │
        │ ✓ Type-safe     │
        │ ✓ Consistent    │
        └─────────────────┘

Summary in one sentence: We use PEP 8 as our style guide, Ruff to enforce it and catch bugs, and Pyright to ensure type safety—three complementary layers of quality assurance.


11. Practical Assignment

11.1 Task: Fix the Road Profile Viewer

Let’s fix all the issues in our main.py file step by step.

11.2 Step 1: Remove Unused Imports

Before:

import sys,os  # Multiple imports, both unused

After:

# Removed - not needed

11.3 Step 2: Fix Import Formatting

Before:

import numpy as np
from dash import Dash, html, dcc, Input, Output
import plotly.graph_objects as go

After:

import numpy as np
from numpy.typing import NDArray

from dash import Dash, Input, Output, dcc, html
import plotly.graph_objects as go

Notice:

11.4 Step 3: Fix Function Spacing

Before:

def generate_road_profile(num_points=100,x_max=80):
    y=0.015 * x_norm**3 * x_max
    return x,y

After:

def generate_road_profile(num_points=100, x_max=80):
    y = 0.015 * x_norm**3 * x_max
    return x, y

11.5 Step 4: Add Type Hints

Before:

def generate_road_profile(num_points=100, x_max=80):
    """Generate a road profile using a clothoid-like approximation."""
    # ...
    return x, y

After:

def generate_road_profile(
    num_points: int = 100,
    x_max: float = 80
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
    """
    Generate a road profile using a clothoid-like approximation.
    
    Args:
        num_points: Number of points to generate
        x_max: Maximum x-coordinate value
        
    Returns:
        Tuple of (x_coordinates, y_coordinates) as NumPy arrays
    """
    # ...
    return x, y

11.6 Step 5: Fix Naming Convention

Before:

def HelperFunction(val):
    result=val*2
    return result

After:

# Removed - function was unused dead code

11.7 Step 6: Fix Variable Spacing

Before:

camera_x,camera_y = 0,2.0
marker=dict(size=12,color='red',symbol='circle')

After:

camera_x, camera_y = 0, 2.0
marker = dict(size=12, color='red', symbol='circle')

11.8 Step 7: Fix Long Lines

Before:

# Add camera ray - This is a very long comment that exceeds the recommended 79 character line length limit specified in PEP8 style guide

After:

# Add camera ray to the plot
# This shows the line from the camera in the direction of the specified angle

11.9 Running Checks After Fixes

$ uv run ruff check .
All checks passed!

$ uv run ruff format --check .
All files formatted correctly!

$ uv run pyright
0 errors, 0 warnings, 0 informations

Success! 🎉


12. Part 9: The Business Case for Code Quality

12.1 Why Companies Care About Code Quality

1. Faster Development

2. Fewer Bugs

3. Lower Maintenance Costs

4. Better Collaboration

5. Competitive Advantage

12.2 Real-World Impact: A Case Study

Scenario: A startup with 20 developers.

Without Automated Quality Checks:

With Automated Quality Checks:

Savings: $123,500 per year

Plus intangible benefits:


13. Part 10: Common Pitfalls and How to Avoid Them

13.1 Pitfall 1: “I’ll Add Quality Checks Later”

The Problem: Adding quality checks to an existing codebase with thousands of violations is overwhelming.

The Solution: Start with quality checks from day one. Make the first commit be:

  1. Project structure
  2. pyproject.toml with Ruff configuration
  3. GitHub Actions workflow for quality checks

13.2 Pitfall 2: “My Code Works, Why Does Style Matter?”

The Problem: Focusing only on functionality, ignoring maintainability.

The Solution: Remember: code is read 10x more than written. Quality is an investment in future productivity.

13.3 Pitfall 3: “Auto-formatters Make My Code Ugly”

The Problem: Personal style preferences conflicting with automated formatters.

The Solution: Trust the formatter. Consistency across a team is more valuable than individual preference.

13.4 Pitfall 4: “Type Hints Are Too Much Work”

The Problem: Avoiding type hints because they take extra time to write.

The Solution: Type hints save far more time than they cost by catching bugs early. Start with function signatures.

13.5 Pitfall 5: “CI/CD Checks Slow Us Down”

The Problem: Seeing failed checks as blockers rather than helpers.

The Solution: Run checks locally before pushing. Use pre-commit hooks. CI becomes a safety net, not a bottleneck.


14. Part 11: Tools Comparison and Ecosystem

14.1 The Python Quality Tools Landscape

14.2 Linters: Ruff vs. Flake8 vs. Pylint

Speed

  • Ruff: 10-100x faster (Rust-based)
  • Flake8: Moderate speed (Python)
  • Pylint: Slowest (most thorough)
  • Winner: Ruff - Speed enables frequent checking

Rules

  • Ruff: 800+ rules (comprehensive)
  • Flake8: 200+ rules (extensible with plugins)
  • Pylint: 300+ rules (opinionated)
  • Winner: Ruff - Most comprehensive out of the box

Configuration

  • Ruff: Simple, works with pyproject.toml
  • Flake8: Uses .flake8 or setup.cfg
  • Pylint: Complex .pylintrc
  • Winner: Ruff - Modern configuration

Auto-fix

  • Ruff: Extensive auto-fix support
  • Flake8: Limited (via plugins)
  • Pylint: Minimal auto-fix
  • Winner: Ruff - Best auto-fix capabilities

Recommendation: Use Ruff for new projects. Consider Flake8 only if you need specific plugins not yet available in Ruff.

14.3 Formatters: Ruff Format vs. Black

Speed

  • Ruff Format: 10-100x faster (Rust)
  • Black: Standard speed (Python)
  • Winner: Ruff Format - Significantly faster

Style

  • Ruff Format: Compatible with Black
  • Black: The original opinionated style
  • Winner: Tie - Nearly identical output

Configuration

  • Ruff Format: Part of unified Ruff config
  • Black: Separate configuration
  • Winner: Ruff Format - One tool, one config

Stability

  • Ruff Format: Newer (2023+), rapidly stabilizing
  • Black: Mature (2018+), battle-tested
  • Winner: Black for ultra-conservative projects, Ruff Format for modern development

Recommendation: Use Ruff Format for unified tooling. Stick with Black only if you have specific compatibility requirements.

14.4 Type Checkers: Pyright vs. mypy

Speed

  • Pyright: 5-10x faster (TypeScript/Node.js)
  • mypy: Standard speed (Python)
  • Winner: Pyright - Much faster on large codebases

IDE Integration

  • Pyright: Built into VS Code Pylance
  • mypy: Requires separate extension
  • Winner: Pyright - Seamless VS Code experience

Type Inference

  • Pyright: More intelligent inference
  • mypy: Good inference, less sophisticated
  • Winner: Pyright - Better at inferring complex types

Maturity

  • Pyright: Newer (2019+), Microsoft-backed
  • mypy: Older (2012+), widely adopted
  • Winner: mypy for legacy projects, Pyright for new projects

Recommendation: Use Pyright if you use VS Code. Use mypy if you have existing mypy configuration or need specific plugins.


15. Summary: Key Takeaways

15.1 Working Code ≠ Quality Code

Code that runs is just the beginning. Professional software must be:

15.2 Automate Quality Checks

Don’t rely on humans to catch style issues:

15.3 Follow PEP 8

Python’s style guide is not optional:

15.4 Type Hints Are Your Friend

Type hints catch bugs before they reach production:

15.5 Build Quality In, Don’t Bolt It On

Quality is not a phase—it’s a practice:

15.6 The ROI is Real

Automated quality checks save time and money:


16. Practical Assignment

16.1 Task: Fix the Road Profile Viewer

  1. Clone the repository:
    git clone https://github.com/your-username/road-profile-viewer.git
    cd road-profile-viewer
    
  2. Set up the environment:
    uv sync --dev
    
  3. Run quality checks:
    uv run ruff check .
    uv run ruff format --check .
    uv run pyright
    
  4. Fix all issues:
    • Remove unused imports
    • Fix spacing violations
    • Correct naming conventions
    • Remove dead code
    • Add type hints to all functions
  5. Verify fixes:
    uv run ruff check .  # Should show 0 errors
    uv run ruff format --check .  # Should show all files OK
    uv run pyright  # Should show 0 errors
    
  6. Commit and push:
    git add .
    git commit -m "Fix code quality issues"
    git push origin main
    
  7. Verify GitHub Actions: Check that all CI/CD checks pass on GitHub.

17. Additional Resources

17.1 Official Documentation

17.2 Learning Resources

17.3 Tools and Extensions


19. References

[1] PEP 8 - Style Guide for Python Code. https://www.python.org/dev/peps/pep-0008/

[2] Ruff Official Documentation. https://docs.astral.sh/ruff/

[3] Astral - Modern Python Tooling. https://astral.sh/

[4] Pyright Documentation. https://microsoft.github.io/pyright/

[5] Black - The Uncompromising Code Formatter. https://black.readthedocs.io/

[6] Flake8 Documentation. https://flake8.pycqa.org/

[7] mypy - Optional Static Typing for Python. https://mypy-lang.org/

[8] Python Type Hints - Official Documentation. https://docs.python.org/3/library/typing.html

[9] Real Python - Python Code Quality Tutorial. https://realpython.com/python-code-quality/

[10] Martin, Robert C. “Clean Code: A Handbook of Agile Software Craftsmanship.” Prentice Hall, 2008.

[11] Slatkin, Brett. “Effective Python: 90 Specific Ways to Write Better Python.” Addison-Wesley, 2019.

[12] Anaya, Mariano. “Clean Code in Python: Develop Maintainable and Efficient Code.” Packt Publishing, 2021.

[13] GitHub Actions Documentation. https://docs.github.com/en/actions

[14] pre-commit Framework. https://pre-commit.com/

[15] NumPy Type Hints Documentation. https://numpy.org/doc/stable/reference/typing.html


Note: This lecture is based on Python 3.12+ and tooling available as of October 2025. Tool versions and specific features may evolve over time.

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk