Functions & Methods

code specs/code/functions.kmd

Design de funções/métodos cross-language: length limits (soft 30 / hard 60), argument count ≤4, early-return preferred sobre nested, default values per-lang, pure vs impure marking, side effects documentadas, cyclomatic complexity (soft 10 / hard 15).

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Functions & Methods

Facet Code do Koder Design.

Cobre design de funções/métodos cross-language. Naming de funções em code/naming.kmd R3.

R1 — Length limits

Limite Soft (warning) Hard (error)
Lines per function 30 60
Lines per method 30 60

Comentários e linhas em branco contam como 0.5 (linter usa LOC efetivo).

Refatorar quando passa:

  • Extract helper function (mais comum)
  • Quebrar por etapa lógica (parsing → validation → execution)
  • Quando é table-driven (loop sobre data), pode ser maior — exceção documentada via comment
# ❌ Função de 80 linhas com 5 responsabilidades misturadas
def process_request(req):
    # parsing (15 linhas)
    ...
    # validation (20 linhas)
    ...
    # business logic (30 linhas)
    ...
    # serialization (15 linhas)
    ...

# ✅ Quebrada por etapa
def process_request(req):
    parsed = _parse(req)
    _validate(parsed)
    result = _execute(parsed)
    return _serialize(result)

R2 — Argument count

Soft limit: 4 positional args. Hard limit: 6.

Mais que isso → agrupar via:

Padrão Linguagem
Options struct Go (idiom), Rust
Named arguments Python (kwarg=), Dart ({required ...})
Builder pattern Java/Kotlin, Rust (idiomático pra config)
Config object JS/TS
// ❌
func CreateUser(name, email, phone, address string, age int, isAdmin, isActive bool) (*User, error)

// ✅ Options struct
type CreateUserOpts struct {
    Name, Email, Phone, Address string
    Age                         int
    IsAdmin, IsActive           bool
}
func CreateUser(opts CreateUserOpts) (*User, error)
# ❌
def create_user(name, email, phone, address, age, is_admin, is_active):
    ...

# ✅ Named-only
def create_user(*, name, email, phone, address, age,
                is_admin=False, is_active=True):
    ...

Receivers/self não contam (r *Repo, self, this).

R3 — Early return / guard clauses preferido

# ❌ Pyramid of doom
def authenticate(user):
    if user is not None:
        if user.is_active:
            if user.has_password():
                if check_password(user, password):
                    return Session(user)
                else:
                    return None
            else:
                return None
        else:
            return None
    else:
        return None

# ✅ Guard clauses
def authenticate(user):
    if user is None:
        return None
    if not user.is_active:
        return None
    if not user.has_password():
        return None
    if not check_password(user, password):
        return None
    return Session(user)

Vale pra todas as linguagens com early-return. Reduz nesting + cyclomatic complexity.

R4 — Default values

Linguagem Suporta? Padrão
Python sim def f(x, y=10): (não usar mutable default — =None)
Rust não (usa Option<T>) f(x: i32, y: Option<i32>) { let y = y.unwrap_or(10); }
Go não usar options struct ou variadic
Dart sim void f(int x, [int y = 10]) ou {int y = 10}
JS/TS sim function f(x, y = 10)
Koda sim def f(x, y = 10)

Mutable default proibido em Python:

# ❌ Default compartilhado entre chamadas (gotcha clássico)
def append(item, items=[]):
    items.append(item)
    return items

# ✅
def append(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

R5 — Pure vs impure

Pure function: sem side effects (não muta argumento, não toca global, não faz IO), output depende só do input.

Impure function: tem side effects (muta state, IO, randomness, time).

Marker convention

  • Default = pure: nome simples (compute_total, format_email)
  • Impure com side effect óbvio: nome verb-ish (save_user, send_email, log_event) — o nome denuncia
  • Impure com side effect não-óbvio: comentário obrigatório:
def get_user(user_id):
    """Get user by id.

    Side effect: increments cache hit/miss counter.
    """
    counter.increment(...)
    return cache.get(user_id) or db.get(user_id)

R6 — Single Responsibility

Cada função faz uma coisa bem-definida. Heurística: o nome da função é uma frase verb que descreve uma ação. Se você precisa de "and" no nome (load_and_validate_config), é provavelmente 2 funções.

# ❌
def load_and_validate_config(path):
    raw = read_file(path)
    parsed = parse_toml(raw)
    if 'database' not in parsed:
        raise ValueError(...)
    if 'auth' not in parsed:
        raise ValueError(...)
    return parsed

# ✅
def load_config(path):
    return parse_toml(read_file(path))

def validate_config(config):
    for required in ('database', 'auth'):
        if required not in config:
            raise ValueError(...)

R7 — Cyclomatic complexity

Soft: 10 (warning). Hard: 15 (error).

Cada if/else/case/loop/try/and/or dentro da função adiciona +1.

Refatorar via:

  • Extract function
  • Replace conditional com polymorphism / dispatch table
  • Early return reduz else branches

Tools per-lang: gocyclo (Go), radon (Python), cyclomatic (generic), dart_code_metrics (Dart), eslint complexity (JS/TS).

R8 — Receiver / self conventions

Linguagem Convenção
Go Single-letter (r *Repo, c Config); consistent across methods of same type
Rust &self (read-only), &mut self (mutate), self (consume)
Python self sempre primeiro arg (idiom)
Dart implicit this; usar this. só pra desambiguar
Koda self implícito; método define receiver
JS/TS this implicit; arrow vs regular afeta binding

R9 — Side effects documentation

Funções que tocam IO/state externo devem:

  • Ter nome que denuncia (R5)
  • Doc-comment listando o side effect explícito quando não óbvio
  • Tratar erro per error-handling.kmd
  • Considerar se cabe injection de dependência (def fetch(http_client) vs def fetch() que cria HTTP client por dentro — testabilidade)

R10 — Naming verbs

Cross-link com code/naming.kmd R3. Resumo:

  • Function/method: verb phrase (get_user, compute_total)
  • Boolean function: predicate (is_active, has_permission)
  • Class/Type: noun (User, OrderSummary)

Anti-patterns

AP-F1 — God function

200 linhas, >10 args, >20 cyclomatic. Refatorar imediatamente.

AP-F2 — Boolean trap

// ❌ Sem ler a sig, é impossível dizer o que cada bool faz
createUser(name, email, true, false, true);

// ✅ Named/options
createUser({name, email, isAdmin: true, isVerified: false, sendWelcome: true});

AP-F3 — Hidden side effect com nome puro

# ❌ Nome sugere pure, mas escreve no disco
def format_email(addr):
    cache.write(addr, ...)  # surpresa
    return addr.lower().strip()

AP-F4 — Premature option parameter

Adicionar parâmetro opcional "pra caso de algum caller precisar" sem caller real. YAGNI. Adicionar quando o segundo caller chegar.

AP-F5 — Output via mutated argument (em linguagens com return)

# ❌
def add_total(items, result):
    result['total'] = sum(items)

# ✅
def total(items):
    return sum(items)

Exceção: Go onde mutation é idiomática (SortSlice(s, less)) ou quando alocar é caro (hot path).

Audit deterministic

functions-audit.sh:

  1. Function lines > soft → warning; > hard → error
  2. Args > 4 → warning; > 6 → error
  3. Cyclomatic > 10 → warning; > 15 → error
  4. Mutable default arg (Python) → error
  5. Boolean position arg > 2 → warning (suggest named/options)
  • code/naming.kmd — naming convention
  • code/error-handling.kmd — exceptions/result patterns
  • code/comments.kmd — quando documentar
  • code/anti-patterns.kmd — patterns proibidos

References