Skip to content

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonYes
JS / TSNo
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSNo
RustYes

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

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
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes