43 min read

AI 시대의 좋은 code, 좋은 codebase, 좋은 engineer

Code Writing Is No Longer the Bottleneck

AI 시대의 좋은 code, 좋은 codebase, 좋은 engineer

부제: Clean Code에서 machine-enforced constraints와 formal verification까지
기준 시점: 2026년 여름까지 공개된 연구·engineering practice를 반영한 synthesis
관점: large-scale software engineering, coding agents, systems programming


들어가며: 이제 “좋은 code”를 다시 정의해야 한다

Software engineering에서 “좋은 code”라는 말은 오랫동안 인간을 전제로 했다. 이름은 사람이 읽기 쉬워야 했고, function은 사람이 한 번에 이해할 수 있어야 했으며, module은 사람이 내부 구현을 몰라도 사용할 수 있어야 했다. Clean Code, Refactoring, Parnas의 information hiding, Ousterhout의 deep module, Rich Hickey의 simplicity 같은 서로 다른 전통도 결국 비슷한 문제를 다뤘다.

미래의 maintainer가 지금의 author가 알고 있는 모든 것을 다시 추론하지 않아도 안전하게 change할 수 있게 하라.

2026년에는 이 문장에서 maintainer의 의미가 달라지고 있다. Human engineer뿐 아니라 coding agent도 repository의 주요 reader이자 writer가 되기 시작했다. Agent는 file을 검색하고, symbol을 따라가고, test를 실행하고, compiler error를 읽고, 여러 차례 수정하며 codebase를 evolve시킨다. 어떤 project에서는 사람이 직접 source code를 거의 쓰지 않는 수준까지 갔다.

이 변화는 “AI가 code를 잘 쓴다”는 사실보다 더 큰 질문을 만든다.

Code writing이 더 이상 scarce resource가 아닐 때, 무엇이 좋은 software를 만드는가?

이 글의 주장은 다음과 같다.

  1. 좋은 code의 목적은 여전히 changeability다.
  2. 다만 change의 비용을 이제 human cognitive load만으로 측정할 수 없다.
  3. Agent에게는 context token, search entropy, inference, tool call, repair iteration도 maintenance cost다.
  4. 따라서 좋은 codebase는 human-readable일 뿐 아니라 agent-legible하고 machine-verifiable해야 한다.
  5. 중요한 design knowledge는 prose보다 type, module, schema, lint, test, property, verifier로 내려갈수록 강해진다.
  6. Large codebase에서 가장 위험한 것은 visible compile error가 아니라 latent semantic error가 repository precedent로 살아남는 것이다.
  7. LLM이 implementation과 proof labor를 싸게 만들면서 formal methods의 economics도 바뀌고 있다.
  8. 앞으로 Senior SWE의 핵심 역량은 code를 많이 쓰는 것보다 semantic intent를 적절한 executable constraint로 바꾸는 것에 가까워질 가능성이 크다.
  9. 그리고 인간에게 좋은 code와 agent에게 optimal한 code가 정말 같은지는 아직 열린 연구 문제다.

결국 이 글은 Clean Code를 버리자는 이야기가 아니다. 오히려 그 안에서 오래 살아남을 insight와, 인간 중심 환경에서만 타당했던 heuristic을 분리해 agentic software engineering의 언어로 다시 쓰는 것에 가깝다.


1. Code를 쓰는 비용이 사라지고 있다

전통적인 software development의 비용을 아주 거칠게 나누면 다음과 같다.

C_{\text{software}}
=
C_{\text{intent}}
+
C_{\text{design}}
+
C_{\text{implementation}}
+
C_{\text{verification}}
+
C_{\text{integration}}
+
C_{\text{maintenance}}

과거에는 implementation 자체가 큰 비용이었다. Syntax를 쓰고, API를 찾고, boilerplate를 만들고, compiler error를 하나씩 고치고, 비슷한 code를 복사해 조정하는 일에 많은 engineer-hour가 들어갔다. 그래서 좋은 engineering practice 중 상당수는 code production이 비싼 세계에서 형성되었다.

LLM coding agent는 이 비용 구조를 바꾸고 있다.

OpenAI는 2026년 2월 공개한 agent-first engineering experiment에서 작은 human team이 Codex를 사용해 약 5개월 동안 사람이 직접 작성한 source line 없이 million-LOC 규모의 internal product를 만들었다고 보고했다. Application logic뿐 아니라 test, CI, docs, observability, internal tooling까지 agent가 작성했고, 사람의 주된 역할은 repository environment와 feedback loop를 설계하는 것이었다.[1]

이 사례를 “이제 모든 software를 agent가 알아서 만든다”는 증거로 해석할 필요는 없다. 더 중요한 lesson은 따로 있다.

Code production throughput이 크게 올라가면 human attention과 acceptance capacity가 새로운 scarce resource가 된다.

Agent가 하루에 사람이 일주일 걸리던 양의 code를 만들 수 있는데 review, test, integration, architecture reasoning이 그대로라면 software delivery는 빨라지지 않는다. 단지 더 많은 unreviewed state가 repository에 쌓일 뿐이다.

여기서 첫 번째 역설이 생긴다.

Code를 싸게 쓸 수 있을수록, 쓸 필요 없는 code를 만들지 않는 능력과 잘못된 code를 빠르게 reject하는 능력이 더 중요해진다.

과거에는 speculative abstraction을 직접 구현하는 비용이 어느 정도 brake 역할을 했다. Agent는 그 brake가 없다. 새로운 framework, wrapper, helper, generic layer, configuration surface를 몇 초 만에 만들 수 있다. 따라서 YAGNI, simplicity, small blast radius 같은 원칙은 code generation 시대에 약해지는 것이 아니라 오히려 중요해질 수 있다.

하지만 이 변화의 핵심을 단순히 “사람은 이제 architect가 된다” 정도로 설명하면 부족하다. Architecture 자체도 agent가 상당 부분 생성할 수 있기 때문이다.

더 근본적인 변화는 이것이다.

\text{Software engineering}
:
\text{production of code}
\longrightarrow
\text{governance of admissible program states}

무슨 code를 써야 하는가보다 어떤 code가 repository에 살아남아도 되는가가 더 중요한 문제가 된다.


2. 우리가 왜 그런 code를 “좋은 code”라고 불렀는가

AI 시대의 code style을 논하기 전에, 인간이 왜 지금까지 특정한 형태의 code를 선호했는지부터 다시 볼 필요가 있다.

Clean Code는 meaningful naming, focused function, duplication 회피, test discipline 같은 local practice를 강조했다. Parnas의 information hiding은 module을 execution phase가 아니라 숨겨야 할 design decision을 기준으로 나누어야 한다고 주장했다. Ousterhout은 simple interface 뒤에 많은 complexity를 숨기는 deep module을 강조했다. RefactoringWorking Effectively with Legacy Code는 behavior를 붙잡은 상태에서 작은 safe change를 반복하는 법을 다뤘다.

표면적으로는 서로 다른 조언이지만 한 문장으로 압축할 수 있다.

한 change를 위해 다시 추론해야 하는 information의 양을 줄여라.

좋은 code의 제1 목적을 readability라고만 부르면 조금 부족하다. 잘 읽히지만 future change가 위험한 code도 존재한다. 더 근본적인 목적은 changeability다.

\text{Good code}
\approx
\text{low cost of correct future change}

Readability는 그것을 위한 중요한 수단이다.

2.1 Complexity는 line 수보다 관계의 수에서 생긴다

Software가 어려운 이유는 line이 많아서만은 아니다. 한 function의 의미가 다음에 의존한다고 생각해보자.

  • global configuration
  • hidden mutable state
  • initialization order
  • lock ordering
  • callback side effect
  • retry policy
  • cache coherence rule
  • external schema
  • build flag
  • undocumented caller convention

이 function이 20 lines밖에 안 되어도 실제 의미를 이해하려면 repository 여러 곳을 읽어야 한다.

반대로 80-line function이 explicit input을 받고, local state만 사용하고, behavior가 한 곳에서 닫혀 있다면 오히려 이해하기 쉬울 수 있다.

따라서 좋은 design의 목표는 단순한 textual smallness가 아니다.

\boxed{\text{semantic locality}}

즉 중요한 meaning이 가능한 한 가까운 곳에서 복원되어야 한다.

2.2 “Small function”보다 coherent function

Clean Code의 small-function heuristic은 유용하지만 절대 규칙은 아니다. Function을 나눌 때마다 새로운 interface가 생긴다.

  • 이름
  • parameter
  • return type
  • precondition
  • side effect
  • error behavior

좋은 split은 이 interface를 통해 complexity를 숨긴다. 나쁜 split은 단지 jump point만 늘린다.

handle()
  → validate()
     → should_validate()
        → is_valid()

각 function을 따라가야만 의미가 보인다면 function 수가 늘었을 뿐 abstraction은 깊어지지 않았다.

Agent 시대에는 이 비용이 더 명시적이다. Function hop은 symbol search, file open, context token, reasoning step이 될 수 있다.

따라서 더 좋은 규칙은 다음과 같다.

Optimize semantic locality, not function length.

2.3 DRY는 syntax가 아니라 knowledge에 적용해야 한다

두 code block이 비슷하다는 이유만으로 abstraction을 만들면 서로 독립적으로 변해야 할 policy를 하나로 묶을 수 있다. 반대로 하나의 business rule이 여섯 곳에 복사되어 있다면 미래의 change가 서로 다른 semantics를 만들 가능성이 높다.

따라서 DRY는 다음처럼 읽는 편이 낫다.

Do not duplicate semantic authority.

Textual duplication은 때로 허용할 수 있다. Semantic duplication은 훨씬 위험하다.

이 distinction은 agent 시대에 특히 중요하다. Agent는 반복 code를 쓰는 비용이 거의 없기 때문에 superficial duplication은 더 자주 생긴다. 동시에 repository의 기존 pattern을 강하게 모방하므로 잘못 복제된 semantic decision은 빠르게 증식할 수 있다.

2.4 Naming은 compressed design이다

좋은 이름은 단순한 decoration이 아니다.

User
UserId
AuthenticatedUser
UserSession
UserRepository

처럼 vocabulary가 안정된 repository에서는 identifier 자체가 domain ontology 역할을 한다.

반대로 같은 concept을 User, AccountHolder, ProfileOwner, Customer처럼 섞어 부르면 인간도 혼란스럽지만 agent는 매 task마다 이 관계를 다시 추론해야 한다.

Naming은 따라서 세 가지 일을 한다.

  1. Human에게 mental model을 준다.
  2. Search system에 lexical index를 준다.
  3. Agent에게 local semantic prior를 준다.

Agent 시대에는 “descriptive”만으로 부족하다.

Names should be globally consistent, semantically discriminative, and searchable.

2.5 Deep module은 cognitive compression boundary다

deep module의 핵심은 implementation이 작다는 것이 아니다.

public interface:     300 tokens
implementation:    20,000 tokens

인 module이 있어도 caller가 300-token contract만 알고 안전하게 사용할 수 있다면 좋은 abstraction이다.

Human에게 이것은 cognitive compression이다.

Agent에게는 더 직접적으로 context compression이다.

\boxed{
\text{deep module}
\approx
\text{semantic compression boundary}
}

좋은 interface는 future maintainer가 implementation을 retrieve하지 않아도 되게 한다. 반대로 shallow wrapper가 많은 codebase에서는 agent가 작은 symbol들을 계속 따라가며 전체 context를 복원해야 한다.

이 지점에서 전통적인 software design과 agentic software engineering은 생각보다 많이 겹친다.


3. Reader가 AI가 되면 maintenance cost는 어떻게 달라지는가

Coding agent는 사람과 똑같이 code를 읽지 않는다. 하지만 “그래서 human-readable code는 이제 필요 없다”고 결론내리기도 어렵다.

Agent는 대략 다음 자원을 반복적으로 소비한다.

  • repository tree 탐색
  • lexical/symbol search
  • file read
  • context window
  • internal reasoning
  • tool invocation
  • build/test execution
  • compiler/error feedback
  • repair turn
  • context compaction 또는 reset

따라서 agent가 task 하나를 수행하는 비용은 source LOC만으로 설명되지 않는다.

C_{\text{task}}
\approx
\sum_i
\left(
T_{\text{context},i}
+
\alpha T_{\text{reasoning},i}
+
\beta T_{\text{generation},i}
\right)
+
\gamma N_{\text{tool}}
+
\delta N_{\text{repair}}

여기서 T는 token 또는 그에 대응하는 inference compute의 proxy로 생각할 수 있다.

3.1 짧은 code가 반드시 싼 code는 아니다

다음 두 API를 비교해보자.

fn advance(a: u64, b: u64) -> u64;
fn advance_consumer_offset(
    current: ConsumerOffset,
    consumed: ByteCount,
) -> ConsumerOffset;

두 번째가 source token은 더 많다. 그러나 첫 번째를 올바르게 사용하려면 surrounding code, caller, documentation을 반복해서 읽어야 할 수 있다.

즉 static verbosity를 조금 지불해서 dynamic inference를 절약할 수 있다.

\text{source tokens} \uparrow
\quad\text{but}\quad
\text{total maintenance inference} \downarrow

그래서 semantic density는 단순히 적은 token에 많은 syntax를 우겨넣는 것으로 정의하면 안 된다.

더 유용한 정의는 다음에 가깝다.

\boxed{
D_{\text{semantic}}
=
\frac{
\text{task-relevant semantic information available locally}
}{
\text{tokens and reasoning needed to recover it}
}
}

3.2 Clean code가 실제 agent cost를 줄이는가

2026년 5월 Trivedi와 Schmitt는 이 질문을 controlled minimal-pair experiment로 직접 측정했다.[3]

연구진은 architecture, dependency, external behavior는 같지만 code cleanliness만 다른 repository pair를 만들고, 33개 task에 대해 Claude Code를 총 660회 실행했다.

결과는 흥미로웠다.

  • final task pass rate는 clean/messy 사이에 의미 있는 차이가 없었다.
  • cleaner code에서는 token use가 약 7–8% 감소했다.
  • 이미 읽은 file을 다시 여는 file revisitation은 약 34% 감소했다.

즉 적어도 이 setting에서는 clean code가 model capability 자체를 크게 올리지는 않았다.

대신 같은 capability를 더 싼 inference trajectory로 사용하게 했다.

이 distinction은 중요하다.

Code quality는 agent의 capability lever가 아닐 수 있지만, compute-efficiency lever일 수 있다.

특히 file revisitation 감소는 흥미롭다. Agent가 첫 탐색에서 repository의 semantic map을 더 잘 만들고, 이전 결정을 다시 확인하기 위해 backtracking하는 일이 줄었다는 신호일 수 있기 때문이다.

3.3 Human-friendly와 agent-friendly는 많이 겹치지만 동일하다고 가정하면 안 된다

현재 model은 인간이 작성한 code를 대량으로 학습했다. 따라서 인간이 발전시킨 convention을 잘 사용하는 것이 놀랍지 않다.

human constraints
  → human-designed languages
  → human conventions
  → human repositories
  → training data
  → coding model

여기에는 path dependence가 있다.

현재 agent가 snake_case, small module, standard library idiom을 잘 다루는 것이 그 형식이 어떤 universal machine optimum이기 때문인지, 아니면 training distribution에 많이 존재하기 때문인지는 분리하기 어렵다.

따라서 현재 evidence를 가장 조심스럽게 해석하면 다음과 같다.

Human-friendly code는 현재 coding agents에도 상당히 좋은 code다. 그러나 human optimum이 agent optimum이라는 증거는 아직 없다.

이 질문은 뒤에서 다시 돌아오게 된다.


4. 100k–1M LOC에서 진짜 문제는 latent error다

함수 하나를 생성하는 benchmark에서는 compile error와 wrong answer가 주요 실패다.

Large codebase에서는 더 위험한 실패가 있다.

plausible local decision
        ↓
compiles
        ↓
tests miss it
        ↓
merged
        ↓
becomes repository precedent
        ↓
future agents imitate it
        ↓
becomes de facto convention

이것은 단순 bug accumulation이 아니다.

Error amplification이다.

Agent는 repository 안의 existing pattern을 local training distribution처럼 사용한다. 따라서 하나의 bad pattern이 merge되면 이후 agent가 그것을 “이 repository에서 이렇게 하는 것이 맞다”고 해석할 수 있다.

4.1 Compile error는 좋은 error다

Large codebase에서는 이런 식의 failure가 오히려 다루기 쉽다.

type mismatch
borrow error
failed test
lint violation

문제가 바로 observable하기 때문이다.

더 위험한 것은:

compiles
looks idiomatic
passes current tests
semantically wrong

이다.

따라서 중요한 metric은 단순한 first-try generation accuracy가 아니다.

P(\text{incorrect state remains admissible})

또는 장기적으로:

P(\text{latent error survives } k \text{ future changes})

같은 개념이 더 중요해진다.

4.2 Architectural entropy

Codebase가 evolve하면서 다음이 생길 수 있다.

  • 같은 역할을 하는 abstraction이 여러 개 생김
  • error handling convention이 갈라짐
  • duplicate schema가 생김
  • boundary를 우회하는 shortcut이 canonical pattern처럼 퍼짐
  • old API와 new API가 병존함
  • naming ontology가 무너짐
  • dependency direction이 흐려짐

이것을 넓게 architectural entropy라고 부를 수 있다.

Agent throughput이 높아질수록 entropy도 빠르게 늘 수 있다. 따라서 cleanup은 더 이상 사람이 가끔 하는 미화 작업이 아니라 continuous repository control loop가 될 수 있다.

OpenAI의 agent-first experiment에서도 비슷한 문제가 관찰되었다. Agent가 existing pattern을 적극적으로 따르기 때문에 좋지 않은 pattern도 복제했고, 팀은 repository-level “golden principles”와 mechanical checks를 만들고 cleanup agent가 반복적으로 violations를 정리하게 했다.[1]

이것은 일종의 entropy garbage collection이다.

4.3 Change amplification은 agent에서도 핵심 metric이다

어떤 semantic change 하나가 2 files를 고치는 것으로 끝나는 codebase와 40 files를 건드려야 하는 codebase를 비교해보자.

사람에게 후자가 어려운 이유는 obvious하다. Agent에게도 마찬가지다.

  • 더 많은 files를 찾아야 한다.
  • 더 많은 context를 읽어야 한다.
  • 일부 caller를 놓칠 확률이 올라간다.
  • partial migration state가 생긴다.
  • test surface가 커진다.
  • repair iteration이 늘어난다.

따라서 classic software design의 change amplification은 agent era에 오히려 더 직접적인 cost metric으로 바뀐다.

좋은 codebase는 agent가 더 똑똑해서 안전한 것이 아니라, 한 change가 작은 semantic neighborhood에서 닫히도록 설계되어 있어서 안전한 것일 수 있다.


5. Codebase 자체가 과거의 decision을 기억하게 하라

100만 LOC를 어떤 한 human이나 agent의 working memory에 넣는 것은 좋은 전략이 아니다.

더 scalable한 접근은 repository 자체가 design decision을 기억하게 만드는 것이다.

이 관점에서 type system, module system, schema, lint는 documentation 이상이다.

\boxed{
\text{machine-enforced persistent repository memory}
}

5.1 Prose보다 type이 강한 이유

다음 rule을 생각해보자.

UserIdDeviceId를 섞으면 안 된다.

Documentation에 이렇게 적을 수 있다.

Do not pass a device ID where a user ID is expected.

하지만 다음 representation에서는 compiler가 아무것도 모른다.

type UserId = u64;
type DeviceId = u64;

반면:

struct UserId(u64);
struct DeviceId(u64);

로 만들면 잘못된 state의 일부가 아예 compile되지 않는다.

사람이 convention을 기억할 필요도, future agent가 docs를 retrieve할 필요도 줄어든다.

5.2 Ownership protocol도 representation이 될 수 있다

DMA buffer의 lifecycle을 예로 들어보자.

CPU owns page
    ↓ submit
NIC owns page
    ↓ completion
CPU owns page

이것을 prose로만 관리할 수도 있다.

Rust에서는 일부를 type state로 옮길 수 있다.

struct CpuOwned;
struct NicOwned;

struct Page<State> {
    frame: Frame,
    _state: PhantomData<State>,
}

fn submit(page: Page<CpuOwned>) -> Page<NicOwned>;
fn complete(page: Page<NicOwned>) -> Page<CpuOwned>;

이제 future agent가 과거 design meeting을 몰라도 transition surface를 통해 상당한 contract를 복원할 수 있다.

중요한 건 PhantomData를 많이 쓰자는 이야기가 아니다.

핵심 원칙은 다음이다.

High-cost invalid state를 가장 싼 enforceable representation으로 제거하라.

5.3 Maximum type cleverness가 아니라 minimum sufficient type precision

Strong type system을 좋아한다고 모든 semantic nuance를 type level에 올리면 다른 complexity가 생긴다.

  • type-level machinery
  • generic diagnostics
  • trait-resolution complexity
  • lifetime propagation
  • more difficult local edits
  • larger agent repair search space

따라서 목표는 maximum type safety를 추상적으로 극대화하는 것이 아니다.

\boxed{
\text{minimum sufficient machine-enforced precision}
}

비싼 bug class는 type으로 막고, trivial local detail은 plain code로 둔다.

같은 이유로 maximum generality도 항상 좋은 목표가 아니다.

Internal function 하나가 &Path만 받으면 충분한데 무조건 impl AsRef<Path>로 generic하게 만드는 것이 agent-first codebase에 더 좋은지는 별도 문제다. Generality에는 trait resolution, diagnostics, compile time, admissible behavior surface라는 cost가 있다.

따라서 agent-native code에서는 다음 원칙도 중요할 수 있다.

Minimum sufficient generality.

5.4 Language choice는 training prior와 verification leverage의 trade-off다

LLM 관점에서 programming language는 두 축으로 볼 수 있다.

Generation prior

Training data와 public ecosystem에 그 language와 idiom이 얼마나 많이 존재하는가.

Verification leverage

잘못된 program을 compiler, type checker, verifier가 얼마나 빨리 reject하고 actionable feedback을 주는가.

Python은 generation prior가 매우 강하지만 semantic contract의 많은 부분이 language 밖에 있다.

Rust는 Python보다 generation prior가 작을 수 있지만 ownership, type, Result, exhaustive matching, Send/Sync 등의 machine-checkable surface가 넓다.

Haskell은 더 극단적이다. Pure functional representation과 strong type system은 많은 state-space를 제거하지만 ecosystem exposure와 advanced idiom의 learning cliff가 있다.

TypeScript/Java는 large corpus와 static checking의 중간지점이다.

따라서 language “실력”을 one-shot generation으로만 평가하면 corpus-rich language가 유리할 수 있다. 그러나 long-running agent loop에서는 질문이 달라진다.

\text{generate}
\rightarrow
\text{compiler}
\rightarrow
\text{diagnostic}
\rightarrow
\text{repair}

이 closed loop에서는 verifier quality가 중요한 생산성 변수다.

5.5 Rust가 쉽다는 뜻은 아니다

Rust-SWE-bench는 34개 Rust repository에서 500개의 real-world issue를 모아 repository-level issue resolution을 평가했다.[4]

연구진이 관찰한 대표적인 어려움은 두 가지였다.

  1. repository-wide structure를 이해하는 것
  2. Rust의 strict type/trait semantics를 지키는 것

즉 strong semantics가 first attempt generation을 쉽게 만들어주는 것은 아니다.

오히려 candidate가 더 많이 compiler에 reject될 수 있다.

하지만 large codebase에서 중요한 질문은 이것이다.

candidate를 처음부터 쉽게 만들 수 있는가, 아니면 잘못된 candidate가 repository에 살아남기 어려운가?

두 목표는 다르다.

Rust의 큰 장점은 후자일 가능성이 있다.

P(\text{first attempt compiles})
\quad\text{보다}\quad
P(\text{bad state survives})

를 중요하게 볼수록 strong static semantics의 가치가 올라간다.


6. 그러나 type system은 specification을 모른다

여기까지 읽으면 “그렇다면 모든 것을 strong type system으로 옮기면 되는가?”라는 질문이 생긴다.

그렇지는 않다.

sort :: Ord a => [a] -> [a]
sort _ = []

는 아주 잘 type-check할 수 있다.

Rust에서도:

fn sort(xs: Vec<i32>) -> Vec<i32> {
    Vec::new()
}

은 compile된다.

Type system은 Vec<i32> -> Vec<i32>라는 contract는 알지만 다음은 모른다.

  • output이 sorted인가?
  • input과 같은 element multiset을 보존하는가?
  • element를 잃지 않는가?

즉:

\text{well typed}
\neq
\text{semantically correct}

Type system은 specification의 일부를 encode할 뿐이다.

그렇다면 다음 질문은 자연스럽다.

어떤 invariant를 어느 verification layer에 놓을 것인가?


7. Verification은 하나의 tool이 아니라 ladder다

Software correctness를 binary하게 “formal verification을 했는가?”로 나누는 것은 실용적이지 않다.

대신 verification strength를 연속적인 ladder로 볼 수 있다.

prose guidance
    ↓
types / schemas
    ↓
lint / structural checks
    ↓
unit / integration tests
    ↓
property-based testing
    ↓
fuzzing / differential testing
    ↓
model checking
    ↓
SMT-backed program verification
    ↓
interactive theorem proving / proof kernel

모든 invariant를 가장 아래의 강한 layer에 넣을 필요는 없다.

좋은 engineering은 가장 싼 충분한 oracle을 고르는 일이다.

7.1 Deterministic check가 가능하면 LLM에게 판단시키지 않는다

예를 들어:

coreadapters를 import하면 안 된다.

이것을 LLM reviewer에게 매번 물어볼 수 있다.

더 좋은 방법은 import/dependency checker로 항상 reject하는 것이다.

UserIdDeviceId가 섞이면 안 된다.

Naming guide에 적는 것보다 type으로 구분하는 편이 강하다.

serialization 후 parse하면 같은 normalized object가 나와야 한다.

Example test 몇 개보다 property test가 더 넓은 input space를 본다.

implementation A와 reference B의 observable behavior가 같아야 한다.

Differential testing이 강력하다.

모든 possible small state에서 allocator free list와 allocated set이 겹치지 않아야 한다.

Model checking이 적합할 수 있다.

특정 mathematical pre/postcondition이 모든 execution에서 성립해야 한다.

SMT-backed verifier나 theorem prover의 영역이다.

핵심은 다음이다.

\boxed{
\text{Reasoning that can be externalized should not be repeatedly paid for inside the LLM.}
}

Verification은 reliability mechanism이면서 inference offload mechanism이다.

7.2 Property-based testing은 중요한 bridge다

Example-based test는 다음처럼 특정 점을 확인한다.

f(3) == 5
f(7) == 9

Property는 더 높은 semantic level을 표현한다.

deserialize(serialize(x)) == normalize(x)
sorted(output)
AND
multiset(output) == multiset(input)
allocate → free 이후 capacity가 복구된다

LLM은 example test 생성뿐 아니라 candidate property를 제안하는 데도 유용하다. 중요한 점은 property가 agent의 prose claim을 executable signal로 바꾼다는 것이다.

7.3 Differential oracle의 가치

Compiler를 만들 때 “정답 compiler”가 이미 있다면 새 compiler의 output을 GCC/Clang과 비교할 수 있다.

Database migration이면 old/new system을 shadow-run할 수 있다.

Protocol implementation이면 reference implementation과 packet trace를 비교할 수 있다.

LLM 시대에는 implementation generation이 싸기 때문에 oracle engineering의 relative value가 올라간다.

정답을 생성하는 방법보다 정답인지 판정하는 방법이 더 scarce해질 수 있다.


8. Formal verification의 economics가 달라질 수 있다

Formal methods가 historically 비쌌던 것은 specification이 어려워서만이 아니다.

대략 다음 비용이 있었다.

C_{\text{verified}}
=
C_{\text{semantic design}}
+
C_{\text{formalization}}
+
C_{\text{proof}}
+
C_{\text{implementation}}
+
C_{\text{maintenance}}

semantic design은 원래 software engineering에서도 비싼 일이었다.

무엇이 legal state인지, failure semantics가 무엇인지, concurrency ordering이 어떤지, ownership이 어떻게 이동하는지는 formal methods가 없어도 누군가는 결정해야 한다.

