Skip to content

Enforce Naming Conventions

General Best Practices

First of all, Python has several naming conventions outlined in PEP-8, like using snake case for variables and camel case for classes. The Naming Rules of the Google Python Style Guide help you enforce these conventions.

Project-Specific Naming Conventions

Besides Pythonic practices, each codebase has its own naming conventions. These are influenced not just by engineering best practices but also by the domain of your software. And often also by events specific to your company, like a re-branding.

In this recipe, we're showing some examples of how to detect misleading names with Sourcery custom rules.

Mismatch Between the Object's Type and Name

This usually occurs as a reminiscence of a significant business or technology change. Imagine the following scenario:

  • Your application started out as a bookstore.
  • Then with time it got popular and started to sell other products as well.

Now, probably you have a Product class and a Book class inheriting from it.

class Book(Product):
  • Some of your old code is still relevant only for Books, like the tag_authors function.
  • Other parts of the code are relevant for all kind of Products, like the stock_forecast function.
  • And in some cases, it's messed up: an object's type has been changed to Product, but it's still called book.

To spot these cases, you can use some Sourcery custom rules. The following 2 rules detect variables and function arguments with the name book and type Product.

  - id: book-vs-product-var
    pattern: |
      ${var}: ${type}
    condition: var.contains("book") and type.contains("Product")
    description: Don't name a `Product` object "book"
    tests:
      - match: |
          book: Product
      - match: |
          books: List[Product]
      - no-match: |
          book: OtherType

  - id: book-vs-product-arg
    pattern: |
      def ...(...,${arg_name}: ${type?} = ${default_value?},...):
        ...
    condition: arg_name.contains("book") and type.contains("Product")
    description: Don't name a `Product` object "book"

Too Many Synonyms

Synonyms make human languages more vivid. In code, however, they only cause confusion. Why do we have objects called holiday and vacation? Do they refer to the same concept? 🤔 If not what's the difference and where is it documented?

It makes sense to pick one term and stick to it. And to use a Sourcery custom rule to find all occurrences of the other term:

  - id: no-holiday-names
    pattern: |
      def ${func_name}(...,${arg_name}: ${type?} = ${default_value?},...):
        ...
    condition: func_name.contains("holiday") or arg_name.contains("holiday")
    description: Use the term "vacation" instead of "holiday"
Domain-Driven Design

In Domain-Driven Design, the very first recommendation after the introductory chapter is to create a "ubiquitous language". Communication gets much smoother if you ensure that the same terminology is used throughout the project. This includes code, documentation, but also emails and meetings.

Consistent Interface

Similarly, it makes sense to stick to one name for technical concepts. Let's say your API has multiple modules providing CRUD functionality. But while some modules have an update function, the respective function is called edit or change in other modules. Let's create a rule that detects these inconsistencies:

  - id: use-update
    pattern: |
      def ${func_name}(...):
        ...
    condition: func_name.contains("edit") or func_name.contains("change")
    description: Use the verb "update" instead of "edit" or "change"
    paths:
      include:
        - api

Note the usage of the paths.include property in the rule above.

While some naming conventions are valid in your whole codebase, some might be relevant only for a part of it. Also while consistent naming is crucial for your public interface, it might be just a nice-to-have for helper modules or local variables.