Commenting Code Effectively

Commenting Code Effectively

Clear, thoughtful comments are one of the most powerful tools you have as a programmer. They help others understand your code, and—just as importantly—they help you remember your own intentions when you revisit your work months later. But not all comments are created equal. In this article, I’ll guide you through the art and science of writing comments that genuinely add value.

Why Commenting Matters

Before we dive into the how, let’s briefly talk about the why. Code tells the computer what to do, but comments explain why you’re doing it. They provide context, describe assumptions, warn about pitfalls, and make complex logic approachable. Well-commented code is easier to debug, extend, and maintain.

Consider this snippet:

x = 5  # Set x to 5

This comment is useless—it just restates what the code already says. Now look at this:

# Default retry threshold for connection timeouts (empirically tested)
retry_limit = 5

This comment adds value. It explains why 5 was chosen and gives a hint about its purpose.

Types of Comments

There are several common types of comments you’ll use in your code. Each serves a different purpose.

Inline Comments: These appear on the same line as the code they reference. Use them sparingly to clarify complex lines.

result = base ** exponent  # Using exponentiation for growth calculation

Block Comments: These are longer explanations, usually placed before a block of code. They’re great for describing the purpose of a function, class, or a tricky algorithm.

# This function calculates the Levenshtein distance between two strings.
# It uses dynamic programming for efficiency with large inputs.
def levenshtein_distance(s1, s2):
    # Implementation here...
    pass

Docstrings: These are multi-line strings placed right after a function, class, or module definition. They’re used by tools like Sphinx to generate documentation.

def calculate_interest(principal, rate, time):
    """
    Calculate compound interest.

    Args:
        principal (float): The initial amount of money.
        rate (float): The annual interest rate (as a decimal).
        time (int): The time the money is invested for, in years.

    Returns:
        float: The amount of interest earned.
    """
    return principal * (1 + rate) ** time - principal

TODO Comments: These are notes to yourself or others about future improvements or fixes.

# TODO: Refactor this into a separate function to avoid repetition

Let’s see how often different types of comments are used in typical Python projects:

Comment Type Frequency of Use
Inline Comments Moderate
Block Comments High
Docstrings Very High
TODO Comments Low to Moderate

Best Practices for Writing Comments

When writing comments, aim for clarity, brevity, and relevance. Here are some guidelines to follow:

  • Write comments that explain why, not what. The code already shows what is happening.
  • Keep comments up to date. Outdated comments are worse than no comments at all.
  • Avoid obvious statements. Don’t comment things like x = 5 # assign 5 to x.
  • Use proper grammar and punctuation. This makes your comments easier to read.
  • Comment tricky parts. If something is non-obvious or uses a clever hack, explain it.

Here’s an example of a well-commented function:

def fibonacci(n):
    """
    Generate the nth Fibonacci number using iterative method.

    This is more efficient than recursion for large n due to constant space.

    Args:
        n (int): A non-negative integer.

    Returns:
        int: The nth Fibonacci number.

    Raises:
        ValueError: If n is negative.
    """
    if n < 0:
        raise ValueError("n must be a non-negative integer")
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

Notice how the docstring explains the function’s purpose, its parameters, return value, and even why an iterative approach was chosen.

Common Pitfalls to Avoid

Even with good intentions, it’s easy to write poor comments. Here are some common mistakes:

  • Over-commenting: Commenting every single line makes the code noisy and hard to read.
  • Under-commenting: Assuming everyone will understand your complex logic.
  • Redundant comments: Stating the obvious.
  • Outdated comments: Forgetting to update comments when the code changes.

Bad example:

x = 10  # set x to ten
y = 20  # set y to twenty
z = x + y  # add x and y and put result in z

Good example:

# Initialize coordinates for the starting point
x = 10
y = 20

# Calculate the Euclidean distance from origin (simplified for 2D)
distance = (x**2 + y**2) ** 0.5

The second example provides meaningful context without stating the obvious.

Tools to Help with Commenting

