DEV Community

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

Posted on

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

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.

But it doesn’t have to be.

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.

Why clean code for LLMs looks different

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 real problem with “prefer boring over clever”

The phrase is misleading.

It suggests:

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

But cleverness isn’t the enemy.

Misplaced cleverness is.

We need to separate two kinds of cleverness.


Two types of cleverness

1) Structural cleverness (good)

This is the kind that:

  • reduces cognitive load
  • models the domain
  • creates stable abstractions
  • simplifies future change

This is engineering art.

2) Syntactic cleverness (risky)

This is the kind that:

  • compresses logic into tricky expressions
  • uses language edge cases
  • depends on implicit assumptions
  • looks impressive but is fragile

It’s fun for the author, painful for everyone else.

The goal isn’t to ban cleverness - it’s to move it to the right layer.


The guiding principle: Clever inside, boring outside

This is the best reconciliation between:

  • human clean code
  • LLM clean code
  • and the joy of building beautiful things

Outside (public boundary / module API): boring

  • explicit, stable, predictable
  • easy to refactor safely
  • easy for humans and LLMs to navigate

Inside (implementation / private module): clever

  • elegant algorithms
  • strong abstractions
  • creative modeling
  • performance tricks (when needed)

Cleverness becomes a contained asset, not a distributed risk.


Examples: Ruby, Go, TypeScript

Below are three examples where we keep the boundary boring and make the inside elegant.


Ruby: Result as a contract (boring boundary, clever core)

✅ Boring boundary

A 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
Enter fullscreen mode Exit fullscreen mode

This is LLM-friendly:

  • predictable .call
  • explicit input coercion
  • clear pipeline
  • no metaprogramming

✅ Clever core

The joy is in the abstraction: a small Result type.

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
Enter fullscreen mode Exit fullscreen mode

And a validation step:

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

This is “good clever”:

you created a domain-friendly, composable pipeline.

Why this works for humans + LLMs

  • humans get elegance through composition
  • LLMs get explicit contracts and safe refactoring boundaries

Go: boring handler, clever domain

Go pushes you toward explicitness — which is great.

But you can still keep coding joy by designing beautiful internal APIs.

✅ Boring boundary

HTTP handler is predictable and 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)
}
Enter fullscreen mode Exit fullscreen mode

LLM can refactor this safely.

✅ Clever core

The domain service enforces invariants and keeps business logic isolated:

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
}
Enter fullscreen mode Exit fullscreen mode

This is where your joy lives:

  • domain model
  • invariants
  • high cohesion

The key: boring IO, elegant domain

IO is boring; domain is art.


TypeScript: discriminated unions as a joyful contract

TypeScript is perfect for “clean code for LLMs” because types reduce ambiguity.

✅ Boring boundary

We define an explicit domain result:

type Result<T, E> =
  | { ok: true; value: T }
  | { ok: false; error: E };
Enter fullscreen mode Exit fullscreen mode

And the public function signature:

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

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

  return { ok: true, value: order };
}
Enter fullscreen mode Exit fullscreen mode

This is:

  • explicit
  • easy to consume
  • hard to misuse
  • trivial for LLMs to extend safely

✅ Clever core

You get joy by building a 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 };
}
Enter fullscreen mode Exit fullscreen mode

That’s “beautiful coding”:

  • invariant enforcing
  • minimal surface area
  • strong semantics

The actual rule set (works for humans + LLMs)

Here’s a practical standard you can adopt.

1) Boring boundaries

  • stable function signatures
  • keyword args / typed structs
  • explicit outputs (Result, typed errors)

2) Clever cores

  • clean abstractions
  • small DSLs inside the module
  • composition pipelines
  • algorithmic elegance

3) Isolate magic

If you must use metaprogramming or DSLs:

  • place them in dedicated folders/modules
  • document WHY
  • write tests like it’s a mini-framework

4) Prefer explicitness over compression

Not because “short is bad”, but because:

  • compressed code is harder to safely modify
  • LLMs refactor structure, not intent

5) Redundancy is okay if it reduces ambiguity

DRY is not religion.

Repeat intent if it prevents misinterpretation.


LLMs don’t kill creativity — they move it up the stack

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.


Closing thought

“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.

Top comments (0)