From today, painting is dead. - Paul Delaroche
1839년, 화가 폴 들라로슈는 다게레오타이프1로 찍은 사진을 처음 보고 "오늘부로 회화는 죽었다"고 선언했다고 한다2. 눈앞의 인물을 실물처럼 옮겨 그리기 위해 수십 년을 수련한 화가 앞에 그 일을 몇 분 만에 해내는 기계가 나타난 것이다. 이 오래된 일화로 글을 시작하는 이유는 지금 개발자들이 마주한 상황과 놀랍도록 닮아 있기 때문이다.
필자는 3년 전 아름다운 코드에 대하여라는 글을 썼다. 아름다움이란 무엇인지부터 시작해서 아름다운 코드의 조건을 사회적, 신뢰적, 선언적, 선형적이라는 네 가지로 정리하고 그 품질을 유지하기 위한 마음가짐을 이야기했다.
그리고 그로부터 3년 가까이 지났다. 그 사이 모임에서 받는 질문이 바뀌었다. 이제 코드가 어떠해야 하는지 묻는 사람 대신 어떤 에이전트를 쓰는지, 컨텍스트는 어떻게 관리하는지, 리뷰는 어디까지 하는지를 묻는다. 3년 사이에 코드 자체를 두고 오가던 대화가 아닌 코드를 만들어내는 기계를 두고 오가는 대화로 바뀐 것이다.
질문 뿐만 아니라 개발이 재미없다는 말도 자주 듣는다. 이런 말을 하는 사람들은 대체로 코드를 대충 쓰던 사람들이 아니다. 오히려 변수 이름 하나를 두고 진지할 수 있는 코드에 진심이던 사람들일수록 이 말을 한다. AI가 내 일자리를 위협해서 불안하다는 이야기와는 결이 다르다. 이 감정은 불안보다는 상실감에 가깝다.
어쩌면 이러한 시대에 코드가 어떻게 되야한다라는 말은 무의미하다고 느껴질 수 있다. 그런데 정말 그럴까?
코드를 바라보는 세 가지 관점
좋은 코드라는 것은 어떤 것일까? 이에 대해 흔히 가독성이 좋다, 성능이 좋다, 유지보수가 쉽다와 같은 말이 기준으로 언급되지만, 이는 대부분 결과를 묘사할 뿐이라 찜찜한 감이 남는다. 좋고 싫음에 대해서는 굉장히 다양한 관점이 얽히기 때문에 단순한 기준을 제시하기엔 어렵다.
그러니 "좋음"에 대해 이야기하기 전에 관점에 대해서 먼저 생각해보자. 평소 우리는 코드를 어떻게 바라보고 있었을까? 이에 대한 이야기를 시작하기 위해 평범한 코드를 하나 살펴보자. 다음 코드는 주문 할인 금액을 계산하는 함수다.
fun calculateDiscount(order: Order, coupon: Coupon?): Money {
var discount = Money.ZERO
if (coupon != null) {
if (!coupon.isExpired()) {
if (coupon.type == CouponType.RATE) {
discount += order.totalAmount * coupon.rate
} else {
discount += coupon.amount
}
}
}
if (order.user.grade == Grade.VIP) {
discount += order.totalAmount * 0.05
}
return discount.min(order.totalAmount)
}이 코드를 보고 무슨 생각이 들었는가? 만약 위 코드를 리뷰한다면 사람마다 거슬리는 지점이 다를 것이다. 필자의 경험에 따르면 주로 세 가지 관점으로 나뉜다.
첫 번째는 인지에 대한 관점이다. 만약 코드 리뷰를 한다면 다음과 같은 코멘트가 달릴 수 있다.
- if가 세 겹으로 중첩되어 눈이 아래위로 오가야 한다. 따라서 조건을 인지하기가 어렵다.
- 0.05라는 숫자가 무슨 뜻인지 알 수 없다. 상수로 빼서 이름을 붙여야 한다. 이름만으로 설명이 어렵다면 주석을 달아야 한다. 이러한 영역은 보통 가독성과 같은 단어로 표현하지만 조금 더 자세히 말하자면 코드가 사람의 눈과 뇌에 어떻게 읽히는지에 대한 이야기다.
두 번째는 실행에 대한 관점이다. 위 코드를 보면 다음과 같은 부분이 문제로 지적될 수 있다.
- 같은 쿠폰으로 두 주문이 동시에 들어오면 중복 적용이 될 수 있다.
- 할인 금액을 곱셈으로 계산할 때 소수점은 어떻게 처리되는가? 반올림, 올림, 버림 중 어떤 방식으로 처리되는가? 반올림을 한다면 어떤 규칙으로 반올림하는가?
- 만료 확인 중에 예외가 나면 주문은 어떻게 되는가? 예외 처리 로직이 필요하다. 이 외에도 성능이나 보안 결함처럼 런타임에서 벌어지는 일이 실행에 대한 관점이라 할 수 있다.
마지막은 구조에 대한 관점이다.
- 할인 정책이 하드코딩되어 있다. 새 프로모션이 생길 때마다 이 함수에서 수정해야 한다.
- VIP 할인율은 바뀔 수 있을텐데 코드에 있는 것이 맞는가?
- 애초에 할인 계산이 주문 도메인의 책임인가? 이와 같은 관점은 이 코드를 어떻게 찾아갈 수 있을지, 의존성 방향은 어떤지와 같은 문제를 다룬다. 디자인 패턴이나 아키텍처 패턴을 비롯해 코드 오너십과 같은 조직적 결정도 이 관점에 속한다.
이렇게 같은 코드를 놓고도 개발자는 서로 다른 의견을 말할 수 있다. 그리고 각 관점은 다 타당한 근거가 있다. 어떻게보면 우리가 개발 실력이라 부르는 것은 사실 이 세 관점을 오가며 문제를 보는 능력의 총합에 가깝다. 다만, 이 삼분법에 확실한 경계가 있는 것은 아니다. 세 가지 관점은 서로 겹치는 부분이 있을 수 있다.
그리고 코드는 결국 세 관점에 대한 판단의 결과로 존재한다. 그렇다면 판단은 무엇을 기준으로 하는가. 서두에서 미뤄둔 좋음이라는 말을 꺼낼 때가 됐는데, 그 전에 세 관점이 실제로 어떻게 부딪히는지부터 잠깐 보자.
관점이 만나는 곳
세 관점은 따로 놀지 않는다. 오히려 프로그래밍 언어는 이 관점들이 부딪히며 발전했다고 볼 수 있다.
실행에 대한 인지를 떠올려보자. 예를 들어, 파일이 없고 네트워크가 끊기는 런타임의 사건을 사람이 읽고 쓸 수 있는 문장으로 접어 넣으려는 시도는 try-catch 문법으로 나타났다고 볼 수 있다. 동시성도 마찬가지여서 동시에 벌어지는 일을 위에서 아래로 읽히는 문장으로 표현하기 위해 async/await와 같은 것이 탄생했다.
구조에 대한 인지도 마찬가지다. 어떠한 구조를 묶기 위해 클래스나 모듈과 같은 문법이 생겼고, 이에 대한 의존성 관리를 위한 문법이 탄생하기도 했다. 인터페이스나 접근 제어자와 같은 문법도 구조를 인지하기 위해 만들어졌다.
구조와 실행에 대한 관점은 보이는 것이 아니므로 조금 덜 눈에 띄지만 분명히 존재한다. 예를 들어, MSA는 실행에 대한 경계를 네트워크로 나누는 결정이고 데이터 지향 설계3와 같은 기법은 CPU 캐시 적중률이라는 실행의 성질에 맞춰 구조를 배치한다. 다만, 구조와 실행이 만나서 내린 결정은 코드 표면에 잘 드러나지 않는다. 이유는 생각보다 단순하다. 문법이란 애초에 사람을 이해시키려고 만든 장치여서 인지가 참여한 교차만 표현으로 굳는다. 인지가 빠진 자리에서 내려진 결정은 아무 문법도 남기지 않는다. 여기서 말을 하나 정해두자. 사건인 실행도, 추상인 구조도, 머릿속의 인지도 그 자체로는 눈에 보이지 않는다4. 유일하게 눈에 보이는 것은 화면 위의 글자, 곧 **표현(Expression)**이다. 표현은 네 번째 관점이 아니라 세 관점이 전부 새겨지는 표면이다. 이 비대칭은 뒤에서 다시 설명한다.
좋음은 목적에 붙는다
이제 서두에서 미뤄둔 질문으로 돌아가자. 좋은 코드란 무엇인가.
'좋다'라는 말부터 뜯어보자. 철학자 피터 기치는 '좋다'가 보통의 형용사와 다르게 동작한다는 것을 지적했다5. 노란 칼은 노란 물건이기도 하지만, 좋은 칼은 '좋은 물건'이 아니라 칼로서 좋은 것이다. 좋다는 말은 홀로 서지 못한다. 반드시 무엇으로서 좋은지를 물어야 하고, 그 무엇이 목적을 데려온다. 요리용 칼은 재료를 잘 자르면 좋은 칼이다. 정말 그럴까? 손목이 아픈 사람에게는 잘 드는 무거운 칼보다 덜 들어도 가벼운 칼이 좋은 칼이다. 같은 물건이라도 목적이 바뀌면 좋음의 기준이 함께 바뀐다.
그래서 좋음의 판단은 절대적일 수 없다. 어떤 기준이 절대적이라면 그것은 이미 좋고 나쁨의 문제가 아니라 따라야 할 원칙이다. 버그가 없어야 한다는 것은 좋은 코드의 조건이 아니라 코드가 갖춰야 할 기본이다. 문제는 기본을 넘어선 다음부터다. 거기서부터의 물음은 전부 비교다. 중첩 if를 Guard Clause로 펴는 것이 더 좋은가. 이 함수를 둘로 쪼개는 것이 더 좋은가. 추상화를 한 겹 얹는 것이 더 좋은가. 어느 것도 절대 기준으로는 답할 수 없고, 무엇을 위해 좋아야 하는지를 정해야 비로소 답이 생긴다.
여기서 세 관점의 정체가 한 번 더 드러난다. 셋은 사실 세 개의 목적을 대변하고 있었다. 인지의 관점은 사람이 빠르고 정확하게 읽는다는 목적을, 실행의 관점은 기계 위에서 옳고 견고하게 돈다는 목적을, 구조의 관점은 변경을 값싸게 받아낸다는 목적을 대변한다. 좋은 코드가 한마디로 정의되지 않았던 이유가 이것이다. 목적이 셋이니 좋음도 셋으로 갈라지고, 갈라진 좋음은 자주 충돌한다. 성능을 위해 가독성을 포기하는 최적화나 유연한 구조를 위해 늘어나는 간접 계층이 그 충돌의 흔적이다. 충돌하는 좋음 사이에서 하나를 고르는 일을 우리는 트레이드오프라 부르는데, 트레이드오프의 다른 이름은 판단이다.
그런데 판단의 현장을 들여다보면 이상한 점이 하나 있다. 우리는 이 판단을 생각보다 자주 계산이 아니라 감각으로 내린다. 리뷰에서 누구도 "이 코드는 인지의 목적에 비추어 열등합니다"라고 말하지 않는다. "지저분한데요"라고 말한다. 세 갈래의 좋음이 하나의 느낌으로 뭉쳐서 도착하는 것이다. 그 느낌을 우리는 아름다움이라 부른다.
아름다움은 표현으로 드러난다
방금 좋음의 세 갈래가 하나의 느낌으로 도착한다고 했다. 이 절은 그 느낌을 뜯어보는 자리다. 관점마다 좋음이 다르니 관점마다 추구하는 아름다움6도 다르다. 인지의 아름다움은 읽기 편한 코드에 있고, 실행의 아름다움은 견고한 코드에 있으며, 구조의 아름다움은 잘 설계된 코드에 있다.
아름다움이라는 말이 거창하게 들린다면 우리가 코드 리뷰에서 자주 쓰는 말들을 떠올려보자. 깔끔하다, 더럽다, 우아하다, 탄탄하다와 같은 말은 아무렇지 않게 쓰지만 전부 미적 술어다. 그리고 이 말들을 하나씩 뜯어보면 어느 관점을 가리키는지, 왜 하필 그 단어인지가 꽤 정확하게 설명된다.
가장 흔한 표현인 더럽다부터 살펴보자. 더럽다라는 표현은 '코드 스멜'이라는 용어가 있을 만큼 프로그래머에게 친숙하며 이 문제를 해결하기 위해 리팩터링이라는 책이 나올 정도로 오래된 주제다. 그런데 코드가 더럽다는게 대체 무슨 뜻일까? 인류학자 메리 더글러스는 더러움을 "제자리에 있지 않은 것"이라고 정의했다7. 흙은 밭에 있으면 흙이지만 식탁 위에 있으면 오물이 된다. 이 정의를 들고 다음 코드를 보면 우리가 어떤 코드를 왜 더럽다고 느끼는지가 분명해진다.
fun handle(req: HttpRequest): HttpResponse {
val d = req.body.split(",")
// val point = d[2].toInt() * 10 // 예전 포인트 적립, 일단 보류
val conn = DriverManager.getConnection(DB_URL)
conn.createStatement()
.execute("UPDATE orders SET state='PAID' WHERE id=" + d[0])
Mailer().send(d[1], "결제가 완료되었습니다")
return HttpResponse(200)
}이 코드의 더러움을 하나씩 짚어보자. 변수명으로 설정한 d는 왜 더럽게 느껴질까? 왜냐하면 의미가 있어야 할 자리에 아무것도 없이 의미없는 문자만 남아있기 때문이다. 주석 부분은 왜 거슬릴까? 왜냐하면 맥락없이 주석만 남아있기 때문이다. HTTP 파싱과 SQL과 메일 발송은 원래 각자 다른 곳에 있어야 할 일인데 한 함수에 뭉쳐 있다. 전부 무언가가 제자리에 위치하지 않기에 더럽게 느껴진다.
여기서 가장 위험한 것은 보안 문제다. 문자열로 이어 붙인 SQL은 사용자가 보낸 값이 그대로 들어간다. 이는 SQL 인젝션이라는 공격으로 이어질 수 있다. 더럽다는 감각을 단순한 취향으로 치부할 수 없는 이유가 여기 있다.
이런 더러운 코드를 보면 누구나 손을 대기 전에 한 번쯤 주저하게 된다. 오염이 옮는 것처럼 여기를 고치면 어딘가 다른 곳에 문제가 생길지도 모른다는 감각은 이전 글에서 말한 "이해할 수 없는 것에 대한 두려움"과 같은 맥락이다.
그런데 더럽다라는 표현은 뒤집을 때 더 흥미로워진다. 더러움을 느꼈다는 것은 올바른 것이 무엇인지를 감각적으로 느끼고 있다는 것이다. 그런 관점에서 깔끔하다라는 표현이 바로 그 반대편에 있다고 볼 수 있다.
이번에는 우아하다라는 표현에 대해 살펴보자. 우아하다라는 표현은 원래 몸가짐의 말이다. 발레리나의 동작이 우아한 것은 불필요한 힘이 하나도 없기 때문이다. 코드에서도 같다. 다음은 쿠폰의 유효기간과 프로모션 기간이 겹치는지 판정하는 두 가지 코드다.
// 겹침의 모양을 하나씩 센다
fun overlaps(a: Period, b: Period): Boolean {
if (b.start in a.start..a.end) return true
if (b.end in a.start..a.end) return true
if (a.start >= b.start && a.end <= b.end) return true
return false
}
// 겹침의 정의를 발견한다
fun overlaps(a: Period, b: Period): Boolean =
maxOf(a.start, b.start) <= minOf(a.end, b.end)첫 번째 코드는 겹치는 경우의 수를 하나씩 나열한다. 시작일이 걸치는 경우, 종료일이 걸치는 경우, 통째로 포함되는 경우 등 빠뜨린 모양은 없는지 모든 경우의 수를 체크하며 확인해야 한다. 반면, 두 번째 코드는 겹침이라는 개념의 정의를 찾아냈다. 정의를 발견하는 순간 경우의 수가 통째로 사라진다. 이는 문제의 더 깊은 구조를 발견했다는 신호이고 수학자들이 좋은 증명에서 느끼는 우아함과 같은 느낌이라 볼 수 있다8.
마지막으로 탄탄하다에 대해 살펴보자. 탄탄하다는 원래 천과 몸에 쓰던 말이다. 짜임이 촘촘해서 힘을 받아도 풀리지 않는 상태를 가리킨다. 코드에서 이 감각은 빈틈이 눈에 보이지 않을 때 온다.
sealed interface PaymentResult {
data class Approved(val receiptId: String) : PaymentResult
data class Declined(val reason: DeclineReason) : PaymentResult
data class Timeout(val retryAfter: Duration) : PaymentResult
}
fun handle(result: PaymentResult) = when (result) {
is Approved -> confirmOrder(result.receiptId)
is Declined -> notifyCustomer(result.reason)
is Timeout -> scheduleRetry(result.retryAfter)
}결제 결과가 세 가지뿐이라는 사실이 sealed로 닫혀 있고, when에 else가 없으니 네 번째 결과가 추가되는 순간 컴파일러가 처리를 빠뜨린 모든 지점을 찾아낸다. 빠진 경우가 없다는 사실을 기계가 증명해주는 것이다. 반대로 곳곳에 else -> null이 흩어져 있는 코드는 어딘가 문제가 있을 것 같은 불안을 준다.
이런 미적 술어들이 하나같이 몸과 물질의 어휘라는 점은 우연이 아니다. 전작에서 아름다움은 생존의 감각에서 왔다고 정리했다. 더러움에 느끼는 혐오는 오염을 피하려는 본능이고 깔끔함이 주는 편안함은 안전하다는 신호다. 코드에는 세균도 낭떠러지도 없지만 코드를 읽는 뇌는 물질 세계를 수백만 년 살아온 뇌라서, 물질의 감각을 빌려 코드를 만진다. 그리고 네 술어의 소속을 다시 보면 이 글의 분류가 그대로 들어 있다. 깔끔함과 더러움은 인지와 구조의 말이고, 탄탄함은 실행의 말이고, 우아함은 문제를 꿰뚫는 판단의 말이다. 우리의 입은 이론을 배우기 전부터 세 관점을 알고 있었던 셈이다. 다만 한 가지 이상한 점이 남는다. 탄탄함은 미래의 장애에 대한 성질이고 우아함은 판단의 성질인데, 우리는 둘 다 실행하지도 않은 코드를 읽으며 느꼈다.
이상할 것 없다. 앞에서 표현을 세 관점이 새겨지는 유일한 표면이라고 불러둔 것이 여기서 값을 한다. 아름다움은 지각되어야 하고, 지각할 수 있는 것은 표현 하나뿐이다. 지향은 셋이지만 그 아름다움이 눈에 보이는 자리는 하나인 것이다. 구조의 아름다움은 어디에서 확인하는가? 디렉터리 구성, 인터페이스 선언, import 문에서 읽는다. 실행의 견고함은 어디에서 확인하는가? Result 타입9, 재시도 로직, 테스트 코드에서 읽는다. 아키텍처 다이어그램은 참고 자료일 뿐이고 결국 모든 것은 코드라는 표면 위에서 확인된다. 화가의 공간 감각과 색채 이론이 결국 캔버스 위 물감으로만 드러나듯, 어느 관점의 아름다움이든 그것이 드러나는 매체는 표현이다.
다만 세 관점이 표면과 맺는 관계가 똑같지는 않다. 인지의 아름다움은 표면에서 직접 겪는다. 잘 읽히는 코드는 읽는 순간 잘 읽히기 때문이다. 반면 구조와 실행의 아름다움은 표면에서 읽어내야 한다. 그리고 읽어낸다는 말에는 액면보다 깊은 구석이 있다. 표면은 원리적으로 자신이 담아야 할 것을 전부 담지 못하기 때문이다. 프로그램의 의미 있는 성질을 텍스트만 보고 판정하는 일반적인 방법은 존재하지 않는다는 것이 계산 이론의 증명된 사실이다10. 실행은 언제나 표현을 초과한다. 그렇다면 코드를 읽으며 탄탄함을 느꼈다는 것은 표면에 적힌 것을 읽었다는 뜻이 아니라, 표면의 단서로부터 표면에 적힐 수 없는 것을 추론했다는 뜻이다. sealed라는 글자와 else 없는 when에서 아직 오지 않은 네 번째 결제 결과까지 읽어내는, 감정의 형태로 압축된 추론. 우리는 그 능력을 감식안이라 부른다. 진화가 적합도라는 계산 불가능한 값을 아름다움이라는 즉각적인 신호로 접어 넣었듯이, 숙련은 판정 불가능한 성질을 감각으로 접어 넣는다.
표현이 유일한 무대라는 이 사실은 개발 문화의 오래된 수수께끼 하나를 설명해준다. 코드를 둘러싼 사람들의 충돌이 왜 하필 표현에서 벌어지는가 하는 문제다. 탭과 스페이스, Early Return 허용 여부, 삼항 연산자, for문과 함수형 체이닝. 바깥에서 보면 아무래도 좋을 논쟁이 당사자들에게는 물러설 수 없는 싸움이 된다. 필자도 리뷰에서 함수 이름 하나를 두고 동료와 댓글을 수십 개씩 주고받은 적이 있다. 지금 생각해보면 그 이름이 서비스의 성패를 가를 리 없었다. 그런데도 물러서지 않았던 이유는, 그 싸움이 이름에 대한 싸움이 아니라 각자가 믿는 아름다움에 대한 싸움이었기 때문이다. 표현은 각자의 미감이 겉으로 드러나는 거의 유일한 자리였고, 그래서 표현에 대한 지적은 종종 취향과 정체성에 대한 공격처럼 느껴졌던 것이다. 칸트는 미적 판단의 기묘한 성격을 짚은 적이 있다. 개념으로 증명할 수 없으면서도 타인의 동의를 요구한다는 것이다11. 노을이 아름답다는 말은 취향의 고백이지만, 우리는 상대도 그렇게 느끼기를 은근히 그러나 완강하게 요구한다. 이 이름이 더 낫다는 말도 같은 구조다. 증명할 수 없으니 싸움은 끝나지 않고, 동의를 요구하니 물러설 수도 없다. 이름 싸움이 그토록 소모적이면서 동시에 그토록 진지했던 이유다.
공학은 미학의 서식지다
여기서 한 가지 반문을 마주해야 한다. 지금까지 공학을 이야기하며 미학의 어휘를 잔뜩 끌어다 썼는데, 그래도 되는가. 공학과 미학의 결합은 애초에 가능한가. 고전 미학의 답은 부정에 가깝다. 칸트에게 미는 목적과 이해관계에서 자유로운 관조의 대상인데, 공학은 목적과 이해관계의 학문 그 자체다. 마감과 예산과 트레이드오프가 있는 곳에 무관심한 관조가 설 자리는 없어 보인다. 그런데 코드를 만져본 사람은 이 부정이 어딘가 틀렸다는 것을 몸으로 안다. 어디가 틀렸는지는 이 글이 이미 지나온 자리들에 있다. 우아함은 불필요한 힘이 하나도 없는 것이라고 했는데 그것은 정확히 공학의 경제성 원리다. 그리고 미는 제약 없는 곳에서 피어난 적이 없다. 소네트가 14행이라는 제약 안에서 아름다워지듯, 공학이 씨름하는 마감과 예산과 트레이드오프는 미의 적이 아니라 미가 자라는 조건이다. 공학은 미학의 반대말이 아니라 미학의 서식지다. 우리가 매일 쓰는 '아키텍처'라는 말의 원산지인 건축이 그 2천 년짜리 증거다. 고대에서 온전한 형태로 전해지는 유일한 건축 논고에서 비트루비우스는 건물의 세 기둥으로 견고함과 유용함과 아름다움을 나란히 세웠다12. 그러니 공학과 미학의 결합이 가능하냐는 물음에는 이렇게 답하게 된다. 가능하냐고 물을 것도 없다. 좋은 공학은 원래부터 그 결합의 다른 이름이었다.
아름다운 코드는 이론의 흔적이다
3년 전 글에서 필자는 궁극적으로 아름다운 코드를 "읽으며 걸리는 부분이 단 하나도 없는 코드"라고 정의했다. 돌아보면 이 정의 역시 인지의 아름다움에 뿌리를 두고 있다. 당시에는 그것이 너무나 자연스러웠다. 코드는 사람이 쓰고 사람이 읽는 것이었고, 표현이야말로 개발자가 매일 손으로 만지는 층이었기 때문이다.
이 정의를 이제 한 층 더 밀고 내려갈 수 있다. 컴퓨터과학자 페테르 나우르는 1985년에 이미 프로그래밍의 본질이 코드 작성이 아니라 이론 구축이라고 진단했다13. 프로그램의 실체는 텍스트가 아니라 그 시스템이 왜 그렇게 생겼는지에 대한 개발자들의 머릿속 이론이라는 것이다. 이 진단을 받아들이면 코드는 이론이 인쇄된 표면이 되고, 감식안이 표면에서 읽어내던 것의 정체도 밝혀진다. 우리는 글자 너머의 이론을 읽고 있었던 것이다. 앞의 미적 술어들도 전부 다시 설명된다. 더러운 코드는 이론 없이, 혹은 서로 다른 이론이 뒤엉킨 채 쌓인 코드다. 우아한 코드는 문제의 더 깊은 이론을 발견한 코드다. 탄탄한 코드는 이론이 경우의 수를 닫았다는 증거가 표면에 남은 코드다. 그러니 이렇게 정의해도 되겠다. 아름다운 코드란 좋은 이론이 배어 나오는 코드다. 3년 전의 정의가 읽는 눈의 정의였다면, 이것은 그 눈이 무엇을 보고 있었는지까지 담은 정의다.
그 전제가, 그러니까 사람이 쓰며 이론을 짓는다는 전제가 흔들리기 시작했다.
그리고 AI가 왔다
2023년 말, 그러니까 그 글을 쓸 무렵에도 코드를 만들어주는 AI는 있었다. 하지만 그때의 AI는 대체로 다음 줄을 제안하는 자동완성에 머물러 있었다. 제안을 받아들일지 말지를 사람이 줄 단위로 결정했고, 코드의 최종 형태는 여전히 사람 손에 있었다. 지금은 다르다. 에이전트는 이슈를 받아 코드베이스를 뒤지고, 파일을 고치고, 테스트를 돌리고, PR을 연다. 개발자가 코드를 쓰는 시간보다 코드를 요구하고 검수하는 시간이 길어졌다. 불과 3년 사이의 일이다.
이 변화의 본질을 한 문장으로 줄이면 이렇다. 표현의 비용이 0으로 수렴했다.
표현은 원래 비쌌다. 머릿속 의도를 문법에 맞는 문장으로 옮기고, 이름을 짓고, 어색한 부분을 다듬는 데는 시간과 정신력이 들었다. 그리고 비쌌기 때문에 자산이었다. 한 번 잘 써둔 코드는 오래 읽히니 다듬을 가치가 있었고, 리팩터링은 자산 관리였으며, 시니어의 손끝에서 나오는 코드 품질은 그 사람의 시장 가치이기도 했다.
표현이 공짜가 되자 이 계산이 전부 뒤집힌다. 마음에 안 드는 코드를 손으로 다듬는 것보다 다시 생성하는 쪽이 싸다면, 코드는 다듬는 자산에서 소모품 쪽으로 옮겨 간다. 리뷰 풍경도 달라진다. "이 이름은 조금 더 명확하면 좋겠어요"라는 코멘트는 점점 어색해진다. 그 이름은 애초에 누가 고심해서 지은 이름이 아니고, 지적하면 에이전트가 30초 만에 고쳐온다. 이름을 두고 밤새 싸우던 두 사람 사이에 이제 아무도 없다. 싸움이 해결된 것이 아니라 싸움의 무대가 철거된 것이다. 그리고 무대와 함께 철거된 것이 하나 더 있다. 미적 판단은 증명 없이 동의를 요구하기에 그 싸움은 소모전이면서 동시에 팀의 감각을 맞추는 공정이기도 했는데, 싸움이 사라진 자리에서는 각자의 미감이 교정받을 기회도 함께 사라진다.
이 변화를 가치의 언어로 옮기면 잃은 것의 윤곽이 더 선명해진다. 표현을 다듬는 일은 두 종류의 가치가 겹쳐 있던 드문 활동이었다. 다듬는 일은 그 자체로 좋았고, 다듬은 결과는 유지보수성과 연봉이라는 다른 것을 위해 좋았다. 내재적 가치와 도구적 가치가 한 활동 안에서 일치했고, 덕분에 우리는 그 자체로 하고 싶은 일을 하면서 돈을 받는 행운을 직업이라 불렀다. AI가 한 일은 이 활동을 없앤 것이 아니라 두 가치를 분리한 것이다. 도구적 가치는 기계가 가져갔고, 내재적 가치는 남았지만 업무와의 연결이 끊겼다. 손글씨가 정확히 이 길을 걸었다. 타자기와 워드프로세서는 필경사라는 직업을 지웠지만 캘리그래피는 죽지 않았다. 다만 일에서 취미가 되었다.
3년 전 글의 핵심 전제도 여기서 다시 검토하게 된다. 그 글에서 코드가 아름다워야 하는 이유를 "코드는 혼자 보는 것이 아니기 때문"이라고 썼다. 이 문장은 코드의 독자가 사람이라는 가정 위에 서 있다. 그런데 이제 코드의 첫 번째 독자, 그리고 가장 성실한 독자는 기계다. 사람은 코드를 처음부터 끝까지 읽는 대신 요약과 diff를 읽는다. 코드 전문을 정독하는 존재가 사람이 아니게 된 시대에, 사람의 눈을 위한 아름다움은 설 자리를 잃은 것처럼 보인다.
오해하지 말아야 할 것이 있다. 표현이 무가치해졌다는 말이 아니다. 값이 무너진 것은 표면으로서의 표현이 아니라 그 표면을 사람 손으로 빚어내는 행위로서의 표현이고, 잘 읽히는 코드는 여전히 잘 읽히는 코드다. 죽은 것은 표현 자체가 아니라 표현에 대한 사람들의 관심이다. 그리고 방금 확인했듯 표현은 우리가 아름다움을 드러내던 거의 유일한 통로였다. 통로가 막히면 무슨 일이 벌어지는가.
우리는 왜 재미가 없어졌는가
이제 서두에서 미뤄둔 상실감의 정체를 말할 수 있다.
개발의 재미가 어디에 있었는지 떠올려보자. 물론 안 풀리던 문제가 풀리는 순간의 쾌감이 있다. 하지만 그에 못지않게 큰 것이 다듬는 재미였다. 어제 쓴 코드보다 오늘 쓴 코드가 조금 더 낫고, 세 겹의 중첩을 펴서 문장이 한 줄기로 흐르게 만들고, 며칠을 굴리던 개념에 마침내 정확한 이름을 붙이는 일. 이것은 목수가 대패질에서, 서예가가 획 하나에서 느끼는 것과 같은 종류의 즐거움이다. 결과물만이 아니라 재료가 손을 거쳐 형태를 얻는 과정 자체가 주는 즐거움 말이다.
그 즐거움의 무대가 표현이었다는 점이 중요하다. 실행과 구조의 문제도 성취감을 주지만 매일 주지는 않는다. 아키텍처를 다시 긋는 날은 일 년에 몇 번 없고, 까다로운 동시성 버그를 잡는 날도 매일은 아니다. 하지만 표현은 달랐다. 매일, 모든 줄이 손을 거쳤다. 개발자가 일상적으로 아름다움을 빚는 활동의 대부분은 표현 위에서 벌어졌다.
AI가 가져간 층이 정확히 그 층이다. 가장 지적인 층이어서가 아니다. 오히려 반대다. 표현을 짓는 일은 가장 패턴화되어 있고, 학습 데이터가 가장 풍부하고, 정답의 범위가 가장 좁았기에 가장 먼저 자동화되었다. 하필 우리의 손맛이 거기에 있었을 뿐이다.
그러니 "요즘 개발이 재미없다"는 말은 게으름도 배부른 투정도 아니다. 일에서 아름다움을 빚을 자리를 잃었다는, 꽤 정확한 자기 진단이다. 가치의 언어로 다시 말하면, 그 자체로 좋은 것과 무언가를 위해 좋은 것이 한 몸이던 활동이 해체된 것에 대한 애도다. 일 자체는 오히려 잘 풀린다. 생산성은 올랐고 결과물은 늘었다. 그런데 하루가 끝나면 무언가를 만들었다는 감각이 이상하리만치 옅다. 필자도 에이전트가 짠 코드를 검수만 하다 하루를 마친 날, 퇴근길에 문득 오늘 코드를 한 줄도 쓰지 않았다는 사실을 깨달은 적이 있다. 일은 끝났는데 손에 남은 것이 없는 기분이었다. 장인에게서 연장을 치우고 검수 도장만 쥐여준 셈이니 당연한 감정이다.
이 공허함에는 감정 이상의 실체가 있다. 코드를 직접 쓰던 시절, 개발자가 시스템을 아는 방식은 내가 썼기 때문에 안다는 것이었다. 도시를 지도로 아는 것과 걸어서 아는 것이 다르듯, 쓴 사람의 앎에는 산책자의 앎이 있었다. 어디가 미끄러운지, 어디를 대충 덮었는지, 어떤 대안을 버렸는지를 몸이 알았다. 검수자의 앎은 종류가 다르다. 에이전트의 설명과 초록색 테스트를 근거로 믿는, 전해 들은 앎이다. 그리고 하루 종일 diff를 승인하면 정당화된 믿음은 쌓이지만 이해는 좀처럼 쌓이지 않는다. 이해란 낱낱의 확인을 더해서 도달하는 상태가 아니라, 부분들이 왜 그렇게 연결되어 있는지를 통째로 쥐는 별개의 상태이기 때문이다14. 그러니 손에 남은 것이 없다는 느낌은 착각이 아니다. 그날 정말로 남지 않은 것이다.
이 감정에는 선례가 있다. 여기서 들라로슈의 이야기로 돌아가자.
회화는 죽지 않았다
1839년 다게레오타입이 공개되었을 때 가장 큰 타격을 받은 것은 초상 세밀화가들이었다. 귀족과 부르주아의 얼굴을 실물처럼 작게 그려주는 일은 당시 화가들의 안정적인 밥벌이였는데, 사진은 그 일을 더 빠르고 더 정확하게 해냈다. 실물과 닮게 그리는 기술, 즉 재현이라는 수련의 대상이 통째로 기계에 넘어간 것이다. 실제로 이 직업군은 몇십 년 안에 거의 사라졌고, 많은 세밀화가가 사진사로 전업했다.
그러나 우리 모두 알듯 회화는 죽지 않았다. 대신 이상한 일이 벌어졌다. 재현의 의무에서 해방된 화가들이 사진이 할 수 없는 것을 찾아 나서기 시작한 것이다. 카메라는 사물을 정확히 옮기지만, 그 순간 빛이 눈에 남긴 인상은 옮기지 못한다. 그래서 인상주의가 나왔다15. 카메라는 한 시점에 고정되지만, 사람의 시선은 대상 주위를 돌며 여러 시점을 종합한다. 그래서 세잔과 입체파가 나왔다. 급기야 재현할 대상 자체를 버리는 추상까지 나아갔다. 사진 이전의 회화가 "얼마나 실물과 닮게 그릴 것인가"를 물었다면, 사진 이후의 회화는 "그림이란 무엇인가"를 물었다. 사진은 회화를 죽인 것이 아니라 회화의 질문을 바꿨다.
여기서 챙겨갈 통찰은 하나다. 아름다움은 죽지 않는다. 드러나던 자리를 빼앗기면 다른 자리를 찾아 이동한다.
들라로슈의 선언은 그래서 반만 맞았다. 재현 기술로서의 회화는 정말 죽었다. 하지만 회화는 그날 이후 다시 정의될 기회를 얻었다. 그렇다면 같은 질문을 우리에게 던질 차례다. 표현이라는 자리를 잃은 코드의 아름다움은 어디로 이동하는가.
아름다움은 이동한다
인지의 아름다움은 독자를 바꾼다
우선 인지의 아름다움은 통째로 폐기되지 않는다. 인지의 주체가 바뀔 뿐이다.
AI는 코드의 가장 성실한 독자가 되었고, 공교롭게도 이 새 독자는 사람과 비슷한 것에 걸려 넘어진다. 이름이 거짓말을 하는 함수, 한 파일에 뒤엉킨 책임, 일관성 없는 컨벤션은 사람만 헤매게 하는 것이 아니라 에이전트도 헤매게 한다. 모델에게 코드베이스는 곧 컨텍스트이고, 일관된 이름과 구조는 다음 행동의 정확도를 직접 끌어올린다16. 3년 전에는 동료를 위해 코드를 다듬었다면 이제는 사람과 기계 모두를 위해 다듬는 셈이니, 얄궂게도 클린 코드는 AI 시대에 오히려 더 실용적인 가치가 되었다.
공교롭다고 했지만 사실 우연이 아니다. 구조와 추상은 애초에 기계가 요구한 것이 아니었다. CPU는 스파게티 코드를 차별하지 않는다. 모듈과 경계와 계층은, 조합적으로 폭발하는 상태 공간을 유한한 작업 기억으로 다뤄야 했던 인간이 발명한 인지 보철이다. 그런데 컨텍스트 윈도우는 작업 기억의 기계적 등가물이다. 유한한 창으로 세계를 읽는 존재라면 무엇이든 덩어리 짓기가 필요하다. 좋은 이름과 경계가 새 독자에게도 통하는 이유는 그것이 인간용이어서가 아니라 유한한 인지 일반의 요구이기 때문이다.
표현은 계약으로 올라간다
여기까지는 읽는 쪽의 이야기였다. 쓰는 쪽에서도 변화가 일어나고 있다. 사람이 쓰는 표현의 층이 달라지는 것이다.
우선 새로운 산문 장르가 태어나고 있다. 스펙 문서, 설계 메모, 에이전트에게 프로젝트의 규칙을 알려주는 컨텍스트 문서, 결정의 이유를 남기는 ADR17 같은 것들이다. 전부 사람이 기계에게, 그리고 기계 너머의 동료에게 의도를 전달하는 글쓰기다. 잘 쓰인 스펙과 못 쓰인 스펙의 차이는 잘 쓰인 코드와 못 쓰인 코드의 차이만큼 크다. 정확한 문장으로 의도를 옮기는 능력은 매체를 코드에서 산문으로 넓혔을 뿐이다.
그리고 산문보다 더 단단한 표현이 있다. 타입이다. 구현은 생성해도 계약은 사람이 긋는다. 앞의 calculateDiscount가 받았던 세 갈래의 지적을 떠올려보자. 그 지적들은 사실 대부분 타입이라는 표현으로 접어 넣을 수 있다.
// 할인 정책은 갈아 끼울 수 있어야 한다는 구조의 결정
interface DiscountPolicy {
fun discountFor(order: Order): Discount
}
// 할인율은 0 이상 1 이하라는 도메인의 불변식
@JvmInline
value class DiscountRate private constructor(val value: BigDecimal) {
companion object {
fun of(value: BigDecimal): DiscountRate {
require(value in BigDecimal.ZERO..BigDecimal.ONE)
return DiscountRate(value)
}
}
}
// 같은 쿠폰은 두 번 성공할 수 없다는 실행의 결정
fun redeemCoupon(couponId: CouponId, orderId: OrderId): Result<RedeemedCoupon>이 스무 줄이 채 안 되는 선언이 에이전트가 생성할 수백 줄의 자유도를 결정한다. 중첩 if를 어떻게 펼지는 기계에게 맡겨도 되지만, 할인율이 1을 넘을 수 없다는 사실과 같은 쿠폰이 두 번 성공할 수 없다는 사실은 사람이 적어야 하는 문장이다. 잘못된 상태를 아예 표현할 수 없도록 타입을 깎는 일은 오래된 기법이지만, 생성된 코드가 계약 안에서만 움직이도록 묶어두는 고삐로서 지금 더 값지다. 테스트도 같은 층에 있다. 무엇이 옳은지를 실행 가능한 문장으로 적어두면 기계가 만든 구현을 기계적으로 검증할 수 있다. 덧붙이면 이런 계약의 최후 방어선은 종종 코드 바깥에 있다. 같은 쿠폰이 두 번 성공할 수 없다는 사실을 가장 값싸고 확실하게 보증하는 것은 재치 있는 동시성 제어가 아니라 데이터베이스의 유니크 제약이다. 구현이 생성되는 시대에 사람이 마지막까지 손으로 쓰게 될 텍스트는 계약과 불변식, 그리고 옳음의 정의다.
물론 계약에 대한 환상은 경계해야 한다. 계약을 아무리 촘촘히 그어도 구현이 그것만으로 결정되지는 않는다. 어떤 규칙도 자신의 모든 적용 사례를 스스로 담을 수 없다는 것은 비트겐슈타인이 규칙 따르기 논의로 보여준 오래된 통찰이다18. 스펙이 정교해질수록 남는 틈은 줄어들지만 사라지지는 않으며, 그 틈을 메우는 것은 결국 사람의 판단이다.
다만 계약을 긋는 일이 아무리 중요해도 그것이 잃어버린 손맛을 그대로 돌려주지는 않는다. 더 중요한 이동은 다른 곳에서 일어난다.
아름다움의 단위가 커진다
사진이 회화에게서 재현을 가져가자 회화의 질문이 "어떻게 그릴 것인가"에서 "무엇을 그릴 것인가"로 옮겨간 것처럼, AI가 표현을 가져가자 개발의 질문도 옮겨간다. 코드 한 줄의 아름다움에서 시스템 전체의 아름다움으로, 아름다움의 단위가 커지는 것이다.
표현을 기계가 채워주는 시대에 병목은 판단이다. 어디에 경계를 그을 것인가. 무엇을 보장하고 무엇을 포기할 것인가. 그리고 무엇을 아예 만들지 않을 것인가. 이것은 구조와 실행의 관점이 던지던 바로 그 질문들이고, 코드 표면에 답이 적혀 있지 않은 질문들이다. AI는 요구하면 무엇이든 빠르게 만들어준다. 그런데 무엇이든 만들 수 있게 되는 순간 드러나는 것은, 무엇을 만들어야 하는지 아는 사람과 모르는 사람의 차이다. 물론 에이전트는 구조도 제안하고 아키텍처 문서도 그럴듯하게 써낸다. 하지만 제안과 결정은 다르다. 결정에는 트레이드오프의 대가를 치를 사람이 필요하고, 아름다움은 그 결정 쪽에 깃든다.
실행의 감식안도 마찬가지다. 에이전트가 만든 코드는 대체로 돌아간다. 문제는 돌아가는 코드와 옳게 돌아가는 코드의 간극이다. 같은 버튼을 동시에 열 명이 누르면? 재시도가 겹치면? 타임존이 다른 사용자가 자정에 주문하면? 이런 질문은 코드를 아무리 읽어도 표면에 보이지 않기에, 생성된 결과물을 받아 드는 것만으로는 결코 얻을 수 없다. 앞서 본 calculateDiscount의 중첩 if는 AI가 즉시 펴주지만, 같은 쿠폰이 동시에 두 번 적용될 수 있다는 사실은 그것을 물어볼 줄 아는 사람에게만 보인다.
앞에서 어느 관점의 아름다움이든 드러나는 매체는 표현이라고 했다. 그 사실은 지금도 유효하다. 경계의 결정은 여전히 인터페이스 선언으로 적히고, 견고함은 여전히 테스트 코드로 적힌다. 달라진 것은 그 표면을 사람이 직접 쓰지 않게 되었다는 점이다. 이제 아름다움은 표면을 다듬는 손끝이 아니라 표면에 무엇을 새길지 정하는 판단에서 태어나고, 코드는 그 판단이 인쇄되는 자리가 된다. 돌아보면 구조와 실행의 아름다움은 원래부터 그런 아름다움이었다. 표현이라는 앞무대에 가려 잘 보이지 않았을 뿐이다.
아름다움은 사치인가
여기까지 읽고도 마음 한구석에서 이런 목소리가 들릴지 모른다. 다 좋은 이야기인데, 실용적으로 갑시다. 코드는 돌아가면 되는 것 아닌가. 게다가 이제 기계가 짜는 시대라면, 생성된 코드에 미감을 들이대는 일이야말로 사진 시대에 세밀화를 그리워하는 향수 아닌가. 그리고 이 목소리에는 대개 동행이 하나 붙는다. 아름다움이니 감각이니 하는 것은 결국 취향 아닌가. 개발자는 데이터로 말해야 하는 것 아닌가.
서두에서 약속한 반박이 이 장이다. 사실 이 글을 쓰게 만든 것의 절반은 상실감이지만, 나머지 절반은 저 문장이다. 아름다움 이야기가 나올 때마다 어김없이 등장해서 대화를 끝내버리는 말. 문제는 저 말의 '실용'이 대부분 실용이 아니라는 데 있다. 저 말의 밑바닥에 깔린 더 오래된 가정, 그러니까 공학과 미학은 애초에 섞이지 않는다는 가정은 앞에서 이미 뒤집어두었다. 이 장에서는 남은 것을 따진다. 저 실용이 정말 실용인지, 그리고 감각을 근거로 삼아도 되는지. 그런 다음 마지막으로, 사치라는 혐의를 가장 많이 받는 것 하나를 변호하려 한다. 재미다.
실용과 편의는 다르다
실용주의는 원래 철학의 이름이다19. 퍼스가 정식화하고 제임스가 다듬은 실용주의의 격률은, 어떤 관념의 의미도 가치도 그것이 낳을 실천적 결과의 총합에서 찾으라는 것이다. 여기서 핵심은 '총합'이다. 다음 분기까지의 결과가 아니라 끝까지의 결과. 나 혼자 겪을 결과가 아니라 이 코드를 만질 모든 사람이 겪을 결과. 실용주의는 결과를 성실하게 계산하자는 엄격한 태도이지, 계산을 생략하자는 태도가 아니다. 그러니 "돌아가면 됐지"는 실용주의가 아니다. 결과의 지평을 오늘 오후로 잘라낸 실용주의, 곧 편의주의다. "실용적으로 갑시다"라는 말의 절반은 "생각을 여기서 멈춥시다"의 공손한 표현이다.
오해는 피하자. 오버엔지니어링을 경계하는 것, 지금 필요 없는 것을 만들지 않는 것은 옳다. 그것이 결과 계산의 산물일 때는 말이다. 진짜 실용주의자도 추한 코드를 출시한다. 마감이 있고 트레이드오프가 있기 때문이다. 다만 그는 그것을 부채로 기록하고 이자율을 어림한다. 편의주의자와의 차이는 결과물이 아니라 장부에 있다. 편의주의자의 장부에는 부채 항목이 없다.
이 장부에 미감을 올려보면 흥미로운 일이 벌어진다. 앞에서 더럽다는 감정을 꽤 정밀한 경보라고 불렀고, 감식안을 표면에서 판정 불가능한 것을 읽어내는 압축된 추론이라고 했다. 실용주의의 저울로 달아도 이것들은 사치품이 아니라 계측기다. 코드 표면의 미세한 단서에서 미래의 장애를 미리 느끼는 감각보다 실용적인 것이 무엇인가. 아름다움을 내치는 실용은 계측기를 떼어낸 실용이다. 그리고 실용주의의 완성자가 듀이, 그러니까 이 글이 내내 기대온 바로 그 미학자라는 사실은 우연이 아니다. 결과를 끝까지 계산하는 사람은 반드시 경험의 질까지 계산하게 된다.
다만 앞에서 말한 공학과 미학의 결합에는 조건이 있다. 양쪽에 하나씩 가짜가 있기 때문이다. 실용을 가장한 편의주의가 한쪽의 가짜라면, 아름다움을 가장한 탐닉이 반대쪽의 가짜다. 아무도 요구하지 않은 추상 계층, 문제보다 정교한 해법, 갈아 끼울 일이 없는 것을 갈아 끼울 수 있게 만든 설계. 앞에서 오버엔지니어링을 경계하는 것이 옳다고 했는데, 이제 그 이유를 더 정확히 말할 수 있다. 과잉 설계는 편의주의의 반대편에 있는 것처럼 보이지만 결과의 계산을 생략했다는 점에서 같은 병이다. 편의주의가 지평을 잘라낸 실용주의라면 탐닉은 목적을 잘라낸 미학이고, 결합은 두 가짜를 걷어낸 자리에서만 성립한다.
감각을 믿어도 되는가
앞에서 미감을 계측기라고 불렀다. 그런데 이 비유에는 불편한 구석이 하나 남아 있다. 계측기는 눈금을 보여주는데 감각은 눈금을 보여주지 않는다. 코드 리뷰에서 "이 설계는 어딘가 걸립니다"라고 말해본 사람은 안다. 그 말이 얼마나 힘이 없는지. 데이터로 말하라, 재현 경로를 보여라, 벤치마크를 가져와라. 감각으로 말하는 것은 개발자의 미덕이 아니고, 이 규율은 옳다. 그런데 얄궂게도 우리 직업은 공식 용어집에 '냄새'라는 단어를 등재해둔 직업이기도 하다. 코드 스멜20. 데이터를 요구하는 문화가 후각의 어휘를 교과서에 실어둔 것이다. 감각을 불신하면서 감각으로 일하는 이 모순을 어떻게 정리해야 하는가.
실마리는 과학이 같은 모순을 이미 정리해봤다는 데 있다. 케쿨레는 꿈에서 제 꼬리를 문 뱀을 보고 벤젠 고리를 떠올렸다고 전해지지만, 논문에 뱀을 적지는 않았다. 과학철학은 이것을 발견의 맥락과 정당화의 맥락의 구분이라 부른다21. 가설이 어떻게 떠올랐는지는 심사 대상이 아니다. 가설이 왜 옳은지만 심사받는다. 퍼스는 이 번뜩임에 이름까지 붙여줬다. 연역도 귀납도 아닌 세 번째 추론, 가추(abduction). 새로운 생각을 들여오는 유일한 추론이고, 본질적으로 직감의 형태로 온다22. 이 구분을 가져오면 감각의 자리가 정확해진다. 감각은 판정 기관이 아니라 가설 생성 기관이다. 앞에서 감식안을 압축된 추론이라 했는데, 압축된 추론의 출력은 판결문이 아니라 수색 영장이다. 냄새가 난다는 것은 이 설계가 틀렸다는 뜻이 아니라 여기를 파볼 이유가 생겼다는 뜻이다. 감각이 울리면 조사할 의무가 생기는 것이지, 이길 권리가 생기는 것이 아니다.
그래서 감각의 품위는 감각의 내용이 아니라 발화의 형식에서 갈린다. "느낌상 이 설계는 틀렸어요"는 가설을 판결처럼 말하는 범주 착오이고, "여기 걸리는 데가 있으니 부하 테스트 하나만 얹어봅시다"는 완벽하게 공학적인 문장이다. 같은 감각, 다른 문법. 시니어의 일에는 여기서 하나가 추가된다. 자신의 압축된 추론을 풀어서 보여주는 번역 노동이다. "냄새나요"에서 멈추지 않고 "상태를 두 군데서 바꾸는 함수는 보통 동시성 버그의 전조라서요"까지 가는 것. 물론 번역이 늘 되는 것은 아니다. 우리는 말할 수 있는 것보다 많이 알기 때문이다. 번역이 안 될 때의 정직한 형식은 근거를 가장하는 것이 아니라 판돈을 거는 것이다. "근거는 아직 못 대겠습니다. 반나절만 제가 파보겠습니다." 감각을 발화할 자격은 직급에서 나오는 것이 아니라, 그 감각에 자기 시간을 거는 데서 나온다.
인정할 것은 인정하자. 감각은 훈련된 범위 안에서만 계측기다. 전문가의 직관이 성립하는 조건은 규칙성이 있는 환경과 빠른 피드백, 두 가지뿐이라는 것이 직관 연구의 합의다23. 낯선 도메인, 낯선 규모, 낯선 도구 앞에서 감각은 자신 없어하는 대신 그냥 틀린다. 기계가 아는 것과 지어내는 것을 구별하지 못한다고 뒤에서 비판할 텐데, 정직하게 말하면 사람의 감각도 그 경계에서 자주 지어낸다. 차이는 하나다. 사람은 자기 감각을 검증에 회부할 수 있고, 회부한 만큼 교정된다는 것. 감각을 믿어도 되느냐는 질문의 답이 감각 자체에 있지 않고 감각을 통과시키는 회로에 있는 이유다.
회로는 이렇게 돈다. 감각이 경보를 울리고, 조사가 판정하고, 반복해서 확인된 판정은 규칙으로 침전된다. 가설, 검증, 배선. 이 회로가 한 바퀴 돌 때마다 말 못 하던 앎이 조금씩 말할 수 있는 앎이 되고, 한 사람의 감이 팀의 자산이 된다. 그리고 이 회로는 검수의 시대에 감각이 마모되지 않게 하는 거의 유일한 방법이기도 하다. diff만 승인하는 하루에는 감각이 맞았는지 틀렸는지 확인할 기회가 없지만, 감각을 명시적으로 검증에 회부하는 습관은 그 피드백을 강제로 되살린다. 그러니 처음의 질문에는 이렇게 답하게 된다. 감각을 믿어도 되는가. 감각만 믿지 않는 사람의 감각은 믿어도 된다.
회로의 마지막 단계인 배선은 실제로 이런 모습이다. 공통점은 하나다. 아름다움에 대한 감각을 감상으로 남기지 않고 기계가 지키는 규칙으로 바꾼다는 것. 도메인은 인프라를 알면 안 된다는 경계의 결정은 문서에 적으면 잊히지만 아키텍처 테스트로 적으면 위반이 들어오는 순간 빌드가 깨지고, 앞의 overlaps처럼 정의를 발견한 코드라면 그 정의가 만족해야 할 성질을 선언해두고 수천 개의 무작위 입력으로 두들겨볼 수 있다24. 경우를 세는 코드는 빠뜨린 경우를 스스로 실토하지 않지만, 성질 검사는 주사위 수천 번으로 그 침묵을 깬다.
팀의 미감도 도구에 저장할 수 있다. 앞에서 이름 싸움의 무대가 철거되면서 공동의 미감이 교정받을 기회도 사라졌다고 했는데, 그 무대의 대체물이 린트 규칙과 훅이다. 싸움의 결론을 규칙으로 적어두면 사람 사이의 합의가 사람이 떠난 뒤에도 도구 안에서 살아남고, 에이전트가 반복하는 실수는 리뷰 코멘트가 아니라 훅이 원천 차단한다. 그리고 이렇게 도구를 벼리는 일이 왜 즐거운지는 바로 뒤에서 할 이야기다.
이것들이 실용주의의 전부는 아니지만 공통의 문법은 보여준다. 편의주의는 아름다움을 비용으로 계산해서 삭감하고, 실용주의는 아름다움을 계측기로 계산해서 배선한다.
재미는 어디에 있는가
아름다움이 어디로 이동하는지 보았고, 그것을 다룰 방법까지 정리했다. 그렇다면 마지막 질문이 남는다. 재미도 함께 이동하는가. 이 질문이 사치처럼 들린다면 더더욱 짚고 가야 한다. 앞에서 상실감의 정체를 다듬는 재미의 소멸에서 찾았으니 이 질문을 피할 수 없다.
재미라는 말이 일의 곁가지처럼 들린다면 장인 쪽에서 보면 된다. 사회학자 리처드 세넷은 장인 정신을 일을 그 자체로 잘 해내려는 오래된 욕구라고 정의했다25. 여기서 결정적인 말은 '그 자체로'다. 숙련은 지루한 반복을 수천 시간 견딘 사람에게만 오는데, 그 시간을 버티게 하는 것은 바깥에서 오는 보상이 아니라 그 일이 그 자체로 붙잡아둔다는 사실이기 때문이다. 그러니 재미는 장인 정신에 딸려오는 사치가 아니라 장인 정신이 서는 토대다. 재미가 사라진 자리에서는 숙련도 자라지 않는다.
실마리는 아름다움의 소재지를 다시 묻는 데서 나온다. 철학자 존 듀이는 미적인 것의 거처를 대상에서 경험으로 옮긴 사람이다26. 아름다움은 사물에 붙어 있는 속성이 아니라, 저항하는 재료와 밀고 당기는 상호작용이 하나의 완결된 경험을 이룰 때 그 경험이 띠는 질이라는 것이다. 이 관점을 빌리면 지금까지의 진단이 한 문장으로 줄어든다. AI는 코드에서 아름다움을 가져간 것이 아니라 개발자에게서 경험의 한 형식을 가져갔다. 생성된 코드가 여전히 깔끔한데도 재미가 없는 이유다. 아름다움은 대상에 남았고, 미적 경험은 사람에게서 떠났다.
다듬는 재미를 해부해보면 세 가지 재료가 나온다. 저항하는 재료, 즉각적인 피드백, 그리고 쌓이는 숙련. 결과를 망칠 수 있는 작업에만 손맛이 있고27, 손맛이 있는 곳에 재미가 있다. 표현은 이 세 가지를 매일 제공하던 드문 무대였다. 그렇다면 이 세 가지가 남아 있는 곳을 찾으면 된다.
첫 번째는 뜻밖에도 버리는 재미다. 표현이 공짜가 되면서 만들어보고 버리는 것도 공짜가 되었다. 재고 차감을 먼저 잠그고 시작하는 비관적 락으로 풀지 이벤트로 풀어 나중에 정합을 맞출지 고민된다면, 예전에는 회의실에서 토론했지만 이제는 오후에 둘 다 만들어 부하를 걸어보면 된다. 프로토타입 세 개를 만들어 두 개를 버리는 일은 예전에는 사치였지만 지금은 합리적인 설계 기법이다. 설계 논쟁이 말싸움에서 실험으로 바뀌는 것인데, 가설이 데이터 앞에서 깨지고 서는 광경은 언제나 재미있다.
두 번째는 시스템의 물성과 씨름하는 재미다. 코드는 생성되지만 레이턴시와 동시성과 장애는 생성되지 않는다. 직접 부하를 걸어 관찰해야만 안다. 요청의 99%가 그 안에 들어오는 응답 시간, 그러니까 p99가 갑자기 튀는 이유를 쫓다가 커넥션 풀 고갈을 찾아내는 과정은 훌륭한 추리극이고, 이 손맛에는 자동화가 가장 늦게 도달할 것이다. 정답이 코드 표면에 적혀 있지 않기 때문이다.
세 번째는 도구를 만드는 재미다. 목수는 가구만 만들지 않는다. 가구를 정확하게 깎기 위한 지그(jig)를 만들고, 솜씨 좋은 목수일수록 지그가 정교하다. 에이전트 시대의 지그는 린트 규칙, 테스트 하네스, 컨텍스트 문서, 검증 훅 같은 것들이다. 앞에서 배선이라 부른 것이 정확히 이 지그다. 에이전트가 반복하던 실수를 훅 하나로 원천 차단했을 때의 만족감은 대패질의 만족감과 그리 멀지 않다. 도구를 벼리는 일은 언제나 장인의 몫이었다.
그리고 마지막으로 튜닝의 재미가 있다. 완성된 기계를 그대로 쓰지 않고 다이얼을 돌려 내 손에 맞게 길들이는 재미다. 자동차의 서스펜션을 바꾸는 사람들, 기계식 키보드의 스위치를 갈아 끼우는 사람들이 아는 그 재미이고, 사실 개발자에게는 낯설지도 않다. 에디터 설정 파일을 십 년째 다듬는 사람, 새 장비를 받으면 하루 종일 개발 환경부터 맞추는 사람이 어느 팀에나 있다. 단축키 하나, 컬러 스킴 하나에 정답은 없지만 취향은 있다.
여기서도 사진의 역사가 앞서 걸었다. 카메라가 재현을 자동화했지만 사진가의 손이 놀게 되지는 않았다. 사진가들은 조리개와 노출을 고르고 암실에서 현상 시간과 인화지를 바꿔가며 같은 필름에서 전혀 다른 사진을 뽑아냈다. 기계가 일을 대신하자 기계 주위에 다이얼이 자라났고, 사진의 손맛은 거기서 다시 태어났다. 지금 개발에도 같은 다이얼이 자라나는 중이다. 어떤 모델을 쓸지, 컨텍스트에 무엇을 넣고 뺄지, 훅을 어디에 걸지, 타임아웃과 재시도 횟수를 얼마로 둘지, 캐시를 얼마나 오래 들고 있을지. 돌리면 결과가 바로 변하고, 뜻대로 되지 않고, 어떤 조합이 좋은지는 감으로만 쌓인다. 다듬는 재미의 세 재료가 고스란히 있는 것이다. 그리고 다이얼을 다 돌리고 난 시스템은 어디에도 없는 내 것이 된다.
변하지 않는 것
이제 마지막 질문이 남았다. 아름다움이 자리를 옮기는 동안 변하지 않는 것은 무엇인가.
세 관점을 다시 보자. 인지, 실행, 구조는 코드를 잘 쓰기 위한 기술 목록이 아니었다. 그것은 소프트웨어를 바라보는 렌즈였고, 렌즈의 용도는 문제를 발견하고 트레이드오프를 판단하는 것이었다. 기술은 낡는다. 전작에서 예로 들었던 문법과 패턴 중 몇몇은 벌써 낡은 이야기가 되었다. 하지만 렌즈는 낡지 않는다. AI가 코드를 쓰는 시대에도 여전히 누군가는 세 가지 질문을 던져야 한다. 이 코드는 읽는 이에게 의도를 전하는가. 이 코드는 실제로 무엇을 하는가. 이 코드는 여기 있어야 하는가. 달라진 것은 질문을 던지는 대상이 손으로 쓴 코드에서 생성된 코드로 바뀌었다는 것뿐이다.
그 렌즈를 받치는 것들은 더 오래되었다. 3년 전, 사람은 이해할 수 없는 것에 두려움을 느낀다고 썼다. 그때는 남이 쓴 난해한 코드 이야기였는데, 이제 이 명제는 방향을 바꿔 우리 자신을 겨눈다. 생성된 코드를 이해 없이 쌓아 올리면 어느 순간 자신의 시스템이 두려워진다. 장애가 났는데 어디를 봐야 할지 모르는, 열어보기 겁나는 코드베이스의 주인이 되는 것이다. 그래서 이해는 여전히 모든 것의 전제다. 이해 없이는 검증도 없고, 검증 없는 위임은 방치이기 때문이다28.
나우르의 이론 구축 이야기로 돌아갈 차례다. 앞에서는 그 진단의 절반, 프로그램의 실체가 머릿속 이론이라는 쪽만 썼다. 이제 나머지 절반이 필요하다. 이론은 문서로도 코드로도 전달되지 않는다는 것이다. 각자가 코드를 만지는 시간 속에서 다시 지어 올리는 수밖에 없다. 쓰기의 소멸이 위험한 진짜 이유가 여기에 있다. 사라진 것은 타이핑이라는 노동이 아니라 이해가 재구축되던 주된 경로다. 표현의 비용이 0이 되자 진짜 비용은 이해 쪽으로 이월되었다. 생성 속도가 이해 속도를 앞지르는 만큼 시스템이 하는 일과 사람이 아는 것 사이의 간극은 매일 벌어진다. 기술 부채가 코드에 쌓인다면 이 부채는 사람 쪽에 쌓이는 인식적 부채이고, 장애의 순간이 그 만기일이다.
세넷은 같은 일이 건축에서 먼저 벌어지는 것을 지켜봤다. 부지를 손으로 그리던 시절의 건축가는 등고선을 긋고 나무를 앉히는 동안 그 땅을 알게 되었는데, 설계가 화면 안으로 들어가자 그 앎이 함께 사라졌다는 것이다29. 도면은 더 정확해졌고 수정은 비교할 수 없이 빨라졌다. 다만 그리는 사람의 머릿속에 남는 것이 줄었다. 도구가 만드는 일을 대신할 때 무엇이 조용히 빠져나가는지에 대한, 우리보다 스무 해 앞선 기록이다.
이해 위에 판단이 선다. 무엇을 만들지, 어디에 경계를 그을지, 무엇을 포기할지 결정하는 일은 표현이 공짜가 될수록 오히려 값이 오른다. 생성은 순식간이지만 무엇을 생성할지 아는 데는 여전히 연차와 흉터가 필요하다. 다만 이 문장에는 불편한 구석이 있다. 그 연차와 흉터의 대부분은 표현을 손으로 만지던 시간에서 왔다. 아리스토텔레스는 보편 규칙을 눈앞의 개별 상황에 맞게 구부리는 이런 종류의 앎을 실천지라 부르며, 가르칠 수 없고 오직 개별 상황의 경험으로만 길러진다고 진단했다30. 젊은이는 수학자는 될 수 있어도 실천지를 가진 자는 될 수 없다고 한 것도 그였다. 수학은 보편에서 출발하지만 실천지는 겪은 개별들에서만 오기 때문이다. 판단이 명제로 전달될 수 있었다면 시니어의 연차는 진작 책 한 권으로 대체되었을 것이다. 그래서 이것은 교육 과정을 손보는 수준의 문제가 아니다. 판단이라는 앎의 유일하게 알려진 습득 경로가 실천인데, 그 실천이 방금 자동화되었다. 훈련장이 사라진 다음 세대는 어디서 판단을 익힐 것인가. 이것은 필자도 답을 갖지 못한 채 업계 전체가 곧 마주하게 될 질문이다.
판단 뒤에는 책임이 따라온다. 프로덕션에서 돌아가는 것에 대해 최종적으로 답하는 존재는 여전히 사람이다. 코드 작성은 위임할 수 있어도 장애 앞에서 "AI가 짰는데요"라는 말이 면책이 되지는 않는다. 작성의 주체가 바뀌어도 서명의 주체는 바뀌지 않았다. 왜 서명은 위임되지 않는가. 관례가 아니라 앎의 구조 때문이다. 사람이 쓴 코드를 믿는 것은 증언을 믿는 것과 같아서, 믿음의 사슬을 거슬러 올라가면 끝에는 직접 아는 사람이 있다. 생성된 코드는 단언의 형식을 완벽하게 갖췄지만 그 사슬의 끝에는 아무도 없다. 기계는 주장하되 알지 못한다.
이 문장에는 당연히 반박이 있을 것이다. 기계도 아는 것 아닌가. 물어보면 답하고, 대체로 맞고, 안에 세계에 대한 표상도 있다. 좋다, 그 말의 어떤 의미에서는 안다고 치자. 의식이 있느냐는 형이상학 논쟁까지 갈 필요도 없다. 그래도 두 가지가 없다. 하나, 지금의 기계는 자신이 아는 것과 지어내는 것을 안에서 구별하지 못한다. 정확한 답과 환각은 같은 공정에서 같은 확신으로 나온다. 증언이라는 제도는 화자가 아는 것만 말하려 한다는 규범 위에 서 있는데31, 아는 상태와 지어내는 상태가 내부에서 구별되지 않는 화자는 이 규범을 지킬 수도 어길 수도 없다. 규범 바깥의 존재인 것이다. 둘, 단언은 본래 내기다. 무언가를 주장한다는 것은 틀렸을 때 잃을 것을 함께 거는 행위이고, 우리가 남의 말을 믿는 것은 절반쯤은 그 판돈을 믿는 것이다32. 물론 사람의 확신도 자주 빗나간다. 하지만 사람은 빗나간 확신의 대가를 치르고, 그 대가가 다음 확신을 벼린다. 판단에 연차와 흉터가 필요하다고 한 것과 같은 이야기다. 기계는 아무것도 걸지 않는다. 걸 것이 없다. 첫 번째 결핍은 언젠가 기술이 메울지도 모른다. 그러나 두 번째 결핍은 기술의 문제가 아니라 지위의 문제라서, 모델이 아무리 좋아져도 그대로 남는다.
증언자 없는 증언인 셈이다. 그래서 생성된 코드의 검수는 남이 이미 아는 것을 재확인하는 일이 아니라 누구도 가진 적 없는 앎을 최초로 만들어내는 일이고, 서명이 사람에게 남는 이유는 앎의 최초 발생지도, 판돈이 걸리는 자리도 사람일 수밖에 없기 때문이다.
그리고 배려가 있다. 3년 전에는 코드를 아름답게 만드는 행위가 배려이자 팀을 위한 활동이라고 썼다. 이 생각은 지금도 유효하다. 바뀐 것은 배려의 매체다. 이제 배려는 코드 스타일보다는 좋은 경계, 좋은 스펙, 좋은 컨텍스트로 나타난다. 다음에 올 사람이, 그리고 다음에 올 사람의 에이전트가 헤매지 않도록 길을 내는 일이다. 나우르 식으로 말하면, 어차피 전달되지 않는 이론을 그래도 다시 짓기 쉽도록 발판을 남겨두는 일이다.
돌아보면 이해, 판단, 책임, 배려는 나열이 아니라 계단이다. 이해 위에서만 판단이 서고, 판단에만 책임이 따르며, 책임까지 져본 사람이 다음 사람을 배려한다. 앎에서 시작해 윤리에서 끝나는 이 사다리는 AI 이전에도 엔지니어링의 핵심이었다. 다만 표현이 비쌌던 시대에는 코드를 잘 쓰는 능력이 이 모든 것의 대리 지표 노릇을 했기에, 코드 잘 쓰는 사람과 좋은 엔지니어를 구분할 필요가 없었을 뿐이다. AI가 표현이라는 껍질을 걷어가자 안에 있던 것이 드러났다. 껍질 안쪽에 아무것도 없는 사람과 원래부터 그 안쪽으로 일하던 사람의 차이다.
마치며
3년 전 글을 필자는 이렇게 맺었다. 아름답다는 말이 '나 답다'에서 왔다는 옛말처럼, 경험과 고민이 축적된 코드가 가장 나 다운, 그래서 가장 아름다운 코드가 아닐까 하고. 그 문장을 지금 다시 쓴다면 한 단어만 바꾸면 될 것 같다. 코드가 아니라 판단이다. 코드는 이제 기계도 쓴다. 하지만 무엇을 만들지, 어디에 선을 그을지, 무엇을 보고 아름답다고 느낄지는 여전히 한 사람의 경험과 고민 속에서만 축적된다. 아름다움은 사라진 것이 아니라 코드에서 그것을 만드는 사람 쪽으로 한 걸음 물러났다.
그러니 요즘 개발이 재미없다고 느끼는 사람에게 이 글이 건네고 싶은 말은 이것이다. 그 상실감은 진짜이고, 부끄러운 것이 아니다. 다만 무대는 옮길 수 있다. 낮에는 계약을 긋고, 셋을 만들어 둘을 버리고, 시스템의 물성과 씨름하고, 도구를 벼리는 더 큰 아름다움으로. 밤에는 다이얼을 돌려 무엇이든 내 손에 맞게 길들이는 작은 아름다움으로.
이 글 역시 정답이 아니라 한 사람의 고민의 흔적일 뿐이다. 3년 뒤에는 지금 쓴 문장 중 몇 개가 또 낡아 있을 것이다. 그래도 하나는 그때에도 유효하리라 믿는다. 도구가 무엇을 대신 만들어주든, 무엇이 아름다운지 스스로 묻기를 멈추지 않는 사람만이 아름다운 것을 만든다.
Footnotes
-
다게레오타입(Daguerreotype)은 1839년 프랑스에서 공개된 최초의 실용적 사진술로, 은도금 판 위에 상을 고정하는 방식이다. 초기에는 한 장을 찍는 데 수 분에서 수십 분의 노출이 필요했다. ↩
-
널리 인용되는 일화지만 정확한 출처는 분명하지 않다. 실제 들라로슈는 이후에도 그림을 그렸고 사진을 회화의 보조 도구로 긍정적으로 평가하기도 했다. ↩
-
데이터 지향 설계(Data-Oriented Design)는 객체의 논리적 관계보다 CPU 캐시가 데이터를 읽는 물리적 순서에 맞춰 자료 구조를 배치하는 설계 방식이다. 성능이 생명인 게임 엔진 분야에서 널리 쓰인다. ↩
-
철학자 티머시 콜번(Timothy Colburn)은 소프트웨어를 '구체적 추상(concrete abstraction)'이라 불렀다. 프로그램은 수학적 대상처럼 추상적이지만 실행되는 순간 물리 세계에 인과를 만든다는 점에서 여느 추상과 다르다. 프로그램이 발견되는 것인지 발명되는 것인지, 텍스트인지 과정인지는 컴퓨터과학철학의 주요 주제다. ↩
-
피터 기치(Peter Geach)는 「선과 악」(1956)에서 '좋다'가 술어적(predicative) 형용사가 아니라 한정적(attributive) 형용사임을 논증했다. '노란 새'는 노랗다와 새다로 쪼갤 수 있지만 '좋은 칼'은 좋다와 칼로 쪼개지지 않는다. 좋음은 언제나 어떤 종류의 것으로서의 좋음이라는 뜻이다. 뒤에 나올 칸트의 부수미, 즉 대상이 무엇을 위한 것인지 알아야 보이는 아름다움과 정확히 포개지는 구분이다. ↩
-
이 글이 기대는 미학 이론은, 대상의 쓸모와 무관한 관조를 요구하는 고전적 미학(칸트의 무관심성)보다는 잘 기능하는 것에서 아름다움을 찾는 기능미(functional beauty)와 일하는 경험 자체에서 미적 경험을 찾는 일상 미학의 전통에 가깝다. 전작에서 아름다움을 편안함과 생존의 감각으로 정의한 것과 같은 노선이다. ↩
-
인류학자 메리 더글러스(Mary Douglas)가 『순수와 위험』(1966)에서 분석의 중심에 놓은 정의로, 원문은 "matter out of place"다. 문구 자체는 더글러스 이전부터 통용되던 오래된 정의이고, 어떤 것이 더러운지는 물질 자체가 아니라 그것이 놓인 질서가 결정한다는 이론으로 벼려낸 것이 그의 공헌이다. ↩
-
수학자 잔카를로 로타(Gian-Carlo Rota)는 수학적 아름다움의 정체를 '깨달음(enlightenment)'으로 분석했다. 증명이 아름다운 것은 짧아서가 아니라 왜 참인지가 한순간에 보이기 때문이라는 것이다. 전작에서 놀라운 아름다움의 조건으로 깨달음을 꼽았던 것과 같은 이야기다. ↩
-
함수가 성공 값 또는 실패 정보를 하나의 반환 타입에 담아 돌려주는 방식이다. 예외와 달리 실패가 타입 시스템에 드러나기 때문에 호출자가 처리를 강제받는다. 이를 활용한 코드 구성은 Railway-Oriented Programming 글에서 다룬 적이 있다. ↩
-
라이스의 정리(Rice's theorem). 프로그램의 자명하지 않은 의미론적 성질은 모두 결정 불가능하다는 정리로, 정지 문제의 일반화다. 정적 분석 도구가 근사와 보수적 추정에 의존할 수밖에 없는 이유이기도 하다. ↩
-
임마누엘 칸트가 『판단력비판』(1790)에서 분석한 취미판단의 성격으로, 주관적 보편성이라 불린다. 덧붙여 칸트는 용도의 개념에 의존하는 부수미와 그렇지 않은 자유미를 구분했는데, 무엇을 하는 함수인지 알아야만 아름다움이 보이는 코드는 명백히 부수미의 영역이다. 앞서 말한 기능미의 노선과 이어지는 이야기다. ↩
-
비트루비우스의 『건축십서』(기원전 1세기)는 건축의 세 원리로 견고함(firmitas), 유용함(utilitas), 아름다움(venustas)을 꼽았다. 이 글의 어휘로 옮기면 탄탄함과 쓸모와 아름다움이 처음부터 한 목록에 있었던 셈이다. 소프트웨어가 건축에서 아키텍처라는 말을 빌려올 때, 세 번째 기둥도 함께 딸려 왔다고 보아야 한다. ↩
-
Peter Naur, "Programming as Theory Building"(1985). 나우르는 프로그램의 죽음을 코드의 소실이 아니라 이론을 가진 팀의 해체로 정의했다. 텍스트가 남아 있어도 이론이 없으면 그 프로그램은 죽은 것이며, 되살리는 길은 문서 인계가 아니라 새 팀이 이론을 다시 짓는 것뿐이라는 급진적인 주장이다. ↩
-
현대 인식론은 이해(understanding)를 참인 믿음의 누적이 아니라 대상의 구조와 의존 관계를 통째로 파악(grasping)하는 별개의 인식 상태로 다룬다. 린다 자그젭스키와 조너선 크반비그의 논의가 대표적이다. 모든 줄을 검증하고도 시스템을 이해하지 못하는 일이 가능한 이유다. ↩
-
물론 인상주의의 등장에는 튜브 물감의 발명이나 일본 판화의 유입 같은 다른 동력도 있었고, 드가처럼 사진을 작업 도구로 적극 끌어들인 화가도 있었다. 다만 재현의 독점이 깨졌다는 조건 변화가 이 실험들의 공통 배경이었다. ↩
-
LLM은 주어진 컨텍스트를 바탕으로 다음 토큰을 예측하는 모델이므로, 코드베이스의 일관성은 곧 예측 정확도의 재료가 된다. 이에 대한 자세한 이야기는 만약 당신이 클로드 블루 때문에 힘들다면을 참고하자. ↩
-
ADR(Architecture Decision Record)은 아키텍처 결정과 그 배경, 검토했던 대안을 짧은 문서로 남기는 관례다. 결정의 결과는 코드에 남지만 이유는 코드에 남지 않기 때문에 필요하다. ↩
-
루트비히 비트겐슈타인이 『철학적 탐구』(1953)에서 전개한 규칙 따르기(rule-following) 논의. 어떤 규칙도 그 적용을 스스로 완전히 결정하지 못하며, 규칙과 적용 사이의 틈은 실천 속의 판단이 메운다는 통찰이다. ↩
-
찰스 샌더스 퍼스가 1878년에 정식화한 실용주의 격률(pragmatic maxim)은 개념의 의미를 그것이 낳을 실천적 효과의 총합으로 정의한다. 윌리엄 제임스는 진리를 "장기적으로, 그리고 전체적으로 보아 작동하는 것"으로 다시 썼다. 두 정식 모두에서 핵심은 장기와 총합이다. 결과의 지평을 좁히는 순간 실용주의는 실용주의이기를 멈춘다. 그리고 이 전통을 교육, 정치, 예술로 확장해 완성한 인물이 존 듀이다. ↩
-
코드 스멜(code smell)은 켄트 벡이 만들고 마틴 파울러의 『리팩터링』(1999)을 통해 널리 퍼진 용어로, 더 깊은 문제가 있을지 모른다는 표면의 징후를 가리킨다. 정의부터가 판정이 아니라 단서다. ↩
-
한스 라이헨바흐가 정식화한 발견의 맥락(context of discovery)과 정당화의 맥락(context of justification)의 구분. 가설에 도달한 경로와 가설을 받아들일 이유는 별개의 심사를 받는다는 과학철학의 고전적 구분이다. ↩
-
퍼스는 가추를 새로운 생각을 들여오는 유일한 논리적 조작이라 불렀고, 그것이 본능에 가까운 추측 능력의 형태로 온다고 봤다. 퍼스가 이 글에 세 번 등장하는 것은 우연이 아니다. 실용주의 격률, 가추, 그리고 뒤에 나올 단언의 책임. 전부 생각과 행동 사이의 회계를 다루는 철학이다. ↩
-
대니얼 카너먼과 게리 클라인이 「직관적 전문성의 조건: 불일치의 실패」(2009)에서 도달한 합의다. 직관을 신뢰할 수 있으려면 환경에 통계적 규칙성이 있어야 하고, 그 규칙성을 학습할 만큼 빠르고 잦은 피드백이 있어야 한다. 체스와 소방은 이 조건을 만족하고 주식 시장은 만족하지 못한다. 소프트웨어는 영역에 따라 그 사이 어딘가에 걸쳐 있다. ↩
-
속성 기반 테스트(property-based testing)는 개별 사례 대신 항상 성립해야 할 성질을 선언하고, 도구가 수천 개의 무작위 입력을 생성해 반례를 찾는 기법이다. Haskell의 QuickCheck에서 시작해 대부분의 언어에 이식되었고, 반례를 찾으면 최소 형태로 줄여서(shrinking) 보여준다. ↩
-
리처드 세넷(Richard Sennett), 『장인』(2008). 그는 장인 정신을 특정 직업의 이름이 아니라 일을 그 자체로 잘 해내려는 인간의 오래된 충동으로 정의하고, 손과 머리가 갈라지지 않는 노동을 그 조건으로 꼽았다. 만드는 일이 곧 생각하는 일이라는 그의 논지는 뒤에 나올 나우르의 이론 구축과 같은 곳을 가리킨다. ↩
-
존 듀이(John Dewey), 『경험으로서의 예술』(1934). 앞서 언급한 일상 미학의 전통이 듀이에서 발원한다. 재료의 저항, 행함과 겪음의 리듬, 하나의 경험으로의 완결이 미적 경험의 조건이라는 그의 분석은 뒤에 나오는 다듬는 재미의 세 가지 재료와 정확히 겹친다. ↩
-
가구 디자이너 데이비드 파이(David Pye)는 결과가 작업자의 손끝에 달려 있어 언제든 망칠 수 있는 작업(workmanship of risk)과 기계가 결과를 보장하는 작업(workmanship of certainty)을 구분했다. 손으로 쓰는 코드와 생성되는 코드의 관계와 정확히 겹치는 구분이다. ↩
-
우리는 이미 컴파일러가 생성한 기계어를 읽지 않으니 위임의 층이 하나 올라간 것뿐이라는 반론이 가능하다. 하지만 컴파일러에 대한 신뢰는 언어 의미론이 보존된다는 보장 위에 선 연역적 신뢰고, LLM에 대한 신뢰는 대체로 잘하더라는 실적에 기대는 증언적 신뢰다. 물론 컴파일러에도 버그는 있다. 다만 그 어긋남은 언어 의미론이라는 준거에 대한 위반으로 판정되고 교정되는 반면, LLM의 출력에는 위반을 판정할 준거 자체가 없다. 같은 위임이라는 단어를 쓰지만 인식론적 범주가 다르며, 스펙에서 코드로 가는 길이 컴파일이 아니라 번역인 한 이 간극은 도구가 좋아져도 범주적으로는 닫히지 않는다. ↩
-
세넷은 같은 책에서 CAD가 건축 설계에 가져온 변화를 다루며, 손으로 부지를 그리는 동안 생기던 이해가 화면 위의 설계에서는 생기지 않는다는 건축가들의 증언을 전한다. 그는 이것을 도구가 과정을 너무 매끄럽게 만들 때 치르는 대가로 봤다. ↩
-
아리스토텔레스가 『니코마코스 윤리학』에서 학문적 지식(에피스테메), 제작 기술(테크네)과 구분한 실천지(프로네시스). 집을 지어봐야 건축가가 되고 거문고를 타봐야 연주자가 된다는 유명한 문장은 덕과 기술의 습득 일반에 대한 말이고(2권), 실천지에 대해서는 따로 못박았다(6권). 젊은이는 수학자는 될 수 있어도 실천지를 가진 자는 될 수 없는데, 실천지가 요구하는 개별 상황의 경험에는 지름길이 없기 때문이다. 마이클 폴라니가 『개인적 지식』에서 다룬 암묵지와 감식안 논의도 같은 계보다. ↩
-
티머시 윌리엄슨(Timothy Williamson)이 정식화한 단언의 지식 규범(knowledge norm of assertion). 아는 것만 단언하라는 규범이 단언이라는 언어 행위를 성립시키는 구성적 규칙이라는 논증이다. 모르면서 단언하는 사람은 규칙을 어기는 참가자지만, 아는 상태와 모르는 상태를 구별하는 능력 자체가 없는 화자는 애초에 이 게임의 참가자가 아니다. ↩
-
퍼스는 단언을 명제의 진리에 대해 책임을 떠맡는 행위로 정의했고, 브랜덤은 이유를 주고받는 게임에서 책무를 인수하는 수로 분석했다. 단언의 본질이 정보 전달이 아니라 책임 인수라는 이 계보에서 보면, 책임질 수 없는 존재의 출력은 단언의 겉모습을 한 다른 무엇이다. ↩