You don’t have to go it alone. There are tools that can help you maintain good commenting habits:

  • Linters: Tools like pylint or flake8 can warn you about missing docstrings or inconsistent commenting.
  • Auto-doc generators: Sphinx can generate beautiful documentation from your docstrings.
  • IDE support: Modern IDEs like PyCharm or VSCode provide templates and shortcuts for docstrings.

For instance, in PyCharm, you can type """ after a function definition and press Enter to generate a docstring template.

When Not to Comment

Sometimes, the best comment is no comment. If your code is self-explanatory, adding a comment might just add clutter. For example:

# Bad
age = 25  # set age to 25

# Good
# Legal driving age in this state
driving_age = 16

Refactor instead of commenting. If you feel the need to write a long comment explaining a block of code, consider whether you can break it into smaller, well-named functions instead.

# Instead of this:
# Check if user is eligible: over 18 and not banned
if user.age > 18 and not user.is_banned:
    # Do something

# Write this:
def is_eligible(user):
    return user.age > 18 and not user.is_banned

if is_eligible(user):
    # Do something

The function name is_eligible makes the comment unnecessary.

Here’s a quick list of when you should avoid writing comments:

  • When the code is simple and self-documenting.
  • When you can refactor to make the code clearer.
  • When the comment would just repeat the code.

Commenting in Teams

When working on a team, consistency is key. Agree on a commenting style and stick to it. This might include:

  • Using a specific format for docstrings (e.g., Google style, NumPy style).
  • Agreeing on how to write TODO comments.
  • Deciding on the level of detail for inline comments.

Many teams adopt a style guide that includes commenting conventions. For example:

# TODO(Alice): Implement error handling here by Q2 2023

This makes it clear who is responsible for the task and by when.

Examples in Practice

Let’s look at a more complete example. Suppose we’re writing a function to sort a list of tuples by the second element.

Without comments:

def sort_tuples(lst):
    return sorted(lst, key=lambda x: x[1])

This is concise, but what if someone doesn’t know what key=lambda x: x[1] means?

With comments:

def sort_tuples(lst):
    """
    Sort a list of tuples based on the second element of each tuple.

    Example:
        Input: [(1, 3), (4, 1), (5, 2)]
        Output: [(4, 1), (5, 2), (1, 3)]

    Args:
        lst (list): List of tuples to be sorted.

    Returns:
        list: Sorted list of tuples.
    """
    # Using sorted() with a key function that selects the second element
    return sorted(lst, key=lambda item: item[1])

Now the purpose, usage, and mechanism are all clear.

Another common scenario: complex logic or workarounds. Suppose you’re using a list comprehension that’s a bit intricate:

# Without comment
filtered = [x for x in data if x % 2 == 0 and x > 10]

# With comment
# Select even numbers greater than 10 for processing
filtered = [x for x in data if x % 2 == 0 and x > 10]

The comment helps readers quickly grasp the filtering criteria.

Advanced Commenting: Annotations and Type Hints

In Python, you can use type hints to make your code more self-documenting. This reduces the need for some comments.

# Without type hints
def get_user_name(user_id):
    # user_id is an int, returns a string or None
    ...

# With type hints
from typing import Optional

def get_user_name(user_id: int) -> Optional[str]:
    ...

The type hint tells us that user_id is an integer and the function returns either a string or None. This is clearer and machine-verifiable.

Summary Table of Commenting Do’s and Don’ts

Do’s Don’ts
Explain why, not what State the obvious
Keep comments current Leave outdated comments
Use clear language Use jargon or unclear terms
Comment complex logic Comment every single line
Write useful docstrings Write redundant docstrings

Final Thoughts

Effective commenting is a skill that improves with practice. Start by reviewing your own code and asking: “If I came back to this in six months, would I understand it?” Use comments to fill in the gaps that the code alone doesn’t cover. And remember: the goal is to make your code as readable and maintainable as possible.

Happy coding, and may your comments always be helpful!