Formal verification의 추가 비용은 그 design을 formal language로 옮기고 proof를 만들고 유지하는 데 있었다.

LLM이 바꾸는 지점은 여기다.

C_{\text{formalization}},
C_{\text{proof}},
C_{\text{implementation}}
\downarrow

이 세 항이 크게 싸진다면 verification의 economics가 달라질 수 있다.

8.1 Specification은 여전히 어려우나 “새로운 bottleneck”은 아니다

LLM이 완벽한 proof를 쓸 수 있게 되어도 잘못된 specification을 증명하면 잘못된 system을 완벽하게 verify할 뿐이다.

예를 들어 은행 withdrawal spec이:

ensures new_balance = old_balance - amount

만 있고 amount <= old_balance가 없다면 negative balance를 허용할 수 있다.

따라서:

P(\text{implementation satisfies spec})

를 1에 가깝게 만드는 것만으로 부족하다.

P(\text{spec captures intent})

도 중요하다.

하지만 이것은 formal verification 때문에 새롭게 생긴 문제가 아니다. Requirement와 design intent를 정확히 결정하는 일은 원래 Senior engineering의 비싼 부분이었다.

달라지는 것은 그 decision을 machine-enforced artifact로 내리는 marginal cost다.

8.2 LLM + verifier loop가 이미 현실적인 영역으로 들어오고 있다

Microsoft Research의 VeriStruct는 Verus를 대상으로 single function이 아니라 data-structure module 수준의 AI-assisted verification을 시도했다.[5]

Planner가 abstraction, type invariant, specification, proof code generation을 orchestrate하고 repair stage가 verification error를 수정한다. 평가한 11개 data-structure module 중 10개를 완전히 verify했고, 전체 129개 function 중 128개가 verification에 성공했다.

이 결과를 “Rust system 전체를 자동 증명할 수 있다”고 읽으면 과장이다.

그러나 다음 사실은 중요하다.

LLM ↔ formal verifier repair loop가 toy theorem을 넘어 module-scale program verification에서도 practical signal을 보이기 시작했다.

2026년 7월에는 더 극단적인 결과도 나왔다. Harnessing Code Agents for Automatic Software Verification은 general coding agent를 Coq/Lean harness에 넣고 proof kernel의 acceptance를 hard gate로 사용했다.[6] 연구 대상에서 Iris core의 4,257 lemmas와 Rust standard-library verification 관련 217 lemmas를 모두 자동으로 처리했다고 보고했다.

이 역시 “새 software의 specification을 자동으로 정확히 만드는 문제”와는 다르다. 주어진 theorem을 proof하는 문제다.

그러나 architecture 관점의 lesson은 강하다.

LLM proposes arbitrary strategy
           ↓
proof assistant kernel
           ↓
ACCEPT / REJECT
           ↺

Agent에게 자유를 많이 주더라도 acceptance surface가 sound하면 결과를 강하게 통제할 수 있다.

8.3 Dafny → Rust보다 verified Rust가 매력적인 이유

다음 pipeline을 생각해보자.

natural-language intent
        ↓
Dafny model / proof
        ↓
LLM rewrites in Rust

여기에는 refinement gap이 있다.

Dafny에서 증명한 program A와 실제 Rust implementation B의 semantics가 같다는 보장이 별도로 필요하다.

Verus 같은 approach가 흥미로운 이유는 specification/proof와 executable Rust를 더 가까운 artifact에 둘 수 있기 때문이다.

Verus/Rust source
   ├── executable code
   └── ghost/spec/proof
          ↓
       verifier

모든 system에 Verus를 쓰자는 뜻은 아니다.

중요한 design principle은:

Proof artifact와 executable artifact 사이의 semantic gap을 가능한 작게 유지하라.

8.4 Formal verification도 verification ladder의 한 층이다

1M LOC 전체를 formal verification하려고 하면 spec/proof codebase 자체가 새로운 maintenance burden이 될 수 있다.

따라서 현실적인 architecture는 hierarchical할 가능성이 높다.

ordinary Rust / language safety
        ↓
critical local contracts
        ↓
model checking / property checking
        ↓
formal verification for high-blast-radius invariants

OS라면 예를 들어 다음은 formal verification 후보가 될 수 있다.

  • allocator invariant
  • capability semantics
  • page ownership transition
  • DMA descriptor lifecycle
  • crash-consistency protocol
  • critical synchronization protocol

반면 CLI formatting, logging glue, routine data conversion까지 모두 theorem prover에 넣을 필요는 없다.

좋은 engineering question은 이것이다.

이 invariant는 README, type, lint, test, property, model checker, proof 중 어디에 살아야 하는가?


9. Harness engineering: prompt에서 executable governance로

초기 LLM coding은 prompt engineering의 문제처럼 보였다.

"더 careful하게 해줘"
"test도 써줘"
"architecture를 지켜줘"

하지만 long-running coding agent에서는 prose instruction만으로 repository integrity를 유지하기 어렵다.

그래서 흐름은 대략 다음처럼 이동했다.

prompt engineering
    ↓
context engineering
    ↓
harness engineering
    ↓
executable governance

9.1 Prompt는 부탁이고 harness는 world다

Prompt는 agent에게 무엇을 하라고 말한다.

Harness는 다음을 결정한다.

  • 무엇을 읽을 수 있는가
  • 무엇을 수정할 수 있는가
  • 어떤 tool을 사용할 수 있는가
  • 어떤 state가 completion으로 인정되는가
  • 어떤 violation이 merge를 막는가
  • 실패하면 어떤 feedback이 돌아가는가

따라서 중요한 repository rule은 가능한 한 prose에서 executable layer로 내려가는 편이 좋다.

"core must not depend on adapters"
        ↓
dependency checker

"do not weaken this invariant"
        ↓
property test / verifier

"do not use raw IDs here"
        ↓
type

"public schema must stay compatible"
        ↓
schema-diff gate

9.2 Map, not manual

OpenAI의 agent-first project에서 큰 AGENTS.md 하나에 모든 rule을 넣는 approach는 실패했다고 보고되었다.[1]

이유는 인간 documentation과 비슷하다.

  • context를 너무 많이 먹는다.
  • 중요도가 평평해진다.
  • stale rule이 쌓인다.
  • verification하기 어렵다.

대신 short map에서 필요한 local documentation과 source-of-truth artifact로 progressive disclosure하는 방식이 더 적합했다.

이것은 code architecture와도 같은 원리다.

좋은 context architecture도 deep module과 같다.

처음에는 작은 interface만 보여주고 필요한 detail을 demand-driven하게 연다.

9.3 Independent evaluation은 semantic 영역에 남는다

모든 것을 deterministic checker로 만들 수는 없다.

예를 들어:

  • UI가 자연스러운가
  • product requirement를 제대로 구현했는가
  • abstraction이 불필요하게 복잡한가
  • user workflow가 coherent한가

같은 질문은 fuzzy하다.

Anthropic은 2026년 long-running application harness에서 generator와 evaluator를 분리해 별도의 QA agent가 running application을 실제로 조작하고 criterion을 평가하게 했다.[2]

핵심 lesson은 “multi-agent가 무조건 좋다”가 아니다.

