honest framework

Honest Code Principles

Sixteen engineering positions. Each names a pattern, states why the conventional alternative fails, and shows the honest version.

The sixteen practices that make Laravel code honest. Start here before reading any other module.
The conventional approach
class UserService:
    def __init__(self, db, config):
        self._db     = db       # hidden state
        self._config = config   # hidden state

    def create_user(self, email: str, role: str):
        if role == "admin":          # dispatch chain
            self._db.grant_admin()   # I/O in method
        elif role == "editor":
            self._db.grant_editor()
        user = User(email=email, role=role)
        self._db.save(user)          # mutation + I/O
        return user
The honest version
ROLE_GRANTS = {
    "admin":  grant_admin,    # dict dispatch (P01)
    "editor": grant_editor,
}

def create_user(manifest: dict) -> dict:  # pure fn (P03)
    return {
        **manifest,
        "user": {"email": manifest["email"],
                 "role":  manifest["role"]},
        "grant": ROLE_GRANTS[manifest["role"]],
    }
# No class. No self. No hidden state.
# I/O happens at the boundary, not here.
The conventional approach
class OrderService
  def initialize(order_repo, mailer)
    @order_repo = order_repo  # hidden state
    @mailer     = mailer      # hidden state
  end

  def create(params)
    order = Order.new(params)  # mutation
    order.save!                # I/O in method
    @mailer.send_confirmation(order)  # I/O in method
    order
  end
end
The honest version
def build_order(manifest)         # pure function
  manifest.merge(
    order: {
      id:     SecureRandom.uuid,
      items:  manifest[:items],
      total:  manifest[:total],
    }
  )
end

# Boundary link handles I/O:
# - save order (honest-persist)
# - emit order.placed (honest-observe)
# - send confirmation (honest-alerts)
The conventional approach
type OrderService struct {
    db     *sql.DB   // hidden state
    mailer Mailer    // hidden state
}

func (s *OrderService) Create(params map[string]any) (*Order, error) {
    order := &Order{Items: params["items"]}  // mutation
    if err := s.db.Save(order); err != nil { // I/O in method
        return nil, err
    }
    s.mailer.Send(order)  // I/O in method
    return order, nil
}
The honest version
// Pure function — no receiver, no I/O
func BuildOrder(manifest Manifest) Manifest {
    return Manifest{
        "order": map[string]any{
            "id":    uuid.New().String(),
            "items": manifest["items"],
            "total": manifest["total"],
        },
    }
}
// Boundary link calls SaveOrder(conn, manifest)
// and EmitOrderPlaced(manifest) — I/O at the edges.
The conventional approach
class OrderService {
    public function __construct(
        private OrderRepository $repo,
        private MailService $mailer,
    ) {}

    public function create(array $data): Order {
        $order = new Order($data);      // mutation
        $this->repo->save($order);      // I/O in method
        $this->mailer->confirm($order); // I/O in method
        return $order;
    }
}
The honest version
function buildOrder(array $manifest): array {
    return array_merge($manifest, [
        'order' => [
            'id'    => (string) Str::uuid(),
            'items' => $manifest['items'],
            'total' => $manifest['total'],
        ],
    ]);
}
// Pure function. No class. No $this. No I/O.
// Boundary link handles save + notify.
The conventional approach
# Class with hidden state and mutating methods.
# I/O inside business logic methods.
# if/elif dispatch chains.
# Inheritance for code reuse.
# Tests need mocks to isolate the I/O.
# The class is the unit. The test is the mock setup.
The honest version
# Pure functions that take data in and return data out.
# I/O only at declared boundaries.
# Dict dispatch tables instead of if/elif chains.
# TypedDict over classes.
# No mocks — the pure function has nothing to mock.
# The test is: assert f(input) == expected_output.

Honest Code is not a style guide. It is a set of engineering positions backed by specific claims about what makes software fail at scale. Each principle names a pattern, states why the conventional alternative fails, and shows what the honest version looks like.

The sixteen principles

Dict-lookup polymorphism. An if/elif/else chain that dispatches on type or category is a data table masquerading as control flow. Replace it with a dict mapping keys to handlers. Adding a new case means adding a row, not modifying logic. The dict is the honest representation of the dispatch table that was already there.

Typed dicts over classes. A class with fields, methods, getters, setters, and lifecycle hooks is a data structure that refuses to admit what it is. A TypedDict is the same data with no behavior attached. If you cannot json.dumps() it, it is too clever by half.

Pure functions over methods. A method that mutates internal state hides its side effects behind a noun. A pure function takes data in and returns data out. No self. No side effects. No surprises. The function is testable with a single assertion.

I/O at the boundary. Pure business logic in the middle; I/O at the edges. The boundary calls the pure function and does the I/O with the result. This is why mocks become unnecessary — the pure core has nothing to mock.

Flat composition over inheritance. pipe(validate, authenticate, rate_limit, create_order) is visible at the point of assembly. class B extends A extends Base hides the execution path. No super() calls. No method resolution order surprises.

