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:
- Clean code for humans and clean code for LLMs are slightly different (human readability vs model ambiguity).
- 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
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
And a validation step:
def validate(input)
return Result.err(:missing_customer) if input.customer_id.nil?
Result.ok(input)
end
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)
}
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
}
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 };
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 };
}
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 };
}
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)