Finding Reference
This page is generated by docs/generate.py from src/noze/glossary/docs.rs.
API Clarity and Constants
Boolean Blindness (boolean_blindness)
What it is
Bare booleans whose meaning is invisible at the call site (f(True, False)) — use an enum or keyword args so calls read clearly.
Why it's bad
Call sites stop reading like code and start reading like truth tables.
Example
Problem
def publish_invoice(
invoice_id: str,
email_customer: bool,
archive_pdf: bool,
) -> None:
invoice = repo.load(invoice_id)
if email_customer:
mailer.send(invoice)
else:
review_queue.add(invoice)
if archive_pdf:
archive.store(render_pdf(invoice))
Proposed fix
Promote each boolean decision into a named strategy so callers choose behavior explicitly.
from typing import Protocol
class InvoiceDelivery(Protocol):
def deliver(self, invoice: Invoice) -> None: ...
class InvoiceArchival(Protocol):
def archive(self, invoice: Invoice) -> None: ...
class EmailDelivery:
def deliver(self, invoice: Invoice) -> None:
mailer.send(invoice)
class ReviewQueueDelivery:
def deliver(self, invoice: Invoice) -> None:
review_queue.add(invoice)
class PdfArchival:
def archive(self, invoice: Invoice) -> None:
archive.store(render_pdf(invoice))
class SkipArchival:
def archive(self, invoice: Invoice) -> None:
return None
def publish_invoice(
invoice_id: str,
delivery: InvoiceDelivery,
archival: InvoiceArchival,
) -> None:
invoice = repo.load(invoice_id)
delivery.deliver(invoice)
archival.archive(invoice)
Problem
function publishInvoice(invoiceId: string, emailCustomer: boolean, archivePdf: boolean): void {
const invoice = repo.load(invoiceId);
if (emailCustomer) {
mailer.send(invoice);
} else {
reviewQueue.add(invoice);
}
if (archivePdf) {
archive.store(renderPdf(invoice));
}
}
Proposed fix
Promote each boolean decision into a named strategy so callers choose behavior explicitly.
interface InvoiceDelivery {
deliver(invoice: Invoice): void;
}
interface InvoiceArchival {
archive(invoice: Invoice): void;
}
class EmailDelivery implements InvoiceDelivery {
deliver(invoice: Invoice): void {
mailer.send(invoice);
}
}
class ReviewQueueDelivery implements InvoiceDelivery {
deliver(invoice: Invoice): void {
reviewQueue.add(invoice);
}
}
class PdfArchival implements InvoiceArchival {
archive(invoice: Invoice): void {
archive.store(renderPdf(invoice));
}
}
class SkipArchival implements InvoiceArchival {
archive(invoice: Invoice): void {
return;
}
}
function publishInvoice(
invoiceId: string,
delivery: InvoiceDelivery,
archival: InvoiceArchival,
): void {
const invoice = repo.load(invoiceId);
delivery.deliver(invoice);
archival.archive(invoice);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.boolean_blindness]
enabled = true
action = "warning"
max_bool_params = 2 # allowed boolean parameters before flagging
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Literal Membership (literal_membership)
What it is
Branching on membership in a literal string list (x in ['a','b']) — stringly-typed categories; use an Enum.
Why it's bad
Hard-coded string sets become a hidden enum that tools cannot help with.
Example
Problem
def is_allowed(status: str) -> bool:
return status in ["draft", "paid", "void"]
Proposed fix
Use an Enum or named constant set with a typed boundary.
from enum import Enum
class InvoiceStatus(Enum):
DRAFT = "draft"
PAID = "paid"
VOID = "void"
def is_allowed(status: InvoiceStatus) -> bool:
return status in {
InvoiceStatus.DRAFT,
InvoiceStatus.PAID,
InvoiceStatus.VOID,
}
Problem
function isAllowed(status: string): boolean {
return ["draft", "paid", "void"].includes(status);
}
Proposed fix
Use a union type, enum, or const object with a derived type.
enum InvoiceStatus {
Draft = "draft",
Paid = "paid",
Void = "void",
}
function isAllowed(status: InvoiceStatus): boolean {
return status === InvoiceStatus.Draft ||
status === InvoiceStatus.Paid ||
status === InvoiceStatus.Void;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.literal_membership]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Magic String Default (magic_string_default)
What it is
A fallback empty or one-character string is standing in for an optional/nullable value (or "", || "?") — the contract is hiding in a sentinel; prefer a nullable/optional string or a dedicated sum type.
Why it's bad
A sentinel string hides the real optionality of the value.
Example
Problem
def display_name(name: str | None) -> str:
return name or ""
Proposed fix
Reject a missing required string explicitly instead of hiding it behind a sentinel fallback.
def display_name(name: str | None) -> str:
if name is None:
raise ValueError("display name is required")
return name
Problem
function displayName(name?: string): string {
return name || "?";
}
Proposed fix
Throw on a missing required string, or model optionality explicitly when absence is valid.
function displayName(name?: string): string {
if (name === undefined) {
throw new Error("display name is required");
}
return name;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.magic_string_default]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Magic Numbers (magic_numbers)
What it is
Unexplained numeric literals in logic — name them as constants so their intent is clear.
Why it's bad
Numbers without names are hard to audit and easy to copy blindly.
Example
Problem
def is_retryable(attempts: int) -> bool:
return attempts < 7
Proposed fix
Extract a named constant near the policy it represents.
MAX_RETRIES = 7
def is_retryable(attempts: int) -> bool:
return attempts < MAX_RETRIES
Problem
function isRetryable(attempts: number): boolean {
return attempts < 7;
}
Proposed fix
Use a named const or configuration value for the policy number.
const MAX_RETRIES = 7;
function isRetryable(attempts: number): boolean {
return attempts < MAX_RETRIES;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.magic_numbers]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
External linter coverage
- Ruff:
PLR2004 - ESLint:
no-magic-numbers
Complexity and Control Flow
Deep Nesting (deep_nesting)
What it is
Control flow nests many levels deep, hard to follow — flatten with early returns or extracted helpers.
Why it's bad
The control flow becomes hard to scan and easy to misread in review.
Example
Problem
def approve(order: Order) -> bool:
if order.is_paid:
if not order.is_flagged:
if order.customer.is_active:
return order.total < order.customer.limit
return False
Proposed fix
Use guard clauses, continue early, or extract a helper for the nested branch.
def approve(order: Order) -> bool:
if not order.is_paid:
return False
if order.is_flagged:
return False
if not order.customer.is_active:
return False
return order.total < order.customer.limit
Problem
function approve(order: Order): boolean {
if (order.isPaid) {
if (!order.isFlagged) {
if (order.customer.isActive) {
return order.total < order.customer.limit;
}
}
}
return false;
}
Proposed fix
Flatten with early returns/continues or pull nested checks into named helpers.
function approve(order: Order): boolean {
if (!order.isPaid) {
return false;
}
if (order.isFlagged) {
return false;
}
if (!order.customer.isActive) {
return false;
}
return order.total < order.customer.limit;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.deep_nesting]
enabled = true
action = "warning"
max_nesting = 4 # allowed nested block depth
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | No |
| Rust | Yes |
External linter coverage
- Ruff:
PLR1702 - ESLint:
max-depth
High Cognitive Complexity (high_cognitive_complexity)
What it is
Hard for a human to follow — nested branches and loops weighted by depth; simplify or break it up.
Why it's bad
The reader has to simulate too many branches and nesting levels at once.
Example
Problem
def score(order: Order) -> int:
total = 0
if order.paid:
if order.customer.active:
if not order.customer.suspended:
if order.total >= 500:
total += 25
elif order.total >= 100:
total += 10
else:
total += 1
return total
Proposed fix
Name the decision steps, flatten branches, and extract cohesive helpers.
NO_SCORE = 0
ENTERPRISE_ORDER_THRESHOLD = 500
LARGE_ORDER_THRESHOLD = 100
ENTERPRISE_ORDER_SCORE = 25
LARGE_ORDER_SCORE = 10
STANDARD_ORDER_SCORE = 1
def score(order: Order) -> int:
if not can_score(order):
return NO_SCORE
return score_total(order.total)
def can_score(order: Order) -> bool:
if not order.paid:
return False
if not order.customer.active:
return False
if order.customer.suspended:
return False
return True
def score_total(total: int) -> int:
if total >= ENTERPRISE_ORDER_THRESHOLD:
return ENTERPRISE_ORDER_SCORE
if total >= LARGE_ORDER_THRESHOLD:
return LARGE_ORDER_SCORE
return STANDARD_ORDER_SCORE
Problem
function score(order: Order): number {
let total = 0;
if (order.paid) {
if (order.customer.active) {
if (!order.customer.suspended) {
if (order.total >= 500) {
total += 25;
} else if (order.total >= 100) {
total += 10;
} else {
total += 1;
}
}
}
}
return total;
}
Proposed fix
Prefer guard clauses and small predicate functions over nested branches.
const NO_SCORE = 0;
const ENTERPRISE_ORDER_THRESHOLD = 500;
const LARGE_ORDER_THRESHOLD = 100;
const ENTERPRISE_ORDER_SCORE = 25;
const LARGE_ORDER_SCORE = 10;
const STANDARD_ORDER_SCORE = 1;
function score(order: Order): number {
if (!canScore(order)) {
return NO_SCORE;
}
return scoreTotal(order.total);
}
function canScore(order: Order): boolean {
if (!order.paid) {
return false;
}
if (!order.customer.active) {
return false;
}
if (order.customer.suspended) {
return false;
}
return true;
}
function scoreTotal(total: number): number {
if (total >= ENTERPRISE_ORDER_THRESHOLD) {
return ENTERPRISE_ORDER_SCORE;
}
if (total >= LARGE_ORDER_THRESHOLD) {
return LARGE_ORDER_SCORE;
}
return STANDARD_ORDER_SCORE;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.high_cognitive_complexity]
enabled = true
action = "warning"
max_cognitive = 15 # allowed cognitive complexity score
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | No |
| Rust | Yes |
External linter coverage
- ESLint:
sonarjs/cognitive-complexity
High Cyclomatic Complexity (high_complexity)
What it is
Many independent paths through the function, so it's hard to test fully — decompose it.
Why it's bad
The function accumulates too many distinct paths to reason about confidently.
Example
Problem
def choose_refund(order: Order) -> RefundDecision:
if order.cancelled:
return deny_refund(order, "cancelled")
if order.fraud_review:
return manual_review(order)
if order.days_since_purchase > 30:
return deny_refund(order, "expired")
if order.damaged:
return full_refund(order)
if order.missing_items:
return partial_refund(order)
if order.vip_customer:
return goodwill_credit(order)
return deny_refund(order, "not_eligible")
Proposed fix
Use a dispatch table or split the branches into named operations.
from collections.abc import Callable
RefundHandler = Callable[[Order], RefundDecision]
REFUND_POLICY: dict[str, RefundHandler] = {
"cancelled": deny_cancelled,
"fraud_review": manual_review,
"expired": deny_expired,
"damaged": full_refund,
"missing_items": partial_refund,
"vip_customer": goodwill_credit,
}
def choose_refund(order: Order) -> RefundDecision:
handler = REFUND_POLICY.get(order.refund_reason(), deny_not_eligible)
return handler(order)
Problem
function chooseRefund(order: Order): RefundDecision {
if (order.cancelled) {
return denyRefund(order, "cancelled");
}
if (order.fraudReview) {
return manualReview(order);
}
if (order.daysSincePurchase > 30) {
return denyRefund(order, "expired");
}
if (order.damaged) {
return fullRefund(order);
}
if (order.missingItems) {
return partialRefund(order);
}
if (order.vipCustomer) {
return goodwillCredit(order);
}
return denyRefund(order, "not_eligible");
}
Proposed fix
Use a map, strategy object, or smaller functions for independent paths.
type RefundHandler = (order: Order) => RefundDecision;
const refundPolicy: Record<string, RefundHandler> = {
cancelled: denyCancelled,
fraud_review: manualReview,
expired: denyExpired,
damaged: fullRefund,
missing_items: partialRefund,
vip_customer: goodwillCredit,
};
function chooseRefund(order: Order): RefundDecision {
const handler = refundPolicy[order.refundReason()] ?? denyNotEligible;
return handler(order);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.high_complexity]
enabled = true
action = "warning"
max_cyclomatic = 10 # allowed independent control-flow paths
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
External linter coverage
- Ruff:
PLR0912 - ESLint:
complexity
Too Many Returns (too_many_returns)
What it is
Many exit points make the function's flow hard to follow — consolidate, or it's doing too much.
Why it's bad
Many exit points make the function harder to follow and test.
Example
Problem
def status_for(order: Order) -> str:
if order.cancelled:
return "cancelled"
if order.refunded:
return "refunded"
if order.failed:
return "failed"
if order.paid:
return "paid"
return "open"
Proposed fix
Group related guards or extract decision helpers.
def status_for(order: Order) -> str:
rules = (
(order.cancelled, "cancelled"),
(order.refunded, "refunded"),
(order.failed, "failed"),
(order.paid, "paid"),
)
for applies, status in rules:
if applies:
return status
return "open"
Problem
function statusFor(order: Order): string {
if (order.cancelled) {
return "cancelled";
}
if (order.refunded) {
return "refunded";
}
if (order.failed) {
return "failed";
}
if (order.paid) {
return "paid";
}
return "open";
}
Proposed fix
Keep meaningful guard clauses, then extract noisy branches into predicates.
const ORDER_STATUS_RULES: Array<[keyof Order, string]> = [
["cancelled", "cancelled"],
["refunded", "refunded"],
["failed", "failed"],
["paid", "paid"],
];
function statusFor(order: Order): string {
for (const [field, status] of ORDER_STATUS_RULES) {
if (order[field]) {
return status;
}
}
return "open";
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.too_many_returns]
enabled = true
action = "warning"
max_returns = 4 # allowed return statements before flagging
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
External linter coverage
- Ruff:
PLR0911
Nested Ternary (nested_ternary)
What it is
A conditional expression contains another conditional expression, forcing readers to match several branches mentally — extract the result into a named function or flatten it into if statements with early returns.
Why it's bad
Nested conditional expressions force readers to mentally match several conditions with their results, making agent-generated logic difficult to review and change.
Example
Problem
def delivery_status(order: Order) -> DeliveryStatus:
return (
DeliveryStatus.READY
if order.is_paid
else DeliveryStatus.RETRY
if order.payment_retry_allowed
else DeliveryStatus.BLOCKED
)
Proposed fix
Extract the decision into a named helper, or replace the expression with ordered if statements and early returns.
def delivery_status(order) -> DeliveryStatus:
if order.is_paid:
return DeliveryStatus.READY
if order.payment_retry_allowed:
return DeliveryStatus.RETRY
return DeliveryStatus.BLOCKED
Problem
export function deliveryStatus(order: Order): DeliveryStatus {
return order.isPaid
? DeliveryStatus.Ready
: order.paymentRetryAllowed
? DeliveryStatus.Retry
: DeliveryStatus.Blocked;
}
Proposed fix
Move result selection into a named function and use explicit if branches with early returns.
function deliveryStatus(order: Order): DeliveryStatus {
if (order.isPaid) {
return DeliveryStatus.Ready;
}
if (order.paymentRetryAllowed) {
return DeliveryStatus.Retry;
}
return DeliveryStatus.Blocked;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.nested_ternary]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
External linter coverage
- ESLint:
no-nested-ternary
Unnecessary Nested If (unnecessary_nested_if)
What it is
An if whose only body is another if, with no else path — combine the conditions with and/&& to flatten the control flow.
Why it's bad
The code says 'if' twice when the condition really belongs on one line.
Example
Problem
def can_ship(order: Order) -> bool:
if order.paid:
if order.address.is_valid:
return True
return False
Proposed fix
Combine the conditions or use a guard clause.
def can_ship(order: Order) -> bool:
if not order.paid:
return False
return order.address.is_valid
Problem
function canShip(order: Order): boolean {
if (order.paid) {
if (order.address.isValid) {
return true;
}
}
return false;
}
Proposed fix
Collapse nested conditions with && or extract a predicate.
function canShip(order: Order): boolean {
if (!order.paid) {
return false;
}
return order.address.isValid;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.unnecessary_nested_if]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
External linter coverage
- ESLint:
sonarjs/no-collapsible-if
Redundant Validation (redundant_validation)
What it is
The same condition is checked repeatedly in one function — establish the invariant once and simplify the later path.
Why it's bad
Repeated guards obscure which invariants have already been established.
Example
Problem
def publish(message):
if message is None:
return
prepare(message)
if message is None:
return
send(message)
Proposed fix
Validate at the boundary or first use, then rely on the established invariant.
def publish(message):
if message is None:
return
prepare(message)
send(message)
Problem
function publish(message: Message | undefined) {
if (message === undefined) return;
prepare(message);
if (message === undefined) return;
send(message);
}
Proposed fix
Validate at the boundary or first use, then rely on the established invariant.
function publish(message: Message | undefined) {
if (message === undefined) return;
prepare(message);
send(message);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.redundant_validation]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
Size and Responsibility
God Module (god_module)
What it is
A module that too much of the codebase depends on (high centrality) — a coupling and change-risk hotspot; split its responsibilities.
Why it's bad
A single module becomes a hot spot with too many reasons to change.
Example
Problem
from god_module.dep_a import run as load_user
from god_module.dep_b import run as charge_user
from god_module.dep_c import run as send_receipt
from god_module.dep_d import run as archive_receipt
def process_checkout() -> int:
return load_user() + charge_user() + send_receipt() + archive_receipt()
Proposed fix
Split by dependency direction and cohesive responsibility.
def load_user(user_id: str) -> User:
return users.find(user_id)
def charge_user(user: User) -> Receipt:
return billing.charge(user)
def send_receipt(user: User, receipt: Receipt) -> None:
mailer.send(user.email, receipt)
def archive_receipt(receipt: Receipt) -> None:
archive.save(receipt)
Problem
import { run as loadUser } from "./dep_a";
import { run as chargeUser } from "./dep_b";
import { run as sendReceipt } from "./dep_c";
import { run as archiveReceipt } from "./dep_d";
export function processCheckout(): number {
return loadUser() + chargeUser() + sendReceipt() + archiveReceipt();
}
Proposed fix
Extract smaller modules and route callers through a narrow public API.
function loadUser(userId: string): User {
return users.find(userId);
}
function chargeUser(user: User): Receipt {
return billing.charge(user);
}
function sendReceipt(user: User, receipt: Receipt): void {
mailer.send(user.email, receipt);
}
function archiveReceipt(receipt: Receipt): void {
archive.save(receipt);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.god_module]
enabled = true
action = "warning"
min_fan = 25 # combined incoming/outgoing fan before god-module risk
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Heavy Nested Function (heavy_nested_function)
What it is
An inner/nested function that grew large and logic-heavy — promote it to a top-level, testable function.
Why it's bad
Nested helpers hide important behavior and make tests awkward.
Example
Problem
def import_customers(rows: list[dict[str, str]]) -> list[Customer]:
def normalize(row: dict[str, str]) -> Customer:
email = row["email"].strip().lower()
status = row["status"].strip()
plan = row["plan"].strip()
region = row["region"].strip()
return Customer(email, status, plan, region)
return [normalize(row) for row in rows]
Proposed fix
Promote the helper to a top-level private function with direct tests.
def normalize_customer(row: Row) -> Customer:
email = row["email"].strip().lower()
status = row["status"].strip()
plan = row["plan"].strip()
region = row["region"].strip()
return Customer(email, status, plan, region)
def import_customers(rows: Rows) -> Customers:
return [normalize_customer(row) for row in rows]
Problem
function importCustomers(rows: Row[]): Customer[] {
function normalize(row: Row): Customer {
const email = row.email.trim().toLowerCase();
const status = row.status.trim();
const plan = row.plan.trim();
const region = row.region.trim();
return new Customer(email, status, plan, region);
}
return rows.map(normalize);
}
Proposed fix
Move the nested function to module scope or a small collaborator.
function normalizeCustomer(row: Row): Customer {
const email = row.email.trim().toLowerCase();
const status = row.status.trim();
const plan = row.plan.trim();
const region = row.region.trim();
return new Customer(email, status, plan, region);
}
function importCustomers(rows: Row[]): Customer[] {
return rows.map(normalizeCustomer);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.heavy_nested_function]
enabled = true
action = "warning"
max_lines = 15 # allowed function or nested-function length
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Large Class (large_class)
What it is
A class with too many methods/responsibilities — split it into focused classes. Related code-smell reference: Refactoring.Guru: Large Class.
Why it's bad
The class stops having a clear job and becomes a grab bag.
Example
Problem
class ReportManager:
def build(self) -> Report:
return build_report()
def send(self) -> None:
mailer.send()
def archive(self) -> None:
archive.save()
def bill(self) -> Receipt:
return billing.charge()
Proposed fix
Extract cohesive collaborators around loading, rendering, delivery, etc.
class ReportBuilder:
def build(self) -> Report:
return build_report()
class ReportMailer:
def send(self, report: Report) -> None:
mailer.send(report)
class ReportArchive:
def save(self, report: Report) -> None:
archive.save(report)
class ReportBilling:
def charge(self, report: Report) -> Receipt:
return billing.charge(report)
Problem
class ReportManager {
build(): Report {
return buildReport();
}
send(): void {
mailer.send();
}
archive(): void {
archive.save();
}
bill(): Receipt {
return billing.charge();
}
}
Proposed fix
Split the class by capability and keep a thin orchestration surface.
class ReportBuilder {
build(): Report {
return buildReport();
}
}
class ReportMailer {
send(report: Report): void {
mailer.send(report);
}
}
class ReportArchive {
save(report: Report): void {
archive.save(report);
}
}
class ReportBilling {
charge(report: Report): Receipt {
return billing.charge(report);
}
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.large_class]
enabled = true
action = "warning"
max_methods = 20 # allowed methods before a class is large
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Long Function (long_function)
What it is
Too many lines to grasp at once — extract cohesive pieces. Related code-smell reference: Refactoring.Guru: Long Method.
Why it's bad
The function becomes a scroll instead of a unit you can hold in your head.
Example
Problem
def import_orders(rows: Rows) -> Orders:
parsed = [parse_order(row) for row in rows]
valid = [order for order in parsed if order.is_valid]
enriched = [enrich(order) for order in valid]
totals = [calculate_total(order) for order in enriched]
discounts = [calculate_discount(order) for order in enriched]
taxes = [calculate_tax(order) for order in enriched]
save_orders(enriched)
notify_import_complete(enriched)
archive_totals(totals)
archive_discounts(discounts)
archive_taxes(taxes)
return enriched
Proposed fix
Extract named chunks that each complete one step of the workflow.
def import_orders(rows: Rows) -> Orders:
orders = _valid_orders(rows)
enriched = [enrich(order) for order in orders]
_persist_import(enriched)
return enriched
def _valid_orders(rows: Rows) -> Orders:
parsed = [parse_order(row) for row in rows]
return [order for order in parsed if order.is_valid]
def _persist_import(orders: Orders) -> None:
save_orders(orders)
notify_import_complete(orders)
archive_totals([calculate_total(order) for order in orders])
Problem
function importOrders(rows: Rows): Orders {
const parsed = rows.map(parseOrder);
const valid = parsed.filter((order) => order.isValid);
const enriched = valid.map(enrich);
const totals = enriched.map(calculateTotal);
const discounts = enriched.map(calculateDiscount);
const taxes = enriched.map(calculateTax);
saveOrders(enriched);
notifyImportComplete(enriched);
archiveTotals(totals);
archiveDiscounts(discounts);
archiveTaxes(taxes);
return enriched;
}
Proposed fix
Extract cohesive helper functions and keep the orchestration readable.
function importOrders(rows: Rows): Orders {
const orders = validOrders(rows);
const enriched = orders.map(enrich);
persistImport(enriched);
return enriched;
}
function validOrders(rows: Rows): Orders {
const parsed = rows.map(parseOrder);
return parsed.filter((order) => order.isValid);
}
function persistImport(orders: Orders): void {
saveOrders(orders);
notifyImportComplete(orders);
archiveTotals(orders.map(calculateTotal));
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.long_function]
enabled = true
action = "warning"
max_lines = 50 # allowed function or nested-function length
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
External linter coverage
- Ruff:
PLR0915 - ESLint:
max-lines-per-function
Long Parameter List (long_parameter_list)
What it is
Too many parameters — group related ones into an object, or the function is doing too much. Related code-smell reference: Refactoring.Guru: Long Parameter List.
Why it's bad
The call contract becomes noisy and easy to pass in the wrong order.
Example
Problem
def search(term: str, page: int, per_page: int, sort: str) -> SearchResults:
return index.find(term, page, per_page, sort)
Proposed fix
Group related parameters into a dataclass or keyword-only options object.
from dataclasses import dataclass
@dataclass(frozen=True)
class SearchQuery:
term: str
page: int
per_page: int
sort: str
def search(query: SearchQuery) -> SearchResults:
return index.find(query)
Problem
function search(term: string, page: number, perPage: number, sort: string): SearchResults {
return index.find(term, page, perPage, sort);
}
Proposed fix
Use an options object or domain type instead of positional arguments.
interface SearchQuery {
term: string;
page: number;
perPage: number;
sort: string;
}
function search(query: SearchQuery): SearchResults {
return index.find(query);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.long_parameter_list]
enabled = true
action = "warning"
max_params = 5 # allowed parameters before flagging
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
External linter coverage
- Ruff:
PLR0913 - ESLint:
max-params
Narrating Code (narrating_code)
What it is
A function is packed with explanatory comments — prefer clearer names or extracted helpers, keeping comments for why.
Why it's bad
The prose becomes a second implementation that readers must keep in sync with the code.
Example
Problem
def activate_plan(account: Account) -> None:
# Load the account owner.
owner = account.owner
# Load the billing profile.
billing = owner.billing_profile
# Check whether billing is enabled.
if not billing.enabled:
return
# Load the selected plan.
plan = billing.selected_plan
# Activate the selected plan.
account.activate(plan)
# Notify the owner.
notify_owner(owner, plan)
Proposed fix
Rename values and extract helper functions so the code says what the comments were saying.
def activate_plan(account: Account) -> None:
owner = account.owner
billing = owner.billing_profile
if not billing.enabled:
return
_activate_selected_plan(account, owner, billing)
def _activate_selected_plan(
account: Account,
owner: Owner,
billing: BillingProfile,
) -> None:
plan = billing.selected_plan
account.activate(plan)
notify_owner(owner, plan)
Problem
function activatePlan(account: Account): void {
// Load the account owner.
const owner = account.owner;
// Load the billing profile.
const billing = owner.billingProfile;
// Check whether billing is enabled.
if (!billing.enabled) {
return;
}
// Load the selected plan.
const plan = billing.selectedPlan;
// Activate the selected plan.
account.activate(plan);
// Notify the owner.
notifyOwner(owner, plan);
}
Proposed fix
Extract named predicates/helpers and keep comments for constraints or rationale.
function activatePlan(account: Account): void {
const owner = account.owner;
const billing = owner.billingProfile;
if (!billing.enabled) {
return;
}
activateSelectedPlan(account, owner, billing);
}
function activateSelectedPlan(
account: Account,
owner: Owner,
billing: BillingProfile,
): void {
const plan = billing.selectedPlan;
account.activate(plan);
notifyOwner(owner, plan);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.narrating_code]
enabled = true
action = "warning"
min_comment_lines = 5 # detector-specific threshold
max_comment_ratio_percent = 30 # detector-specific threshold
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Coupling and Cohesion
Data Clump (data_clump)
What it is
The same group of values is passed together through many functions — bundle them into one object or typed structure. Related code-smell reference: Refactoring.Guru: Data Clumps.
Why it's bad
The same bundle has to be kept in sync everywhere it travels.
Example
Problem
def format_label(street: str, city: str, zip_code: str) -> str:
return f"{street}, {city} {zip_code}"
def shipping_zone(street: str, city: str, zip_code: str) -> str:
return zones.lookup(street, city, zip_code)
Proposed fix
Introduce a dataclass, TypedDict, or domain object for the repeated fields.
from dataclasses import dataclass
@dataclass(frozen=True)
class Address:
street: str
city: str
zip_code: str
def format_label(address: Address) -> str:
return f"{address.street}, {address.city} {address.zip_code}"
def shipping_zone(address: Address) -> str:
return zones.lookup(address.street, address.city, address.zip_code)
Problem
function formatLabel(street: string, city: string, zipCode: string): string {
return `${street}, ${city} ${zipCode}`;
}
function shippingZone(street: string, city: string, zipCode: string): string {
return zones.lookup(street, city, zipCode);
}
Proposed fix
Introduce an interface or value object and pass that object through the API.
interface Address {
street: string;
city: string;
zipCode: string;
}
function formatLabel(address: Address): string {
return `${address.street}, ${address.city} ${address.zipCode}`;
}
function shippingZone(address: Address): string {
return zones.lookup(address.street, address.city, address.zipCode);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.data_clump]
enabled = true
action = "warning"
min_fields = 4 # shared fields required for a data clump
min_occurrences = 3 # functions sharing the clump before flagging
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Divergent Change (divergent_change)
What it is
One module gets edited for many unrelated reasons — it has too many responsibilities; split it along those axes. Related code-smell reference: Refactoring.Guru: Divergent Change.
Why it's bad
One file starts changing for unrelated reasons, so fixes get tangled.
Example
Problem
class InvoiceReport:
def render_header(self) -> str:
return self.title.upper()
def calculate_total(self) -> float:
return self.subtotal + self.tax
def save_archive(self) -> None:
archive.write(self.path)
Proposed fix
Split the module by reason-to-change: presentation, pricing, persistence, etc.
class InvoiceRenderer:
def render_header(self, invoice: Invoice) -> str:
return invoice.title.upper()
class InvoiceTotals:
def calculate_total(self, invoice: Invoice) -> float:
return invoice.subtotal + invoice.tax
class InvoiceArchive:
def save(self, invoice: Invoice) -> None:
archive.write(invoice.path)
Problem
class InvoiceReport {
renderHeader(): string {
return this.title.toUpperCase();
}
calculateTotal(): number {
return this.subtotal + this.tax;
}
saveArchive(): void {
archive.write(this.path);
}
}
Proposed fix
Move unrelated responsibilities into focused modules or services.
class InvoiceRenderer {
renderHeader(invoice: Invoice): string {
return invoice.title.toUpperCase();
}
}
class InvoiceTotals {
calculateTotal(invoice: Invoice): number {
return invoice.subtotal + invoice.tax;
}
}
class InvoiceArchive {
save(invoice: Invoice): void {
archive.write(invoice.path);
}
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.divergent_change]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Divergent Abstraction (divergent_abstraction)
What it is
An abstraction has only two implementations that grow in different directions — remove the forced common type or narrow it to genuine shared behavior.
Why it's bad
A forced common type couples two implementations whose real responsibilities no longer match.
Example
Problem
from typing import Protocol
class Worker(Protocol):
def run(self): ...
class FileWorker(Worker):
def run(self): ...
def open_file(self): ...
def rotate_file(self): ...
class QueueWorker(Worker):
def run(self): ...
def reserve_job(self): ...
def acknowledge_job(self): ...
Proposed fix
Narrow the Protocol to genuine shared behavior, or use the concrete types directly.
class FileWorker:
def open_file(self): ...
def rotate_file(self): ...
class QueueWorker:
def reserve_job(self): ...
def acknowledge_job(self): ...
Problem
abstract class Worker {
abstract run(): void;
}
class FileWorker extends Worker {
run() {}
openFile() {}
rotateFile() {}
}
class QueueWorker extends Worker {
run() {}
reserveJob() {}
acknowledgeJob() {}
}
Proposed fix
Narrow the interface to genuine shared behavior, or use the concrete types directly.
class FileWorker {
openFile() {}
rotateFile() {}
}
class QueueWorker {
reserveJob() {}
acknowledgeJob() {}
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.divergent_abstraction]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Feature Envy (feature_envy)
What it is
A method uses another object's data more than its own — move it onto the class that owns that data. Related code-smell reference: Refactoring.Guru: Feature Envy.
Why it's bad
Logic sits next to the wrong data, so edits keep reaching through a foreign object.
Example
Problem
class Account:
balance: int
fee_rate: int
limit: int
class BillingPolicy:
name: str
def fee_for(self, account: Account) -> tuple[str, int]:
fee = account.balance + account.fee_rate + account.limit
return self.name, fee
Proposed fix
Move the behavior onto the object that owns most of the data.
class Account:
balance: int
fee_rate: int
limit: int
def billing_fee(self) -> int:
return self.balance + self.fee_rate + self.limit
class BillingPolicy:
name: str
def fee_for(self, account: Account) -> tuple[str, int]:
return self.name, account.billing_fee()
Problem
class Account {
balance = 0;
feeRate = 0;
limit = 0;
}
class BillingPolicy {
name = "standard";
feeFor(account: Account): [string, number] {
const fee = account.balance + account.feeRate + account.limit;
return [this.name, fee];
}
}
Proposed fix
Put the calculation on the owning class/module or expose a narrow query method.
class Account {
balance = 0;
feeRate = 0;
limit = 0;
billingFee(): number {
return this.balance + this.feeRate + this.limit;
}
}
class BillingPolicy {
name = "standard";
feeFor(account: Account): [string, number] {
return [this.name, account.billingFee()];
}
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.feature_envy]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Inappropriate Intimacy (inappropriate_intimacy)
What it is
Two classes each reach into the other's internals, so neither can change independently — narrow the shared surface or merge them. Related code-smell reference: Refactoring.Guru: Inappropriate Intimacy.
Why it's bad
Two classes know too much about each other's internals, so refactors ripple.
Example
Problem
class Account:
def __init__(self) -> None:
self._token = "secret"
self._salt = "pepper"
def audit_account() -> str:
account: Account = Account()
return account._token + account._salt
Proposed fix
Add a public method on the collaborator or merge the coupled objects.
class Account:
def __init__(self) -> None:
self._token = "secret"
self._salt = "pepper"
def audit_key(self) -> str:
return self._token + self._salt
def audit_account() -> str:
account = Account()
return account.audit_key()
Problem
class Account {
_token = "secret";
_salt = "pepper";
}
function auditAccount(): string {
const account: Account = new Account();
return account._token + account._salt;
}
Proposed fix
Expose a narrow method/property instead of reaching into internals.
class Account {
private token = "secret";
private salt = "pepper";
auditKey(): string {
return this.token + this.salt;
}
}
function auditAccount(): string {
const account = new Account();
return account.auditKey();
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.inappropriate_intimacy]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Message Chain (message_chain)
What it is
A long a.b.c.d access chain couples the caller to a deep object graph (Law of Demeter) — ask the immediate collaborator instead. Related code-smell reference: Refactoring.Guru: Message Chains.
Why it's bad
Deep property walking couples the caller to the whole object graph.
Example
Problem
def city(order: Order) -> str:
return order.customer.address.city.name
Proposed fix
Ask the nearest object for the answer through a method or property.
class Order:
def shipping_city(self) -> str:
return self.customer.shipping_city()
class Customer:
def shipping_city(self) -> str:
return self.address.city_name()
class Address:
def city_name(self) -> str:
return self.city.name
def city(order: Order) -> str:
return order.shipping_city()
Problem
function city(order: Order): string {
return order.customer.address.city.name;
}
Proposed fix
Expose a query on the immediate collaborator instead of chaining internals.
class Order {
shippingCity(): string {
return this.customer.shippingCity();
}
}
class Customer {
shippingCity(): string {
return this.address.cityName();
}
}
class Address {
cityName(): string {
return this.city.name;
}
}
function city(order: Order): string {
return order.shippingCity();
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.message_chain]
enabled = true
action = "warning"
max_depth = 4 # allowed message-chain depth
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Refused Bequest (refused_bequest)
What it is
A subclass inherits methods/fields it doesn't use or stubs out — the inheritance is wrong; prefer composition. Related code-smell reference: Refactoring.Guru: Refused Bequest.
Why it's bad
Inheritance promises behavior the subclass does not actually want.
Example
Problem
class Report:
def export_pdf(self) -> bytes:
return render_pdf(self)
def send_email(self) -> None:
mailer.send(self)
class ReadOnlyReport(Report):
def export_pdf(self) -> bytes:
raise NotImplementedError
def send_email(self) -> None:
raise NotImplementedError
Proposed fix
Prefer composition or split the base class into smaller capabilities.
class ReadOnlyReport:
def export_pdf(self) -> bytes:
return render_pdf(self)
Problem
class Report {
exportPdf(): Uint8Array {
return renderPdf(this);
}
sendEmail(): void {
mailer.send(this);
}
}
class ReadOnlyReport extends Report {
exportPdf(): Uint8Array {
throw new Error("not supported");
}
sendEmail(): void {
throw new Error("not supported");
}
}
Proposed fix
Use composition or narrower interfaces instead of a broad base class.
class ReadOnlyReport implements ExportableReport {
exportPdf(): Uint8Array {
return renderPdf(this);
}
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.refused_bequest]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Shotgun Surgery Hazard (shotgun_surgery_hazard)
What it is
A symbol so widely depended-on that one change ripples across many modules — a blast-radius hotspot. Related code-smell reference: Refactoring.Guru: Shotgun Surgery.
Why it's bad
One edit fans out to many dependents.
Example
Problem
TAX_RATE = 0.2
def calculate(amount: float) -> float:
return amount * TAX_RATE
Proposed fix
Separate stable interfaces from volatile implementation details.
class TaxPolicy:
def __init__(self, rate: float) -> None:
self.rate = rate
def calculate(self, amount: float) -> float:
return amount * self.rate
Problem
export const TAX_RATE = 0.2;
export function calculate(amount: number): number {
return amount * TAX_RATE;
}
Proposed fix
Reduce fan-in by splitting policy from shared utility shape.
class TaxPolicy {
constructor(private readonly rate: number) {}
calculate(amount: number): number {
return amount * this.rate;
}
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.shotgun_surgery_hazard]
enabled = true
action = "warning"
min_blast = 4 # dependent modules before shotgun-surgery risk
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Type and Data Modeling
Implicit Schema (implicit_schema)
What it is
A dict/object accessed by many string keys — an unwritten schema; model it as a typed structure.
Why it's bad
Stringly typed payloads drift silently when the shape changes.
Example
Problem
def create_user(payload: dict[str, object]) -> User:
email = payload["email"]
plan = payload["plan"]
active = payload["active"]
return users.create(email, plan, active)
Proposed fix
Model the payload with a dataclass, TypedDict, or Pydantic model.
from dataclasses import dataclass
@dataclass(frozen=True)
class UserPayload:
email: str
plan: str
active: bool
def create_user(payload: UserPayload) -> User:
return users.create(payload.email, payload.plan, payload.active)
Problem
function createUser(payload: Record<string, unknown>): User {
const email = payload["email"];
const plan = payload["plan"];
const active = payload["active"];
return users.create(email, plan, active);
}
Proposed fix
Replace loose records with an interface or validated schema.
interface UserPayload {
email: string;
plan: string;
active: boolean;
}
function createUser(payload: UserPayload): User {
return users.create(payload.email, payload.plan, payload.active);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.implicit_schema]
enabled = true
action = "warning"
min_keys = 4 # string keys on one object before implicit-schema risk
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Loose Typing (loose_typing)
What it is
A public signature leans on vague types (Any/untyped/overly broad) — tighten annotations so callers and tools know the contract.
Why it's bad
Weak types make invalid states look valid until runtime.
Example
Flags only direct escape hatches.
Problem
from typing import Any
def notify_contact(raw_contact: Any) -> None:
if raw_contact["active"]:
mailer.send(raw_contact["email"])
Proposed fix
Use a dataclass or another concrete model so callers pass named fields instead of loose keys. Do not fix this by creating a shallow alias such as UserPayload = dict[str, Any] or UserId = str.
from dataclasses import dataclass
@dataclass(frozen=True)
class UserContact:
email: str
active: bool
def notify(user: UserContact) -> None:
if user.active:
mailer.send(user.email)
Flags escape hatches plus schema-erasing maps and records.
Problem
from typing import Any
def notify_contact(contact: dict[str, Any]) -> None:
if contact["active"]:
mailer.send(contact["email"])
Proposed fix
Use a dataclass or another concrete model so callers pass named fields instead of loose keys. Do not fix this by creating a shallow alias such as UserPayload = dict[str, Any] or UserId = str.
from dataclasses import dataclass
@dataclass(frozen=True)
class UserContact:
email: str
active: bool
def notify(user: UserContact) -> None:
if user.active:
mailer.send(user.email)
Flags everything in medium, primitive-only collections, and shallow aliases.
Problem
EmailAddress = str
def notify_all(addresses: list[EmailAddress]) -> None:
for address in addresses:
mailer.send(address)
Proposed fix
Use a dataclass or another concrete model so callers pass named fields instead of loose keys. Do not fix this by creating a shallow alias such as UserPayload = dict[str, Any] or UserId = str.
from dataclasses import dataclass
@dataclass(frozen=True)
class UserContact:
email: str
active: bool
def notify(user: UserContact) -> None:
if user.active:
mailer.send(user.email)
Flags only direct escape hatches.
Problem
function notifyContact(rawContact: any): void {
if (rawContact.active) {
mailer.send(rawContact.email);
}
}
Proposed fix
Replace any with an interface, unknown plus narrowing, or a schema-derived type. Do not fix this by creating a shallow alias such as type UserPayload = Record<string, any> or type UserId = string.
interface User {
email: string;
active: boolean;
}
function notify(user: User): void {
if (user.active) {
mailer.send(user.email);
}
}
Flags escape hatches plus schema-erasing maps and records.
Problem
function notifyContact(contact: Record<string, any>): void {
if (contact.active) {
mailer.send(contact.email);
}
}
Proposed fix
Replace any with an interface, unknown plus narrowing, or a schema-derived type. Do not fix this by creating a shallow alias such as type UserPayload = Record<string, any> or type UserId = string.
interface User {
email: string;
active: boolean;
}
function notify(user: User): void {
if (user.active) {
mailer.send(user.email);
}
}
Flags everything in medium, primitive-only collections, and shallow aliases.
Problem
type EmailAddress = string;
function notifyAll(addresses: EmailAddress[]): void {
for (const address of addresses) {
mailer.send(address);
}
}
Proposed fix
Replace any with an interface, unknown plus narrowing, or a schema-derived type. Do not fix this by creating a shallow alias such as type UserPayload = Record<string, any> or type UserId = string.
interface User {
email: string;
active: boolean;
}
function notify(user: User): void {
if (user.active) {
mailer.send(user.email);
}
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.loose_typing]
enabled = true
action = "warning"
strictness = "low"
Flags only direct escape hatches.
[smells.<lang>.rules.loose_typing]
enabled = true
action = "warning"
strictness = "medium"
Flags escape hatches plus schema-erasing maps and records.
[smells.<lang>.rules.loose_typing]
enabled = true
action = "warning"
strictness = "high"
Flags everything in medium, primitive-only collections, and shallow aliases.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Tuple Packing (tuple_packing)
What it is
Data passed as positional tuples whose fields aren't named — use a named structure so meaning is explicit.
Why it's bad
Positional bundles hide meaning and make the code brittle to reorderings.
Example
Problem
def summarize(user: User) -> tuple[str, str, str]:
return user.name, user.email, user.plan
Proposed fix
Use a named tuple, dataclass, or object with explicit fields.
from dataclasses import dataclass
@dataclass(frozen=True)
class UserSummary:
name: str
email: str
plan: str
def summarize(user: User) -> UserSummary:
return UserSummary(name=user.name, email=user.email, plan=user.plan)
Problem
function summarize(user: User): [string, string, string] {
return [user.name, user.email, user.plan];
}
Proposed fix
Use an interface/object instead of anonymous positional tuple data.
interface UserSummary {
name: string;
email: string;
plan: string;
}
function summarize(user: User): UserSummary {
return { name: user.name, email: user.email, plan: user.plan };
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.tuple_packing]
enabled = true
action = "warning"
max_tuple_return = 2 # allowed positional tuple items in a return
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Defensive Fallback Soup (defensive_fallback)
What it is
Broad error handling and repeated empty defaults hide invalid states — validate the boundary and let unexpected failures remain visible.
Why it's bad
Broad catches plus empty defaults turn contract violations into plausible but wrong data.
Example
Problem
def load_payload(raw):
try:
names = raw.get("names") or []
options = raw.get("options") or {}
return names, options
except:
return [], {}
Proposed fix
Validate once at the boundary, catch specific failures, and preserve unexpected errors.
def load_payload(raw):
try:
return Payload.parse(raw)
except InvalidPayload as error:
raise UserInputError() from error
Problem
function loadPayload(raw: Input) {
try {
const names = raw.names || [];
const options = raw.options || {};
return { names, options };
} catch {
return { names: [], options: {} };
}
}
Proposed fix
Parse once at the boundary, catch specific failures, and preserve unexpected errors.
function loadPayload(raw: unknown): Payload {
return PayloadSchema.parse(raw);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.defensive_fallback]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
Mutation and State
Mutated Parameter (mutated_parameter)
What it is
The function mutates a caller's argument in place — a hidden side effect; return a new value instead.
Why it's bad
Mutating inputs hides side effects and makes call order matter.
Example
Problem
def add_item(items: list[Item], item: Item) -> list[Item]:
items.append(item)
return items
Proposed fix
Return a new collection or make the mutation explicit in the API name.
def add_item(items: list[Item], item: Item) -> list[Item]:
next_items = list(items)
next_items.append(item)
return next_items
Problem
function addItem(items: Item[], item: Item): Item[] {
items.push(item);
return items;
}
Proposed fix
Return a copied value or make mutation an intentional method on an owner.
function addItem(items: Item[], item: Item): Item[] {
return [...items, item];
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.mutated_parameter]
enabled = true
action = "warning"
include_attributes = false # also flag mutation of fields on parameters
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
External linter coverage
- ESLint:
no-param-reassign
Reassigned Parameter (reassigned_parameter)
What it is
A parameter is rebound to a new value inside the body — confusing; use a separate local.
Why it's bad
Rebinding a parameter muddies the original meaning of the value.
Example
Problem
def normalize(name: str) -> str:
name = name.strip()
return name.title()
Proposed fix
Introduce a local variable for the transformed value.
def normalize(name: str) -> str:
cleaned_name = name.strip()
return cleaned_name.title()
Problem
function normalize(name: string): string {
name = name.trim();
return name.toUpperCase();
}
Proposed fix
Use a separate const for each semantic step.
function normalize(name: string): string {
const cleanedName = name.trim();
return cleanedName.toUpperCase();
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.reassigned_parameter]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
External linter coverage
- ESLint:
no-param-reassign
Split Variable (split_variable)
What it is
One local is reassigned to mean different things at different points — use distinct, single-purpose bindings.
Why it's bad
A variable with multiple meanings is a trap for both readers and debuggers.
Example
Problem
def invoice_total(invoice: Invoice) -> float:
result = sum(item.price for item in invoice.items)
result = result - invoice.discount
result = result + invoice.tax
return result
Proposed fix
Use separate locals named for each meaning.
def invoice_total(invoice: Invoice) -> float:
subtotal = sum(item.price for item in invoice.items)
discounted_total = subtotal - invoice.discount
return discounted_total + invoice.tax
Problem
function invoiceTotal(invoice: Invoice): number {
let result = sum(invoice.items.map((item) => item.price));
result = result - invoice.discount;
result = result + invoice.tax;
result = roundCurrency(result);
return result;
}
Proposed fix
Prefer distinct const bindings for distinct concepts.
function invoiceTotal(invoice: Invoice): number {
const subtotal = sum(invoice.items.map((item) => item.price));
const discountedTotal = subtotal - invoice.discount;
return discountedTotal + invoice.tax;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.split_variable]
enabled = true
action = "warning"
min_assigns = 2 # assignments to one local before flagging
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
Performance
Nested Loop (nested_loop)
What it is
A loop is nested directly or through a helper called inside a loop — work grows multiplicatively; combine passes or pre-index the data.
Why it's bad
Costs grow faster than the data and the code gets hard to flatten.
Example
Problem
def find_matches(users: list[User], orders: list[Order]) -> list[tuple[User, Order]]:
matches = []
for user in users:
for order in orders:
if order.user_id == user.id:
matches.append((user, order))
return matches
Proposed fix
Pre-index one side, combine passes, or use a clearer iterator pipeline.
def find_matches(users: list[User], orders: list[Order]) -> list[tuple[User, Order]]:
orders_by_user = {order.user_id: order for order in orders}
matches = []
for user in users:
order = orders_by_user.get(user.id)
if order is not None:
matches.append((user, order))
return matches
Problem
function findMatches(users: User[], orders: Order[]): Array<[User, Order]> {
const matches: Array<[User, Order]> = [];
for (const user of users) {
for (const order of orders) {
if (order.userId === user.id) {
matches.push([user, order]);
}
}
}
return matches;
}
Proposed fix
Build a lookup map or flatten the data before iterating.
function findMatches(users: User[], orders: Order[]): Array<[User, Order]> {
const ordersByUser = indexOrdersByUser(orders);
const matches: Array<[User, Order]> = [];
for (const user of users) {
const order = ordersByUser.get(user.id);
if (order) {
matches.push([user, order]);
}
}
return matches;
}
function indexOrdersByUser(orders: Order[]): Map<string, Order> {
const ordersByUser = new Map<string, Order>();
for (const order of orders) {
ordersByUser.set(order.userId, order);
}
return ordersByUser;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.nested_loop]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
N+1 Loop Call (n_plus_one_call)
What it is
An external-looking call runs once per loop item — prefer a bulk query/request or prefetch so work scales by batch, not item.
Why it's bad
A per-item call can explode runtime and hit the backend one request at a time.
Example
Problem
def load_profiles(api: ProfileApi, users: list[User]) -> list[Profile]:
profiles = []
for user in users:
profiles.append(api.fetch(user.id))
return profiles
Proposed fix
Batch-load or prefetch the related data before the loop.
def load_profiles(api: ProfileApi, users: list[User]) -> list[Profile]:
ids = [user.id for user in users]
profiles = api.fetch_many(ids)
return [profiles[user_id] for user_id in ids]
Problem
function loadProfiles(api: ProfileApi, users: User[]): Profile[] {
const profiles = [];
for (const user of users) {
profiles.push(api.fetch(user.id));
}
return profiles;
}
Proposed fix
Use a bulk endpoint/query or prefetch into a map before rendering.
async function loadProfiles(api: ProfileApi, users: User[]): Promise<Profile[]> {
const ids = users.map((user) => user.id);
const profiles = await api.fetchMany(ids);
return ids.map((id) => profiles[id]);
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.n_plus_one_call]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | No |
| JS / TS | No |
| Rust | No |
Repeated Iteration (repeated_iteration)
What it is
The same collection is iterated several times in one scope — fuse the passes so the data is scanned once.
Why it's bad
The same collection gets scanned over and over when one pass would do.
Example
Problem
def summarize(items: Items) -> Summary:
has_items = any(items)
total = sum(items)
return Summary(has_items=has_items, total=total)
Proposed fix
Fuse compatible passes or cache the intermediate result.
def summarize(items: Items) -> Summary:
has_items = False
total = 0
for item in items:
has_items = True
total += item
return Summary(has_items=has_items, total=total)
Problem
function summarize(items: Items): Summary {
const hasItems = items.some(Boolean);
const total = items.reduce((sum, item) => sum + item.price, 0);
return { hasItems, total };
}
Proposed fix
Combine loops when the operations share the same traversal.
function summarize(items: Items): Summary {
let hasItems = false;
let total = 0;
for (const item of items) {
hasItems = true;
total += item.price;
}
return { hasItems, total };
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.repeated_iteration]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |
Sort In Loop (sort_in_loop)
What it is
A collection is sorted inside a loop — hoist sorting or maintain ordered data to avoid repeated O(n log n) work.
Why it's bad
Repeated sorts turn a small loop into a surprisingly expensive one.
Example
Problem
def grouped_names(groups: Groups) -> Names:
result = []
for group in groups:
names = list(group.names)
names.sort()
result.extend(names)
return result
Proposed fix
Sort once before the loop or keep data ordered as it is built.
def grouped_names(groups: Groups) -> Names:
names = []
for group in groups:
names.extend(group.names)
names.sort()
return names
Problem
function groupedNames(groups: Groups, names: string[]): string[] {
const result: string[] = [];
for (const group of groups) {
names.sort();
result.push(group.name);
}
return result;
}
Proposed fix
Hoist sorting out of the loop or maintain an ordered structure.
function groupedNames(groups: Groups): string[] {
const names: string[] = [];
for (const group of groups) {
names.push(...group.names);
}
names.sort();
return names;
}
Tune It
Replace <lang> with python, javascript, typescript, or rust.
[smells.<lang>.rules.sort_in_loop]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
| Language | Enabled by default |
|---|---|
| Python | Yes |
| JS / TS | Yes |
| Rust | Yes |