오히려 더 중요한 lesson은 두 가지다.

  1. 자신이 만든 결과를 자신이 평가하면 leniency가 생길 수 있다.
  2. evaluator의 비용도 model capability에 따라 정당화되어야 한다.

Anthropic은 model이 더 강해지면서 이전에 필요했던 일부 sprint decomposition과 evaluation step을 줄일 수 있었다고 설명한다.[2]

따라서 좋은 harness의 원칙은 복잡한 orchestration을 많이 쌓는 것이 아니다.

\boxed{
\text{minimum scaffolding necessary for the current model and task}
}

Harness component 하나는 사실 다음 가정을 encode한다.

“현재 model은 이 부분을 스스로 reliable하게 처리하지 못한다.”

Model capability가 올라가면 이 가정도 다시 검증해야 한다.

9.4 “Done”을 agent가 선언하게 하지 않는다

Long-running task의 중요한 문제는 premature completion이다.

Agent가:

“대체로 구현된 것 같습니다.”

라고 말하는 것은 weak evidence다.

더 강한 구조는 다음이다.

task contract
    ↓
artifact
    ↓
independent observation
    ↓
acceptance evidence
    ↓
merge

Deterministic test가 가능한 영역에서는 test가 judge다.

Formal property라면 verifier가 judge다.

Subjective requirement라면 independent evaluator와 human spot-check가 남는다.

여기서 핵심은:

\boxed{
\text{Make acceptance expensive to fool.}
}

Generation은 싸고 자유롭게 해도 된다.

Acceptance는 가능한 한 좁고 강하게 만든다.


10. 2026년판 Clean Code: 무엇이 살아남고 무엇이 달라지는가

LLM이 code를 쓴다고 해서 classic clean-code insight가 사라지지는 않는다.

다만 각 rule의 justification이 달라진다.

전통적 rule 인간 중심 의미 Agent-era 재해석
Meaningful names 읽기 쉽다 search key + semantic anchor + repository ontology
Small functions 한 번에 이해 bounded semantic unit, 단 excessive context hop 금지
SRP/cohesion 한 책임 change locality와 blast radius를 줄임
DRY code duplication 제거 semantic authority duplication 제거
Explicit types readability/safety persistent machine-readable knowledge
Comments why 설명 hidden contract를 임시 보존; 가능하면 executable layer로 승격
Tests regression 방지 agent self-repair와 acceptance oracle
Consistency 사람이 익숙함 agent의 local prior와 search entropy를 안정화
Refactoring readability 개선 architectural entropy garbage collection
Style guide 사람 간 통일 mechanical style은 formatter로, semantic vocabulary만 중요

10.1 Naming은 더 중요할 수 있다

Agent는 grep, symbol search, file names, test names를 통해 repository를 탐색한다.

따라서 generic naming:

manager
helper
process
data
common
utils

는 human smell인 동시에 retrieval smell이다.

같은 domain concept에 같은 vocabulary를 쓰는 것은 human readability를 넘어 search-space control이다.

10.2 Small function dogma는 더 약해진다

Agent에게도 giant function은 나쁘다.

하지만 5-line helper 열 개로 쪼개는 것이 항상 좋은 것은 아니다.

각 hop은 context cost다.

따라서:

small enough to reason locally, deep enough to avoid pointer chasing

가 더 좋은 목표다.

10.3 Semantic DRY + syntactic WET

Agent는 10 lines를 반복해서 쓰는 비용을 거의 느끼지 않는다.

따라서 일부 local duplication을 허용해 abstraction을 단순하게 유지하는 것이 나을 수 있다.

하지만 같은 auth rule, error policy, state invariant가 여러 곳에 독립적으로 존재하면 위험하다.

그래서 agent-era DRY는:

\boxed{
\text{DRY knowledge, not necessarily syntax}
}

가 된다.

10.4 Comment는 weaker contract다

이런 comment는 거의 가치가 없다.

// increment count
count += 1;

하지만 다음은 중요하다.

// `ready.store(Release)` publishes all writes to `data`.
// Readers must load `ready` with Acquire before reading `data`.

문제는 comment가 stale할 수 있다는 것이다.

따라서 중요한 repeated rule은 가능하면 다음 방향으로 승격한다.

comment
   ↓
structured contract
   ↓
type/schema
   ↓
test/property
   ↓
formal check

Comment는 실패가 아니라 아직 더 강한 representation으로 내리지 못한 knowledge storage일 수 있다.

10.5 Boring architecture의 가치가 올라간다

Agent는 complex abstraction을 쓰는 비용을 느끼지 않기 때문에 unnecessary cleverness를 쉽게 만든다.

다음은 사람이 쓰기에도, agent가 유지하기에도 비싼 경우가 많다.

  • magic dependency injection
  • implicit control flow
  • macro-heavy DSL
  • generic framework for one use case
  • many equivalent idioms
  • hidden callbacks
  • metaprogramming with poor diagnostics

Agent-first codebase에서는 오히려:

  • explicit
  • predictable
  • canonical
  • low-branching
  • easy-to-check

한 architecture가 강할 수 있다.

좋은 style의 목표가 “elegant”에서 “predictably legible”로 조금 이동하는 것이다.


11. 앞으로 Senior SWE는 무엇을 잘해야 하는가

여기까지의 논의를 “앞으로 engineer는 code를 안 쓴다”로 요약하면 너무 단순하다.

Code를 이해하고 구현하는 능력은 여전히 중요하다. 다만 leverage point가 올라간다.

11.1 Bug를 찾는 것보다 bug class를 없애는 것

Agent가:

UserId ↔ DeviceId confusion

이라는 bug를 만들었다고 하자.

한 번의 review에서 그 line을 찾아 고치는 것도 가치가 있다.

더 scalable한 대응은:

struct UserId(...);
struct DeviceId(...);

로 바꿔 같은 class의 error를 compiler가 reject하게 하는 것이다.

즉 좋은 Senior는 bug fix 뒤에 한 번 더 묻는다.

왜 이 종류의 잘못된 state가 repository에서 admissible했는가?

그리고 답을:

  • type
  • API
  • module boundary
  • lint
  • property test
  • model
  • verifier
  • runtime assertion

중 하나로 바꾼다.

11.2 Error propagation topology를 본다

Junior-ish review가 local correctness를 본다면:

이 loop가 맞나?
이 API call이 맞나?

Senior review는 다음을 더 많이 보게 된다.

이 assumption의 source of truth는 어디인가?
이 API가 evolve하면 stale assumption이 어디 남는가?
이 mistake가 한 번 merge되면 agent가 몇 곳으로 복제할 수 있는가?
이 subsystem의 failure blast radius는 어디까지인가?

latent-error propagation topology를 보는 능력이다.

11.3 모든 line을 읽는 대신 assurance case를 이해한다

Agent throughput이 충분히 높아지면 사람이 모든 generated LOC를 line-by-line 이해한다는 원칙은 scalable하지 않다.

그렇다고 blind merge가 답은 아니다.

Review 대상이 올라간다.

\text{Can I explain every line?}

에서:

\boxed{
\text{Can I explain why this change is safe to accept?}
}

로.

이때 Senior가 이해해야 할 것은:

  • semantic intent
  • public contract
  • changed invariants
  • verification evidence
  • unverified assumptions
  • failure blast radius
  • rollback path