DOM as state. The DOM is the state. The server renders HTML; HTMX swaps it into the page. Server-rendered fragments replace client-side state management. One copy of truth, not two.

HTML attributes over imperative DOM manipulation. The attribute declares intent; the library handles mechanism. hx-post="/endpoint" and hf-format="currency" replace seventy-three lines of addEventListener, querySelector, and innerHTML.

Typed exceptions at the boundary. Let functions raise. The route handler catches, inspects the exception type, and returns the appropriate status code. Retry logic belongs in infrastructure, not inline in the function.

SQL over application caches. A single SQL join with proper indexes runs under 3ms. The cache adds invalidation bugs, stale data, and a second source of truth. Profile the query first. Only cache after measurement proves it necessary.

Pure function assertions over mocks. assert f(input) == expected_output. That is the whole test. If you need nine mocks to test a function, the function has nine hidden dependencies. Extract the pure logic and test it directly.

Type declarations over imperative validation. Declare a schema, a TypedDict, a SQL column constraint, or an <input type="email">. The runtime, type checker, database, or browser enforces it. The programmer declares; the machinery enforces.

Context managers over instance state. async with create_connection(config) as conn: opens and closes within the scope. No persistent state leaks into the caller. Crash recovery is trivial because there is nothing to clean up.

Configuration as parameters. Pass config: dict as an argument. The dependency is visible in the signature. No hidden state. No initialization order bugs.

Simple Gherkin steps signal honest architecture. If your step definition is thirty lines of mock configuration, the code under test has hidden dependencies. When the function is pure, the step is: call the function, check the result.

Declarative equivalents over lifecycle hooks. hx-trigger="load" replaces componentDidMount. Server-rendered HTML arrives ready. No client-side initialization sequence.

Strangler pattern for migration. Extract one pure function from one class method per sprint. The class still exists; the interface does not change. After six months the class is a thin shell. Removing it is a cleanup task.

The abstract principle

Honest code is not a style. It is a set of structural properties with mechanical consequences.

The sixteen principles are not aesthetic preferences. Each one names a structural pattern, states the mechanical consequence of the dishonest version, and shows the honest version. The honest version is always measurably easier to verify, test, and reason about. Not subjectively cleaner. Measurably better on specific dimensions.

The core claim

Big State is the disease. Classes are the primary vector.

A class with fields, methods, getters, setters, and lifecycle hooks combines data and behavior in one object. The data is hidden behind the interface. The behavior has side effects on the hidden data. The side effects are not visible at the call site. The hidden state accumulates across calls. The program is a network of objects mutating each other's hidden state.

This is the standard object-oriented model. It is also why large OOP codebases are difficult to understand, difficult to test, and prone to bugs that appear only under specific sequences of method calls.

A pure function has no hidden state. It takes data in and returns data out. Every input is visible at the call site. Every output is visible at the return. The program is a composition of functions. Understanding any part of it requires only reading that function and the functions it calls.

Why dict dispatch is theoretically superior

An if/elif/else chain is an incomplete function. It can have cases that fall through. It can have unreachable branches. It can have implicit defaults. None of these properties are verifiable without exhaustive testing.

A dict dispatch table is a partial function with a declared domain. The keys are the domain. Every key either has a handler or it does not. This is checkable by set inspection: assert set(FEATURES.keys()) == set(HANDLER_TABLE.keys()). The check is O(n), deterministic, and requires no execution.

This is the formal argument for dict dispatch, not just the practical one: a dict is a verifiably total (or partial) function. An if/elif/else chain is a partial function with no verifiable domain declaration.

Referential transparency

A pure function has the property of referential transparency: the function call can be replaced by its return value without changing the program's meaning. This is the mathematical definition of a pure function.

Referential transparency enables equational reasoning: you can substitute equals for equals in your mental model of the program. You can understand a composition of pure functions by understanding each function in isolation and combining the understanding. You cannot do this with methods that have side effects on shared state.

honest-test's purity verification checks referential transparency empirically: run the function twice with the same input. If the outputs differ, the function is not referentially transparent. This is not a proof. It is a strong practical check that catches the most common purity violations.

The cost accounting argument

The Honest Code benchmark data (honestcode.software/evidence.html) shows that honest code is faster than dishonest code on the same hardware. Python dishonest 1,916ns vs honest 1,417ns. Java mock-heavy 1,376ns vs pure function 334ns (4.1x).

The performance argument is secondary to the correctness argument, but it resolves a common objection. The objection is: "functional style is cleaner but slower." The benchmark data shows this is false. Pure functions doing pure computation with no pointer-chasing, no vtable dispatch, no heap scatter, and no synchronization overhead are faster, not slower.

The CPU performs best doing pure computation. Hidden state, synchronization, and object indirection are not free. They are costs paid for no architectural benefit.

Full specification

The Sixteen Principles

P01 — Dict-Lookup Polymorphism An if/elif/else chain that dispatches on type or category is a data table masquerading as control flow. Replace it with a dict mapping keys to handlers. Adding a case means adding a row, not modifying logic. honest-check HC-P001 enforces this statically. The deciding rule: if the condition's values are enumerable, the pattern applies.

