Functions & Methods
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
- Definir nova função
All triggers
- Escrever função/método novo
- Refatorar função longa/complexa
- Code review verificando function design
- Decidir entre named params, builder, options struct
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.kmdR3.
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
elsebranches
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)vsdef 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:
- Function lines > soft → warning; > hard → error
- Args > 4 → warning; > 6 → error
- Cyclomatic > 10 → warning; > 15 → error
- Mutable default arg (Python) → error
- Boolean position arg > 2 → warning (suggest named/options)
Cross-link
code/naming.kmd— naming conventioncode/error-handling.kmd— exceptions/result patternscode/comments.kmd— quando documentarcode/anti-patterns.kmd— patterns proibidos
References
rfcs/design-RFC-001-koder-design-system.kmdspecs/code/naming.kmdspecs/code/error-handling.kmdspecs/code/comments.kmdpolicies/design-governance.kmd