이다.

이것은 source comprehension의 포기가 아니라 assurance-case comprehension이다.

11.4 Senior SWE는 semantic designer + constraint engineer + verification architect가 된다

Future Senior 역할을 하나의 직함으로 줄이면 틀리기 쉽다.

architect, manager, reviewer가 모두 일부만 설명한다.

더 정확하게는 다음 세 역할의 합에 가깝다.

Semantic designer

System이 무엇을 의미해야 하는지 결정한다.

  • legal state
  • user-visible semantics
  • failure policy
  • ownership
  • ordering
  • consistency

Constraint engineer

그 semantic decision을 misuse-resistant representation으로 만든다.

  • type
  • API
  • module
  • schema
  • static rule

Verification architect

어떤 assumption을 어디에서 어떻게 확인할지 결정한다.

  • test
  • property
  • fuzz
  • model checker
  • formal proof
  • production observability

이 세 가지가 잘 되면 implementation throughput은 agent가 크게 늘려도 repository integrity가 같이 올라갈 수 있다.


12. 인간에게 좋은 code와 AI에게 좋은 code는 정말 같은가

이제 가장 speculative하지만 중요한 질문으로 돌아온다.

현재 coding model이 배운 “좋은 code”는 대부분 인간이 만든 software history에서 왔다.

따라서 우리가 현재 관찰하는 agent preference는 다음의 결과일 수 있다.

human ergonomics
  → language design
  → coding conventions
  → high-quality repositories
  → model training distribution

이제 agent가 repository의 primary writer가 되기 시작하면 새로운 optimization target을 생각할 수 있다.

12.1 Agent-centric objective

Human-centric maintainability는 대략:

M_H
=
\frac{\text{safe changes}}
{\text{human cognitive effort}}

라고 생각할 수 있다.

Agent-centric maintainability는:

M_A
=
\frac{
P(\text{correct semantic change})
}{
\text{inference compute}
+
\text{navigation}
+
\text{repair}
+
\text{verification cost}
}

에 더 가까울 수 있다.

그리고 long horizon에서는 여기에 latent error와 entropy가 들어간다.

Q_{\text{codebase}}
=
\text{correct change throughput}
-
\lambda C_{\text{inference}}
-
\mu C_{\text{repair}}
-
\nu E_{\text{latent}}
-
\rho E_{\text{architecture}}

여기서 정확한 coefficient가 중요한 것은 아니다.

중요한 것은 LOC, cyclomatic complexity, human readability만으로는 agent-era maintainability를 설명하기 부족해진다는 점이다.

12.2 AI-native code는 더 explicit할 수 있다

Human은 concise한 idiom을 좋아할 수 있다.

Agent는 오히려 intermediate semantic state가 named되어 있는 code를 더 싸게 이해할 수 있다.

let validated = validate_requests(requests)?;
let normalized = normalize_requests(validated);
let resolved = resolve_users(normalized)?;

이런 code는 compact pipeline보다 token이 조금 많지만 다음 장점이 있다.

  • intermediate state를 검색할 수 있다.
  • failure location을 localize할 수 있다.
  • test insertion point가 명확하다.
  • agent가 한 stage만 수정하기 쉽다.

따라서 static verbosity와 dynamic inference cost의 trade-off를 실험할 가치가 있다.

12.3 AI-native language는 어떤 성질을 가질까

미래의 agent-optimal PL이 반드시 Rust, Haskell, Python 중 하나일 필요는 없다.

흥미로운 design axis는 다음과 같다.

  • 표현 방법이 너무 많지 않음
  • domain invariant를 type으로 올리기 쉬움
  • escape hatch가 명확하고 제한적임
  • compiler diagnostics가 actionable함
  • incremental build/check가 빠름
  • module boundary가 강함
  • effect/state가 explicit함
  • canonical formatting/idiom이 강함
  • property/formal verification과 자연스럽게 연결됨
  • machine-readable AST/semantic index가 first-class임

즉 인간에게 가장 expressive한 language가 agent에게 가장 좋은 language라는 보장은 없다.

Agent 입장에서는 오히려:

less freedom, stronger local semantics, faster rejection

이 좋은 조합일 수 있다.

12.4 Training distribution도 tune해야 한다

Agent가 잘못된 pattern을 repository에 만들고, future model이 그 repository를 다시 학습한다면 feedback loop가 생긴다.

human code
   ↓
model
   ↓
AI-generated code
   ↓
future training data
   ↓
next model

이 evolution이 저절로 agent-optimal convention으로 수렴한다는 보장은 없다.

Bad pattern도 대량으로 증폭될 수 있다.

따라서 human의 역할 중 하나는 model weight를 직접 fine-tune하는 것보다 넓은 의미의 software ecosystem tuning일 수 있다.

  • golden examples
  • canonical API
  • lint
  • repository policy
  • eval
  • cleanup
  • training/evaluation corpora

를 통해 어떤 code distribution이 살아남는지 선택하는 것이다.


13. 무엇을 연구해야 하는가: longitudinal codebase benchmark

현재 coding benchmark는 대체로 한 issue를 해결하거나 한 function을 생성하게 한다.

하지만 agent-first future에서 더 중요한 질문은 이것이다.

1000번의 sequential change 뒤에도 codebase가 coherent한가?

이를 직접 측정하는 benchmark를 상상해볼 수 있다.

13.1 Human-style vs agent-style repository

동일한 semantics와 test surface를 가진 두 repository를 만든다.

H-style

현재 human best practice를 충실히 따른 conventional implementation.

A-style

Agent maintenance를 위해 의도적으로 설계한다.

  • stable domain vocabulary
  • semantic newtypes
  • strong module boundary
  • local contracts
  • canonical idiom
  • limited generality
  • explicit state transitions
  • machine-readable architecture rules
  • property/verifier integration

그리고 동일 model/harness에 수백~수천 개의 sequential change request를 준다.

13.2 측정해야 할 것

Capability

  • task success
  • escaped defect
  • semantic regression

Inference economy

  • input/output tokens
  • reasoning proxy
  • wall-clock latency
  • tool calls
  • files opened
  • unique files opened
  • file revisitations
  • compile iterations
  • repair iterations

Repository health

  • files touched per semantic change
  • duplicated semantic authority
  • architecture boundary violation
  • number of competing abstractions
  • public API growth
  • dependency growth
  • latent invariant violation

Longitudinal stability

  • performance after 100 / 500 / 1000 changes
  • regression rate trend
  • token cost trend
  • architecture entropy trend
  • recovery cost after injected bad precedent

이렇게 하면 “AI-friendly code란 무엇인가?”를 취향 논쟁이 아니라 empirical PL/SE question으로 만들 수 있다.

13.3 Codebase quality를 compute cache로 보는 관점

이 benchmark에서 가장 흥미로운 hypothesis 중 하나는 이것이다.

잘 규약된 codebase는 agent가 과거의 semantics를 매번 재추론하지 않게 해주는 compute cache다.

Newtype 하나는 몇 token을 더 쓴다.

Module boundary도 boilerplate를 늘릴 수 있다.

Property test와 verifier도 CI compute를 사용한다.

