RizTech Academy logo
RizTech Academy
Best Practices: Code Others Can ReadLesson 1 of 535 min

Naming things, which is most of the job

You will spend far more time reading code than writing it — your own included, six months later, with no memory of writing it. A name is the interface between what the code does and what the next person believes it does, and when those drift apart, that is where bugs live.

Python makes this more important than most languages, because there are no type declarations to fall back on. In Java, List<Delivery> x tells you something even with a terrible name. In Python, x tells you nothing at all.

PEP 8, which is not optional

Thing Convention Example
Variable, function, method snake_case total_paise, load_expenses
Class CapWords ExpenseTracker
Constant UPPER_SNAKE_CASE MAX_ENTRIES
Module, package short lowercase storage.py, reports
"Internal" one leading underscore _cache, _normalise
Name-mangled two leading underscores __slots — rarely what you want
Avoiding a keyword one trailing underscore class_, id_

These are not matters of taste. Every Python codebase you join follows them, Ruff enforces them, and departing marks the code as written by somebody who did not know. Module 10 covered running Ruff; this is what it is checking against.

Two specifics worth calling out:

Never use l, O or I as single-character names. In most fonts l is indistinguishable from 1 and O from 0. PEP 8 says this explicitly, and it is the only naming rule that exists purely because of typography.

One leading underscore is a convention, not protection. _cache means "this is internal, do not touch". Nothing stops you. Python trusts you and expects you to read.

Say what, not how

# how
def filter_list_by_date_using_loop(items, month):
    ...

# what
def expenses_in(month):
    ...

The second survives changing the implementation. The first is a comment about the current body, written where a comment cannot be ignored, and it becomes a lie the moment somebody rewrites it as a comprehension.

Length should match scope

for e in expenses:
    total += e.amount_paise

e is fine — it is born and dies within two lines. But a name that lives longer has to carry more:

self.expenses_by_category = {}     # good
self.d = {}                        # not

The further a name travels, the more it has to carry. A comprehension variable, one letter. A module-level constant, a full phrase.

Put the unit in the name

The single highest-value naming habit in this course:

amount_paise = 28_500
timeout_seconds = 30
file_size_bytes = 1_048_576

amount alone raises a question the reader must answer from somewhere else — rupees or paise? amount_paise cannot be misread, and the day somebody writes amount_paise = 285 meaning ₹285, the name is standing right there arguing with them.

Python's 1_000_000 underscores help too. 28_500 is obviously twenty-eight and a half thousand; 28500 needs counting.

Booleans read as questions

if expense.is_recurring:
    ...
if tracker.has_entries():
    ...
if user.can_edit(entry):
    ...

is, has, can, should. The test is whether the if reads as English.

And avoid negatives — if not entry.is_not_valid is two negatives and one confused reader.

Functions that return a boolean, and functions that do a thing

def validate(entry):      # does it return something? raise? both?
    ...

def is_valid(entry):      # returns a bool
    ...

def validate_or_raise(entry):   # raises
    ...

Python has no return-type declaration in the signature unless you add a hint, so the name is carrying that information. This matters more here than in a typed language.

Say no to the noise words

expense_data          # as opposed to an expense that is not data?
expense_info
expense_object
process_expense       # "process" means nothing
handle_expense
do_save()
manage_expenses()

manager, helper, util, data, info, process, handle. If removing the word does not change the meaning, remove it.

The exception is a real convention: ExpenseStore says where expenses live. ExpenseHelper says nothing.

A function you cannot name is usually a function doing more than one thing. That is a design problem surfacing as a naming problem, and it is worth listening to.

Be consistent about one idea

Pick one word per concept:

# Pick one:
get_expense / fetch_expense / load_expense / read_expense

Two words for the same thing makes a reader search for a distinction that is not there. Two words for genuinely different things is right — the capstone uses entry for one row and expense for the concept, and those differ.

Python's own traps

Do not shadow builtins.

list = [1, 2, 3]        # now list() is gone for the rest of this scope
dict = {}
type = "recurring"
id = 5
input = "..."

This is the most common Python naming bug, and the error it produces later — TypeError: 'list' object is not callable — points at the line that uses list(), nowhere near the line that broke it. Use items, mapping, kind, identifier, text.

_ means deliberately unused.

for _ in range(3):
    print("hello")

name, _, amount = line.split(",")

It tells the reader "I know there is a value here and I do not want it".

Module names are short and lowercase. expense_store.py, not ExpenseStore.py or expensestore.py. A capitalised module name is a common giveaway of somebody arriving from Java.

Names as a design signal

Naming difficulty is information:

  • "I cannot name this function" — it probably does two things.
  • "I need and in the name" — save_and_notify is two functions.
  • "I keep writing manager" — the responsibility is not clear yet.
  • "The name is very long" — either it does too much, or it belongs on a different class where the context is implied.
# hard to name
def process_expense_and_update_totals_and_save(entry): ...

# easy to name
def add(entry): ...
def recalculate_totals(): ...
def save(): ...

Check your work

Why names matter more in Python: there are no type declarations to fall back on, so the name carries information a signature would carry elsewhere.

Why PEP 8 is not taste: every codebase and every tool assumes it.

Why not l, O, I: in most fonts they are indistinguishable from 1 and 0.

What one leading underscore means: a convention saying "internal", not protection.

Why name what, not how: a name describing the implementation becomes a lie when the implementation changes.

The highest-value habit: put the unit in the name — amount_paise, timeout_seconds.

How booleans should read: as a question, with is, has, can, should.

Why shadowing builtins is the classic bug: the error appears at the line that uses list(), far from the line that broke it.

What _ communicates: a value deliberately ignored.

What naming difficulty tells you: a function you cannot name does two things.

Practice

  1. Open your capstone and find the three worst-named things. Rename them.
  2. Find every variable holding money. Confirm each has _paise in its name.
  3. Write list = [1, 2, 3] then call list("abc") and read the error. Note how far the message is from the cause.
  4. Find a function whose name starts with process, handle or do. Rename it to what it actually does.
  5. Find a boolean-returning function not starting with is, has, can or should. Read a call site aloud.
  6. Search your code for data, info, manager, helper, util. Try deleting the word from each.
  7. Find two words used for the same concept. Pick one.
  8. Run Ruff on your capstone and look only at the naming complaints.
  9. Find a function with and in its name and split it.
  10. Ask somebody to guess what one of your functions does from its name alone.

Next: how big is too big.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship