Clean Code for Humans and LLMs (Without Killing the Joy of Coding)

Published: (January 19, 2026 at 02:56 PM EST)
5 min read
Source: Dev.to

Source: Dev.to

LLM‑friendly Clean Code

LLMs can write code. They can refactor. They can generate tests.

If you’ve been coding for years, it’s easy to feel uneasy — not because the tool is bad, but because it touches something personal: a big part of coding joy comes from cleverness.

  • From elegant solutions, from seeing a problem collapse into a clean abstraction.
  • And then you read advice like: “Prefer boring over clever.”
  • It sounds like an obituary for creativity.

It doesn’t have to be that way. This article argues two things:

  1. Clean code for humans and clean code for LLMs are slightly different (human readability vs. model ambiguity).
  2. We can reconcile them without sacrificing the pleasure of coding, by placing cleverness where it belongs.

Context vs. Prediction

Humans read code with context

  • team conventions
  • architectural history
  • domain knowledge
  • intuition about “how we do things here”

LLMs don’t have that context.
They work by prediction, not intent.

So LLM‑friendly code is code that:

  • minimizes ambiguity
  • has explicit contracts
  • is structurally regular
  • is safe to refactor locally

This is not “writing for machines”.
It’s writing code that survives refactors — human or automated.

The phrase “writing for machines” is misleading because it suggests:

  • cleverness is bad
  • elegance is dangerous
  • fun is unprofessional

But cleverness isn’t the enemy – misplaced cleverness is.

Two Kinds of Cleverness

Good clevernessBad cleverness
reduces cognitive loadcompresses logic into tricky expressions
models the domainuses language edge‑cases
creates stable abstractionsdepends on implicit assumptions
simplifies future changelooks impressive but is fragile
Engineering artFun for the author, painful for everyone else

The goal isn’t to ban cleverness – it’s to move it to the right layer: clever inside, boring outside.

The Reconciliation

  • Human clean code
  • LLM clean code
  • Joy of building beautiful things

All three can coexist when we make the outside explicit, stable, predictable, and easy to refactor, while keeping the inside elegant, expressive, and domain‑focused.

Examples

1️⃣ Ruby Service with a Stable Signature

class CreateOrder
  def self.call(input)
    new.call(input)
  end

  def call(input)
    input = Input.new(input)

    validate(input)
      .bind { persist(input) }
      .bind { publish_event(input) }
  end
end
  • Predictable .call
  • Explicit input coercion
  • Clear pipeline
  • No metaprogramming

Result type (good cleverness)

class Result
  def self.ok(value = nil) = new(true, value, nil)
  def self.err(error)      = new(false, nil, error)

  attr_reader :value, :error

  def initialize(ok, value, error)
    @ok   = ok
    @value = value
    @error = error
  end

  def ok? = @ok

  def bind
    return self unless ok?
    yield(value)
  end
end

Validation step

def validate(input)
  return Result.err(:missing_customer) if input.customer_id.nil?
  Result.ok(input)
end

Domain‑friendly, composable pipeline → humans get elegance through composition, LLMs get explicit contracts and safe‑refactor boundaries.

2️⃣ Go HTTP Handler – Explicit & Dumb

type Server struct {
    Orders *OrdersService
}

func (s *Server) CreateOrder(w http.ResponseWriter, r *http.Request) {
    var req CreateOrderRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid json", http.StatusBadRequest)
        return
    }

    order, err := s.Orders.Create(r.Context(), req.ToInput())
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    json.NewEncoder(w).Encode(order)
}

LLM can refactor this safely.

Domain service – where the joy lives

type OrdersService struct {
    Repo  OrdersRepo
    Clock Clock
}

func (s *OrdersService) Create(ctx context.Context, in CreateOrderInput) (*Order, error) {
    if err := in.Validate(); err != nil {
        return nil, err
    }

    order := NewOrder(in.CustomerID, s.Clock.Now())
    order.AddItem(in.ItemID, in.Qty)

    if err := s.Repo.Save(ctx, order); err != nil {
        return nil, err
    }
    return order, nil
}

Domain model, invariants, high cohesion → IO is boring; domain is art.

3️⃣ TypeScript – Types Reduce Ambiguity

// Explicit domain result
type Result<T, E> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

Public function signature

export async function createOrder(
  input: CreateOrderInput
): Promise<Result<Order, { code: string }>> {
  if (!input.customerId) {
    return { ok: false, error: { code: "MISSING_CUSTOMER" } };
  }

  const order = buildOrder(input);
  await repo.save(order);

  return { ok: true, value: order };
}

Explicit, easy to consume, hard to misuse, trivial for LLMs to extend safely.

Strongly‑typed domain model

type Money = { currency: "PLN" | "EUR"; cents: number };

function addMoney(a: Money, b: Money): Money {
  if (a.currency !== b.currency) throw new Error("currency mismatch");
  return { currency: a.currency, cents: a.cents + b.cents };
}

Invariant enforcing, minimal surface area, strong semantics → “beautiful coding”.

Practical Standard You Can Adopt

GuidelineWhy
Stable function signatures (keyword args / typed structs)Guarantees a contract that LLMs can rely on
Explicit outputs (Result, typed errors)Removes ambiguity
Clean abstractions (small DSLs inside the module)Keeps cleverness localized
Composition pipelinesAllows algorithmic elegance without leaking complexity
If you must use metaprogramming or DSLs:
• place them in dedicated folders/modules
• document WHY
Prevents accidental spread of fragile tricks
Write tests like it’s a mini‑frameworkTests act as a safety net for both humans and LLMs

Not because “short is bad”, but because compressed code is harder to modify safely. LLMs refactor structure, not intent. DRY is a guideline, not a religion.

TL;DR

  • Cleverness belongs inside – where it models the domain, reduces cognitive load, and creates stable abstractions.
  • Boring, explicit code belongs outside – stable signatures, clear contracts, regular structure.

When we respect this boundary, we get:

  • code that is easy for humans and LLMs to navigate,
  • safe refactorability,
  • and the joy of building beautiful, creative solutions.

Repeat intent if it prevents misinterpretation.

If coding joy came from:

  • writing every line by hand
  • showing off syntactic wizardry
  • squeezing 10 ideas into 1 line

…then yes, some of that fades.

But the deeper joy remains — and grows:

  • modeling domains
  • designing APIs
  • creating abstractions
  • reducing complexity
  • making systems resilient

LLMs don’t replace that. They amplify it.

“Prefer boring over clever” is incomplete advice.

A better version is:

  • Prefer boring where change happens often.
  • Prefer clever where meaning lives.

Or, simply:

Clever inside, boring outside.

That’s clean code for humans.

That’s clean code for LLMs.

And it keeps the joy of coding alive.

Back to Blog

Related posts

Read more »

𝗗𝗲𝘀𝗶𝗴𝗻𝗲𝗱 𝗮 𝗣𝗿𝗼𝗱𝘂𝗰𝘁𝗶𝗼𝗻‑𝗥𝗲𝗮𝗱𝘆 𝗠𝘂𝗹𝘁𝗶‑𝗥𝗲𝗴𝗶𝗼𝗻 𝗔𝗪𝗦 𝗔𝗿𝗰𝗵𝗶𝘁𝗲𝗰𝘁𝘂𝗿𝗲 𝗘𝗞𝗦 | 𝗖𝗜/𝗖𝗗 | 𝗖𝗮𝗻𝗮𝗿𝘆 𝗗𝗲𝗽𝗹𝗼𝘆𝗺𝗲𝗻𝘁𝘀 | 𝗗𝗥 𝗙𝗮𝗶𝗹𝗼𝘃𝗲𝗿

!Architecture Diagramhttps://dev-to-uploads.s3.amazonaws.com/uploads/articles/p20jqk5gukphtqbsnftb.gif I designed a production‑grade multi‑region AWS architectu...