그러나 그것들이 미래 task마다 수천 token의 search와 reasoning을 없앤다면 total system cost는 감소한다.

따라서 software architecture의 경제학을 다음처럼 다시 볼 수 있다.

\text{upfront semantic encoding cost}
\quad\leftrightarrow\quad
\text{future inference avoided}

이것은 인간 시대의 strategic programming과 매우 비슷하다.

단지 future maintenance labor 중 일부가 GPU inference로 바뀌었을 뿐이다.


14. 결론: 좋은 codebase는 기억하고, 거부하고, 설명한다

AI 시대에도 좋은 software의 가장 오래된 원칙은 살아남는다.

Parnas의 information hiding은 여전히 중요하다.

Ousterhout의 deep module도 중요하다.

Clean Code의 naming discipline과 local coherence도 중요하다.

Refactoring의 safe incremental change도 중요하다.

하지만 이 원칙들을 문자 그대로 보존하기보다 왜 유용했는지를 다시 읽어야 한다.

좋은 code는 단순히 사람이 읽기 좋은 code가 아니었다.

좋은 code는 future maintainer가 과거 author의 머릿속을 복원하지 않아도 되는 code였다.

이제 future maintainer에는 agent도 포함된다.

그래서 2026년의 좋은 codebase는 세 가지를 잘해야 한다.

1. 기억한다

Design decision을 local, machine-readable form에 보존한다.

names
types
modules
schemas
contracts

2. 거부한다

중요한 invalid state를 가능한 한 빨리 reject한다.

compiler
lint
tests
properties
model checker
formal verifier

3. 설명한다

모든 것을 prove할 수 없는 곳에서는 human과 agent가 필요한 context를 빠르게 복원할 수 있게 한다.

stable vocabulary
deep interfaces
explicit rationale
observability
structured evidence

이 세 가지가 잘 되면 agent는 더 많은 code를 써도 된다.

중요한 것은 code volume이 아니다.

\boxed{
\text{low inference cost}
+
\text{low latent-error survival}
+
\text{low change amplification}
}

이것이 agent era에서 Clean Code보다 더 넓은 의미의 good software를 정의하는 세 축이 될 수 있다.

그리고 engineer의 역할도 여기에서 나온다.

미래의 Senior SWE는 단순히 agent가 만든 latent error를 남들보다 빨리 눈으로 찾아내는 사람이 아닐 가능성이 크다.

더 높은 leverage는 다음에 있다.

어떤 semantic decision이 중요한지 알아보고, 그것을 미래의 human이나 agent가 다시 추론하지 않아도 되도록 적절한 machine-enforced layer에 encode하는 것.

Code writing이 bottleneck이 아닌 세계에서도 software engineering은 사라지지 않는다.

오히려 더 본질적인 부분만 남는다.

무엇을 의미해야 하는가. 무엇이 잘못된 state인가. 그리고 그 잘못을 system이 어떻게 스스로 거부하게 만들 것인가.


References

이 글은 특정 자료의 요약이 아니라 기존 software-design literature와 2025–2026년 agentic coding / verification 연구를 묶은 synthesis다. 아래는 본문에서 직접 언급하거나 최신 관점을 확인하는 데 사용한 주요 자료다.

  1. Ryan Lopopolo, “Harness engineering: leveraging Codex in an agent-first world.” OpenAI, February 11, 2026.
    https://openai.com/index/harness-engineering/

  2. Anthropic, “Harness design for long-running application development.” March 24, 2026.
    https://www.anthropic.com/engineering/harness-design-long-running-apps

  3. Priyansh Trivedi and Olivier Schmitt, “Does Code Cleanliness Affect Coding Agents? A Controlled Minimal-Pair Study.” arXiv:2605.20049, May 2026.
    https://arxiv.org/abs/2605.20049

  4. Jiahong Xiang et al., “Evaluating and Improving Automated Repository-Level Rust Issue Resolution with LLM-based Agents.” ICSE 2026 / arXiv:2602.22764.
    https://arxiv.org/abs/2602.22764

  5. Chuyue Sun et al., “VeriStruct: AI-assisted Automated Verification of Data-Structure Modules in Verus.” TACAS 2026.
    https://www.microsoft.com/en-us/research/publication/veristruct/

  6. Shuangxiang Kan, Shuanglong Kan, Sebastian Ertel, “Harnessing Code Agents for Automatic Software Verification.” arXiv:2607.06341, July 2026.
    https://arxiv.org/abs/2607.06341

  7. Microsoft Research, Practical System Verification publications, including Verus, AutoVerus, VeriStruct, ExVerus.
    https://www.microsoft.com/en-us/research/project/practical-system-verification/publications/

  8. Kani Rust Verifier, bit-precise model checking for Rust.
    https://github.com/model-checking/kani

  9. John Ousterhout, A Philosophy of Software Design.

  10. David L. Parnas, “On the Criteria To Be Used in Decomposing Systems into Modules.”

  11. Martin Fowler, Refactoring: Improving the Design of Existing Code.

  12. Michael Feathers, Working Effectively with Legacy Code.

  13. Rich Hickey, “Simple Made Easy.”

  14. Robert C. Martin, Clean Code.


짧은 최종 원칙

처음부터 끝까지의 논지를 다시 가장 짧게 압축하면 다음과 같다.

  1. Code generation이 싸질수록 verificationintegration의 relative value가 올라간다.
  2. 좋은 code의 목적은 예쁜 code가 아니라 safe future change다.
  3. Agent에게는 human cognitive load뿐 아니라 token, search, reasoning, repair cost가 존재한다.
  4. 따라서 semantic locality는 human readability와 inference efficiency를 동시에 개선할 수 있다.
  5. Naming은 style이 아니라 ontology이자 search index다.
  6. Deep module은 human cognitive compression이면서 agent context compression이다.
  7. DRY는 textual duplication이 아니라 semantic authority에 적용해야 한다.
  8. 중요한 hidden knowledge는 prose보다 type/schema/checker로 내린다.
  9. Type system은 strong persistent memory지만 full specification은 아니다.
  10. Verification은 test와 formal proof 사이의 ladder다.
  11. Deterministic하게 판단 가능한 것은 LLM에게 judge시키지 않는다.
  12. Formal methods의 historical proof labor가 LLM 때문에 싸질 가능성이 커지고 있다.
  13. Large codebase에서 가장 위험한 것은 visible error보다 latent bad precedent다.
  14. Harness는 agent에게 부탁하는 문서가 아니라 invalid result를 reject하는 environment다.
  15. Harness는 model이 강해질수록 필요 없는 scaffolding을 제거할 수 있어야 한다.
  16. Senior SWE의 leverage는 bug를 직접 고치는 것보다 bug class를 inadmissible하게 만드는 데 있다.
  17. 모든 generated line을 읽는 대신 acceptance evidence와 unverified assumption을 이해하는 review가 중요해질 수 있다.
  18. Human-good code와 agent-good code는 많이 겹치지만 동일하다고 가정하면 안 된다.
  19. AI-native PL/codebase는 minimum sufficient precision, minimum sufficient generality, 빠른 diagnostics를 중요하게 볼 가능성이 있다.
  20. 잘 규약된 repository는 correctness device이면서 미래 inference를 절약하는 compute cache다.