Two named sub-patterns: - Data table: same operation, varying values — {"USD": 1.0, "EUR": 0.85, "GBP": 0.73} - Handler table: different operations per key — {"email": send_email, "sms": send_sms}

P02 — Typed Dicts Over Classes A class with fields, methods, getters, setters, and lifecycle hooks is a data structure that refuses to admit what it is. Replace it with a TypedDict (or language equivalent). The data is just data. If you cannot json.dumps() it, it is too clever by half. honest-check HC-P002 and HC-P003 enforce this statically.

P03 — Pure Functions Over Methods A method that mutates internal state hides its side effects. A pure function takes data in and returns data out. No self. No side effects. No surprises. A pure function is testable with a single assertion: assert f(input) == expected. honest-check HC-P002 enforces this.

P04 — I/O at the Boundary Pure business logic in the middle; I/O at the edges. The boundary calls the pure function and does the I/O with the result. This is why mocks become unnecessary — the pure core has nothing to mock. honest-check HC-P004 enforces this. The @link decorator declares which functions are boundaries.

P05 — Flat Composition Over Inheritance pipe(validate, authenticate, rate_limit, create_order) is visible at the point of assembly. class B extends A extends Base hides the execution path. No super() calls. No method resolution order surprises. honest-check HC-P003 enforces this.

P06 — DOM as State (DATAOS) The DOM is the state. The server renders HTML; HTMX swaps it into the page. hx-get and hx-target replace useState and useEffect. One copy of truth, not two. honest-check HC-P011 enforces this.

P07 — HTML Attributes Over Imperative DOM Manipulation The attribute declares intent; the library handles mechanism. hx-post="/endpoint" and hf-format="currency" replace addEventListener, querySelector, and innerHTML. Seventy-three lines of JavaScript become six attributes.

P08 — Typed Exceptions at the Boundary Let functions raise. The route handler catches, inspects the exception type, and returns the appropriate HTTP status. Retry logic belongs in infrastructure, not inline in the function.

P09 — SQL Over Application Caches A single SQL join with proper indexes runs under 3ms. The cache adds invalidation bugs, stale data, and a second source of truth. Profile the query first. Cache only after measurement proves it necessary. honest-check HC-P006 flags caches without profiling evidence.

P10 — Pure Function Assertions Over Mocks assert f(input) == expected. That is the whole test. If you need nine mocks to test a function, the function has nine hidden dependencies. Extract the pure logic and test it directly.

P11 — Type Declarations Over Imperative Validation Declare a schema, a TypedDict, a SQL column constraint, or an <input type="email">. The runtime, type checker, database, or browser enforces it. The programmer declares; the machinery enforces.

P12 — Context Managers Over Instance State async with create_connection(config) as conn: opens and closes within the scope. No persistent state leaks into the caller. Crash recovery is trivial because there is nothing to clean up. honest-check HC-P007 flags instance state in constructors.

P13 — Configuration as Parameters Pass config: dict as an argument. The dependency is visible in the signature. No hidden state. No initialization order bugs.

P14 — Simple Gherkin Steps Signal Honest Architecture If your step definition is thirty lines of mock configuration, the code under test has hidden dependencies. When the function is pure, the step is: call the function, check the result.

P15 — Declarative Equivalents Over Lifecycle Hooks hx-trigger="load" replaces componentDidMount. Server-rendered HTML arrives ready. No client-side initialization sequence. honest-check HC-P011 enforces this.

P16 — Strangler Pattern for Migration Extract one pure function from one class method per sprint. The class still exists; the interface does not change. After six months the class is a thin shell. Removing it is a cleanup task.

Statically Enforced Principles

Principlehonest-check Rule
P01 Dict-lookup polymorphismHC-P001
P02 Typed dicts over classesHC-P002, HC-P003
P03 Pure functions over methodsHC-P002
P04 I/O at the boundaryHC-P004
P05 Flat composition over inheritanceHC-P003
P06 DOM as stateHC-P011
P07 HTML attributesHC-P011
P09 SQL over cachesHC-P006
P12 Context managersHC-P007
P15 Declarative over lifecycle hooksHC-P011

Principles P08, P10, P11, P13, P14, P16 are architectural positions verified by code review and honest-test, not by static rules.

Runtime Enforcement

honest-test's honesty tests verify P03 (purity), P04 (boundary isolation), and P13 (configuration as parameters) at runtime by instrumenting link execution and detecting I/O, mutations, and non-determinism automatically.

Conformance

A conformant codebase satisfies all sixteen principles. The degree of automated enforcement depends on the tooling available:

  • Statically verifiable: P01, P02, P03, P04, P05, P06, P07, P09, P12, P15 — honest-check enforces these before commit
  • Runtime verifiable: P03, P04, P10, P13 — honest-test verifies these in the test suite
  • Architecturally verified: P08, P11, P14, P16 — verified through code review against this document

Reference