| name | three-word-naming |
| description | Use when naming or renaming a Python function, method, or class — especially when a name is heading past three words (ensure_model_file_present, load_and_validate_config), contains and/or/with/if_missing, or describes several steps at once. |
Three-Word Naming
Overview
Functions, methods, and classes get at most three words. A name that needs a fourth word is not a naming problem — it is a scope problem: the code is doing more than one thing, or it is missing the namespace that should carry part of the name. Fix the scope, and the short name falls out.
Never fix a too-long name by abbreviating, dropping vowels, or fusing words. Shorten the responsibility, not the spelling.
Counting
- Split snake_case on
_, CamelCase on capitals: read_frame_bgr = 3, LoadedFaceModel = 3, ensure_model_file_present = 4 ❌.
- Every token counts — suffixes like
_bgr, _px, prepositions like _to_, _from_. mask_to_polygon_px = 4 ❌.
- One leading
_ (private) and dunders don't count. An acronym or digit group is one word (sha256 = 1).
- Scope: functions, methods, classes. Exempt: test functions, module-level constants (
DEFAULT_MASK_DILATE_PX is fine), local variables.
Over three words? Two remedies
1. Raise the abstraction — split and compose. A 4+ word name is usually a step list. Each step gets its own ≤3-word function; the composition point keeps a short name describing the outcome, not the steps.
2. Move a word into a namespace. If the extra words are a noun phrase repeated across the module, they are the module's (or class's) name, not each function's. Callers read model_file.ensure(...) — the context words are written once.
def ensure_model_file_present(model_path, fallback_dir): ...
def verify_model_file_digest(model_path, expected): ...
def ensure(model_path, fallback_dir): ...
def verify_digest(model_path, expected): ...
def copy_from_fallback(model_path, fallback_dir): ...
def ensure_model(model_path, fallback_dir): ...
The same move works with a class as the namespace: ModelFile.ensure(), EmbeddingCache.refresh().
Name smells that predict a 4th word
| Smell in the name | What it reveals | Fix |
|---|
_and_ (load_and_validate_config) | two responsibilities | split: load_config + validate_config, compose at the caller |
_with_ / _by_ tail (filter_faces_by_score_and_size) | parameters leaking into the name | the criteria are arguments: filter_faces(min_score=, min_px=) |
_if_missing / _if_needed | a guard clause fused into the verb | ensure_ prefix already means conditionally: ensure_model |
repeated noun phrase across functions (model_file_*) | a missing module/class | namespace it; each function keeps the verb |
step-list verb chain (download_verify_load) | orchestration named by its steps | name the outcome (load_model); steps become their own functions |
Common mistakes
| Mistake | Fix |
|---|
Abbreviating to sneak under the limit (ens_mdl_file_present) | Still 4 responsibilities. Split or namespace — never compress spelling. |
Vague 1-worder to dodge the limit (process, handle, do_it) | Under-specific is as bad as over-long. Three precise words beat one vague one. |
Counting _bgr/_px as "free" suffixes | They count. mask_to_polygon_px → put the unit in the type or docstring, or namespace the conversion. |
Renaming without re-scoping (ensure_model_file_present → ensure_file) | Now the name lies. First split the responsibilities, then name what's left. |
| Applying the limit to tests/constants | Test names narrate behavior; constants encode ranges. Both are exempt. |
Red Flags — STOP
- The name you just typed has a 4th
_ segment → stop typing, split or namespace.
- You wrote
_and_, _with_, _if_ inside a function name → the scope is too broad.
- You're about to abbreviate a word to fit → wrong axis; shrink the responsibility.
- Two or more functions share a 2-word noun prefix → extract the module/class namespace.