티스토리 뷰

개발

추상화란

개발자 아무개 2026. 6. 20. 17:01

최근 대부분의 개발 업무를 에이전틱 프로그래밍으로 진행하고 있다. 이런 형태의 개발을 하게 된 것도 이제는 1년이 다 되어가는 것 같다.

 

마틴 파울러의 정의에 따르면 바이브 코딩은 사람이 코드를 아예 들여다보지 않는, 심지어 코드의 존재 자체를 잊어버리는 방식이고, 에이전틱 프로그래밍은 코드를 여전히 신경 쓰고 꼼꼼히 리뷰하는 방식이다. 개인적으로 나도 이렇게 AI를 활용한 개발 방법론을 분류 해야한다고 생각한다. 코드를 어느 정도까지 LLM에게 맡길지 결정하는 위임의 수준은 프로젝트 마다 달라야 하며 모든 프로젝트를 바이브 코딩할 수 없다고 생각하기 때문이다.

참고: Martin Fowler, Agentic Programming — "Humans are still responsible for what the software does and how it works."

 

 

나중에 AI를 어떻게 다뤄야 하는가에 대한 고민을 담은 글을 작성할 예정인데, 그 글에서 이 "회색 지대"와 "위임" 같은 내용에 대해서 다뤄볼 생각이다.

 

오늘 내가 다루려고 하는 주제는 "추상화"다. 에이전틱 프로그래밍을 하면서 많이 느끼는 감정중 하나는 AI가 추상화에 약하다는 것이다.

 

추상화란

추상화란 복잡한 사물, 현상, 또는 개념에서 핵심적이고 공통적인 특징만을 추출하고, 불필요한 세부 사항은 생략해 단순화하는 과정을 말한다.

 

소프트웨어 엔지니어링에서 추상화란 세부적인 구현체를 숨기고 단순한 인터페이스만을 노출시키는 방법을 의미한다.

 

추상화를 알아보며 플라톤과 아리스토텔레스의 추상화에 대한 철학을 알게 되었는데 이 또한 재밌는 지점이었다. 플라톤은 추상화가 이미 있는 원형(이데아)에서 내려온다고 보았다. 정답이 어딘가에 먼저 있고, 감각의 잡음을 걷어내며 그 정답에 가까워진다는 것이다.

 

관련해서 우연히 찾은 재밌는 글: https://blog.naver.com/beyond-zero/223725086164

 

 

그리고 아리스토텔레스는 정답은 처음부터 주어지는 게 아니라 충분히 겪은 뒤에 올라온다고 얘기한다. 개별 사례들을 충분히 경험하고 나서야 그 안에서 공통의 형상이 귀납적으로 떠오른다는 것이다.

 

예를 들어 우리가 '의자'라는 개념을 정의한다고 해보자.

  • 플라톤의 방식은 머릿속에 '인간이 엉덩이를 붙이고 편하게 앉을 수 있는 완벽한 가구'라는 이상적인 의자의 원형(이데아)을 먼저 선언하는 것이다. 완벽한 개념적 도면이 먼저 존재하고, 현실의 목수는 그 이데아를 모방하여 나무를 깎고 다리를 붙인다. 현실의 의자들은 그 완벽한 원형에 가까워지려는 불완전한 시도들이다.
  • 반면 아리스토텔레스의 방식은 다르다. 등받이가 있는 나무 쪼가리, 거실의 폭신한 가죽 소파, 길가의 둥근 그루터기 등 사람들이 무언가에 앉아 있는 구체적인 개별 사례들을 수없이 경험하는 것이 먼저다. 이 수많은 변종을 겪고 나서야 "아, 모양과 재질은 달라도 이것들은 결국 '인간을 앉히기 위한 목적'이라는 공통의 형상을 품고 있구나"라며 '의자'라는 추상적 개념을 사후에 귀납적으로 추출해 낸다.

개인적으로 이 얘기가 소프트웨어 엔지니어링에서 추상화를 수행하는 방법과 대비되어서 재밌었다. 우리가 인터페이스를 먼저 설계하고 구현을 맞춰 나가는 하향식 설계는 플라톤적이고, 중복이 세 번쯤 보일 때까지 기다렸다가 공통 모듈을 뽑아내는 Rule of Three는 아리스토텔레스적이다.

 

미술에는 추상화와 구상화가 있다. 추상화의 아버지인 칸딘스키는 원래는 구상화를 그리던 화가였다. 눈앞의 풍경과 사물을 있는 그대로 캔버스에 옮기는 일을 했다. 그러다 바그너의 오페라 로엔그린을 듣던 어느 날 기묘한 체험을 한다.

 

음악에는 형태가 없다. 바이올린 선율을 눈으로 볼 수 없고, 화음의 모양을 손으로 만질 수도 없다. 그런데 음악은 감동을 준다. 형태 없이도 본질에 닿는다.

 

칸딘스키는 생각했다. 그림도 그럴 수 있지 않을까? 사과의 형태를 그리지 않고도, 사과가 주는 감각에 닿을 수 있지 않을까? 풍경의 윤곽을 버리고, 풍경이 불러일으키는 감정만 남길 수는 없을까?

 

그는 캔버스에서 구체적인 형상을 하나씩 걷어내기 시작했다. 나무와 산과 사람 대신 선과 색과 면만 남겼다.

 

결론적으로 추상화란 결국 익숙한 감각을 전달하는 일이 아닐까 하는 생각을 하게 되었다. 플라톤의 방식이든 아리스토텔레스의 방식이든, 미술에서든 소프트웨어 엔지니어링에서든, 잘 추상화된 무언가를 보았을 때 우리는 직관을 통해 감각적으로 그 의미를 전달받는다.

 

인간은 추상화 위에서 산다

인간의 작업 기억은 형편없이 작다. 조지 밀러의 고전적인 연구가 말하는 "마법의 수 7±2"처럼, 우리가 한 번에 머리에 담을 수 있는 항목은 몇 개 되지 않는다. 그런데도 인간이 거대한 복잡성을 다룰 수 있는 이유는 단 하나, 청킹(chunking)이다. 여러 개의 정보를 하나의 덩어리로 묶어 단일한 핸들로 다룬다.

 

생각해 보면 우리는 추상화 없이는 한 문장도 말할 수 없다. "사과"라는 단어 자체가 추상화다. 세상에 완전히 동일한 사과는 두 개도 없지만, 우리는 수만 개의 서로 다른 빨갛고 둥근 과일을 "사과"라는 하나의 기호로 압축해서 다룬다. 돈은 가치의 추상화고, 지도는 땅의 추상화고, 자동차의 액셀 페달은 내연기관(혹은 모터)의 추상화다. 운전자는 연료 분사 타이밍을 몰라도 차를 몬다.

 

인간은 복잡한 것을 견디지 못한다. 그래서 끊임없이 복잡한 것을 감추고, 이름을 붙이고, 인터페이스 뒤로 밀어 넣는다. 이건 인간의 약점(작은 작업 기억)이 만들어낸 생존 전략이고, 동시에 인간 지능의 핵심 능력이다.

 

흥미로운 건 망각조차 추상화의 도구라는 점이다. 우리는 어제 읽은 코드의 세부를 잊는다. 하지만 "그 훅은 필터 패널 상태를 관리하는 거였지"라는 요약은 남는다. 모든 것을 기억하는 존재는 오히려 추상화할 이유가 없다.

 

감각은 어떻게 전달되는가

앞서 좋은 추상화는 "직관을 통해 감각적으로" 의미를 전달한다고 했는데, 이 "감각"이라는 게 대체 뭘까? 코드를 읽다가 "아, 이건 깔끔하다"거나 "뭔가 냄새가 난다"고 느낄 때, 그 느낌은 어디서 오는 걸까. 막연한 신비주의처럼 들리기 쉬운 이 이야기에는 의외로 단단한 인지과학적 설명이 있다.

 

1973년 체이스와 사이먼의 고전적인 체스 실험이 있다. 체스 마스터와 초보자에게 실제 대국 중의 말 배치를 몇 초만 보여주고 재현하게 하면, 마스터는 거의 완벽하게 복기하지만 초보자는 몇 개밖에 못 맞춘다. 그런데 말을 무작위로 흩어 놓으면 마스터의 우위가 거의 사라진다. 마스터의 기억력이 좋은 게 아니다. 마스터는 개별 말을 외우는 게 아니라, 의미 있는 배치를 하나의 덩어리(chunk)로 묶어서 본다. 무작위 배치에는 그 덩어리가 없으니 우위도 없다.

 

 

청킹 이론에 따르면, 전문가가 어떤 분야에서 요소들 사이의 의미 있는 관계를 인식할 때 작업 기억에 청크가 만들어지고 그 패턴 정보는 장기 기억에 저장된다. 그리고 경험이 쌓이면 이 장기 기억에 기반한 빠르고 자동적인 패턴 인식이 작동해서, 수많은 작은 요소로 이루어진 복잡한 상황을 소수의 큰 덩어리로 순식간에 부호화한다. 다시 말해 전문가의 직관은 타고난 처리 속도나 더 큰 작업 기억에서 나오는 게 아니라, 축적된 도메인 지식과 반복되는 패턴을 감지하는 민감성에서 나온다. 일종의 정보 압축인 셈이다.

 

이게 코드를 읽는 경험과 정확히 겹친다. 숙련된 개발자가 어떤 함수의 이름을 보고 0.5초 만에 "아 이건 그거"라고 느낄 때, 그의 머릿속에서는 글자를 한 자씩 해석하는 일이 벌어지는 게 아니다. 이 이름이 장기 기억에 저장된 수백 개의 패턴 중 하나를 즉시 활성화시키고, 그 덩어리가 통째로 의식에 떠오른다.

 

좋은 추상화가 "익숙한 감각"을 전달한다는 말의 정체가 이것이다. 좋은 이름과 좋은 경계는 읽는 사람의 장기 기억에 이미 들어 있는 덩어리를 정확히 호출하는 열쇠다. 반대로 나쁜 추상화는 어떤 덩어리도 깔끔하게 호출하지 못한다. 그래서 우리는 파일을 열어 함수 구현을 한줄한줄 읽으며, 느린 의식적 처리로 직접 덩어리를 재구성해야 한다. 인지 부하란 바로 이 재구성 비용이다.

 

직관의 정체를 좀 더 밀고 들어가면 이중 처리 이론(dual-process theory)에 닿는다. 카너먼이 대중화한 System 1과 System 2의 구분이다. System 1은 빠르고 자동적이고 노력이 들지 않으며 연상적이다. System 2는 느리고 의식적이고 노력이 들며 규칙에 따라 작동한다. 흥미로운 건 카너먼이 직관(System 1)의 작동 방식이 지각(perception)과 매우 닮았다고 본 점이다.

 

우리가 사과를 "보는" 데 노력이 들지 않듯, 전문가가 좋은 코드를 "좋다고 느끼는" 데도 노력이 들지 않는다. 둘 다 System 1이 즉각 내놓는 출력이다. 코드의 "감각"이나 "테이스트"가 논리적 설명에 앞서 먼저 오는 이유, 그리고 왜 그렇게 느끼는지 말로 풀기 어려운 이유가 여기 있다. 그것은 추론의 결론이 아니라 지각에 가까운 무엇이기 때문이다.

 

게리 클라인의 인식 기반 의사결정(RPD) 모델은 이 그림을 현장에서 확인해 준다. 클라인은 소방관과 응급실 간호사, 군 지휘관 같은 전문가들이 촌각을 다투는 상황에서 어떻게 결정하는지를 연구했는데, 이들은 여러 선택지를 나란히 비교하지 않았다. 대신 현재 상황을 과거의 원형(prototype)과 비슷한 것으로 인식하고, 하나의 행동을 떠올리고, 그것을 머릿속으로 시뮬레이션해 보고, 괜찮아 보이면 실행했다. 클라인은 이걸 직관과 분석의 혼합이라고 불렀다. 패턴 매칭이 직관적인 부분이고, 멘탈 시뮬레이션이 의식적이고 분석적인 부분이다. 좋은 엔지니어가 코드를 보고 "여기가 잘못됐다"고 느낀 뒤(직관) 왜 그런지 따져보고 대안을 그려보는(분석) 과정이 정확히 이 구조다.

 

 

카너먼과 클라인은 학문적으로 대립하던 사이였는데, 2009년 "직관적 전문성의 조건"이라는 공동 논문에서 한 가지에 합의했다. 직관적 전문성이 신뢰할 만하려면 두 조건이 필요하다는 것이다. 첫째, 환경이 충분히 규칙적이어서 유효한 단서가 존재해야 한다. 둘째, 전문가가 빠르고 명확한 피드백을 통해 그 규칙성을 오랫동안 학습할 기회를 가졌어야 한다. 둘 중 하나라도 깨지면, 그 확신에 찬 직관은 체계적인 오류가 된다.

 

전문가가 자기 분야 안에서 패턴을 인식할 때와, 경계를 모르고 넘어가 표면적 유사성으로 잘못된 패턴을 끌어다 쓸 때, 두 경우의 주관적 경험은 똑같다. 둘 다 빠르고 확신에 차 있고 주관적으로 설득력 있다. 진짜 패턴을 알아본 것과 표면적 유사성을 오인한 것 사이에 느낌상의 차이가 없다는 것이다. 이게 AI의 추상화에서 벌어지는 일에 대한 가장 정확한 묘사라고 생각한다. AI는 표면적 유사성을 진짜 구조로 오인하면서도, 그 둘을 구분할 내적 신호가 없다.

 

소프트웨어의 역사는 추상화 수준의 상승이다

기계어의 0과 1을 견디지 못해 어셈블리가 나왔고, 어셈블리의 레지스터를 견디지 못해 고급 언어가 나왔다. 수동 메모리 관리를 견디지 못해 가비지 컬렉터가 나왔고, DOM 조작을 견디지 못해 React가 나왔다. SQL은 데이터를 "어떻게" 가져올지가 아니라 "무엇을" 원하는지만 선언하게 했고, 클라우드는 서버라는 물리적 실체마저 감췄다.

 

 

각 단계마다 우리는 아래 계층의 세부 사항을 잊을 권리를 얻었다. 다익스트라는 이렇게 말했다. "추상화의 목적은 모호해지는 것이 아니라, 절대적으로 정밀할 수 있는 새로운 의미론적 수준을 만드는 것이다." 추상화는 정보를 줄이는 게 아니라, 더 높은 수준에서 정확하게 사고할 수 있는 새 언어를 만드는 일이다.

 

그리고 지금, 자연어로 의도를 말하면 코드가 나오는 시대가 왔다. 카파시가 말하는 Software 3.0이다. 어떤 의미에서 LLM은 역사상 가장 높은 추상화 계층이다. 그런데 아이러니하게도, 이 가장 높은 추상화 계층이 정작 추상화를 만드는 일에는 서툴다.

 

왜 AI의 추상화는 별로일까?

개인적으로 AI가 추상화를 못한다고 생각한다. 최근 내가 Data Grid의 필터 기능을 추가하는 작업을 진행했는데, 해당 코드에서의 몇 가지 예를 가져와 보려 한다. 내가 느끼기에 보통 AI는 추상화를 한다기보단, 표면적인 유사성을 기반으로 단순하게 묶어내는 경우가 많았다.

 

예제: useTableFilterBarState

이 훅은 이름상 "필터바 상태"를 다루는 것처럼 보이지만, 실제로는 너무 많은 상태를 한 번에 품고 있었다.

 

 

useTableFilterBarState는 얼핏 보면 필터바의 상태를 관리하는 합리적인 추상화처럼 보인다. 하지만 내부를 열어보면 패널의 열림 여부, 조합자 드롭다운의 열림 여부, 단계 전환, 검색어, 입력값, 선택된 컬럼과 연산자가 모두 들어 있다.

 

이 훅의 묶음 기준을 들여다보면 두 가지다. "이것들은 모두 상태다"라는 종류, 그리고 "이것들은 모두 필터바에 있다"라는 위치. 즉 분류로 묶었다. "무엇인가"를 기준으로 묶었을 뿐, "왜 변하는가"를 기준으로 묶지 않았다. 패널의 열림 여부는 disclosure UX가 바뀔 때 변하고, wizard step은 필터 생성 플로우가 바뀔 때 변하고, 검색어는 컬럼 탐색 요구사항이 바뀔 때 변한다. 서로 다른 이유로 변하는 일곱 개의 상태가 "상태"라는 종류와 "필터바"라는 주소를 공유한다는 이유만으로 한 지붕 아래 모인 것이다.

 

그래서, 왜 못하는 걸까

이 현상의 원인을 몇 가지로 정리해 볼 수 있을 것 같다.

 

첫째, AI에게는 아리스토텔레스의 길이 닫혀 있다.

 

앞에서 좋은 추상화는 충분히 겪은 뒤에 올라온다고 했다. 그런데 LLM은 이 코드베이스를 "겪은" 적이 없다. 6개월 전 이 훅이 왜 이렇게 생겼는지, 지난 분기에 어떤 요구사항 변경이 이 경계를 흔들었는지, 어떤 수정이 유독 고통스러웠는지에 대한 누적된 경험이 없다. 매 세션이 백지에서 시작한다.

 

경험의 연속성이 없는 존재에게 귀납적 추상화는 구조적으로 불가능하다. AI가 할 수 있는 건 기형적인 플라톤주의뿐이다. 학습 데이터에 존재하는 "Data와 Handlers로 나누는 패턴" 같은 기성품 이데아를 가져와서 현재 코드에 덮어씌우는 것. 그 패턴이 이 문제의 형상인지 묻지 않고.

 

둘째, 다음 토큰 예측은 국소적으로 최적화한다.

 

추상화는 본질적으로 전역적인 행위다. "이 일곱 개의 상태 중 어떤 것들이 같은 이유로 변하는가"를 판단하려면 코드베이스 전체의 변경 이력과 미래의 요구사항 방향까지 머리에 그려야 한다.

 

반면 토큰을 순서대로 생성하는 모델은 지금 이 자리에서 가장 그럴듯한 다음 코드를 쓰는 데 최적화되어 있다. 국소적으로 그럴듯한 코드의 합이 전역적으로 좋은 구조가 되리란 보장은 없다.

 

줄 수를 줄이라고 하면 줄 수를 줄인다. 인지 부하를 줄이는 것과는 다른 문제인데, 인지 부하는 측정해서 보상할 수 없고 줄 수는 할 수 있기 때문이다.

 

셋째, AI는 나쁜 추상화의 대가를 치르지 않는다.

 

인간 개발자가 추상화를 신중하게 하는 이유는 단순하다. 잘못 만든 추상화의 고통을 3개월 뒤의 자신이 겪기 때문이다. 새벽에 그 코드를 다시 열어 본 경험, 경계를 잘못 그어서 수정 하나에 파일 다섯 개를 건드린 경험이 감각을 만든다.

 

AI에게는 유지보수라는 미래가 없다. 결과물에 대한 장기적 이해관계가 없는 존재는 "지금 동작하는 코드"와 "오래 살아남는 코드"를 구분할 유인이 없다.

 

넷째, 평균의 중력.

 

LLM은 인터넷에 존재하는 코드의 분포를 학습했고, 그 분포의 대부분은 평범한 코드다. 좋은 추상화는 정의상 분포의 꼬리에 있다. 흔한 패턴(Data/Handlers 분리, utils 폴더, helpers 파일)은 흔하기 때문에 강하게 학습되어 있고, 모델은 자연스럽게 그쪽으로 끌린다. AI의 추상화가 "틀렸다"기보다 "지독하게 평균적"으로 느껴지는 이유가 여기 있다고 생각한다.

 

여기서 한 가지는 공정하게 짚고 가야 한다. AI가 추상화의 "지식"이 없는 건 아니다. 응집도와 결합도에 대해 물어보면 교과서적인 답을 한다. 문제는 그 지식을 지금 이 코드의 맥락에 적용하는 판단이다. 추상화에 대해 말하는 것과 추상화를 하는 것은 다르다. 그리고 그 간극이 바로 "감각" 혹은 "테이스트"라고 불리는 것의 정체일 것이다.

 

추천글: https://parul.substack.com/p/taste-is-the-new-10x

 

그래서 좋은 추상화란

AI가 못하는 것을 비판하는 건 쉽다. 더 중요한 질문은, 그래서 좋은 추상화란 무엇이고 우리는 그걸 어떻게 판단하느냐다. 내가 생각하는 기준은 이렇다.

 

1. 익숙한 감각을 전달해야 한다

좋은 추상화를 마주하면 내부를 열어보기 전에 이미 안다. 이름과 인터페이스만 보고도 "아, 이건 그거구나"라는 감각이 온다. 그게 어떤 컴포넌트의 훅인지가 아니라, 무엇을 수행하는 훅인지를 알게 해야 한다. useTableFilterBarState는 위치를 말하고, useFilterPanelDisclosure는 행위를 말한다.

 

최근 토스의 Frontend Fundamentals에서 코드와 UI 구조가 1대1로 대응되게 짜는 것이 좋다고 한 것도 같은 맥락이라고 생각한다. 화면에서 필터 패널을 보고 코드에서 FilterPanel을 찾을 수 있을 때, 코드를 읽는 사람은 UI라는 이미 가진 감각을 그대로 코드 탐색에 재사용한다. 새로운 멘탈 모델을 만들 필요 없이, 익숙한 감각 위에 코드가 얹히는 것이다. 추상화가 전달해야 하는 게 바로 이 "이미 아는 느낌"이다.

 

칸딘스키로 돌아가면, 그가 형태를 지우고도 그림이 성립했던 이유는 색과 선이 음악처럼 감각을 직접 전달했기 때문이다. 코드의 추상화도 마찬가지다. 구현을 지우고도 이름과 시그니처가 의미를 직접 전달해야 한다. 구현을 지웠는데 의미까지 같이 지워졌다면(Data, Handlers, utils, common...) 그건 추상화가 아니라 은폐다.

 

2. 분류가 아니라 목적으로 묶어야 한다

추상화의 본체는 이름이 아니라 경계다. 어디까지를 안으로 넣고 어디부터를 밖에 둘 것인가. 그리고 경계를 긋는다는 건 결국 "무엇을 기준으로 묶을 것인가"라는 질문이다.

 

코드를 묶는 기준은 크게 두 가지가 있다. 하나는 분류다. "이것은 무엇인가"를 묻고, 같은 종류끼리 묶는다. components, hooks, utils, types 폴더가 이 방식이다. 다른 하나는 목적이다. "이것은 무엇을 위해 존재하고, 무엇이 바뀔 때 함께 바뀌는가"를 묻고, 같은 이유로 변하는 것끼리 묶는다.

 

FSD(Feature-Sliced Design)가 정확히 이 지점을 지적한다. FSD 공식 문서는 세그먼트 이름이 목적(the why)을 설명해야지 본질(the what)을 설명해서는 안 된다고 말하며, components나 hooks 같은 이름을 나쁜 예로 든다. 그런 폴더는 파일이 "무엇인지"는 말해주지만 안의 코드를 찾는 데 도움이 안 되고, 서로 관련 없는 코드를 물리적으로 가까이 묶어두기 때문에 리팩토링의 영향 범위가 넓어진다는 것이다.

 

그런데 왜 분류로 묶으면 응집도가 떨어질까? 직관적으로는 이상하다. 같은 종류끼리 모아두는 건 정리정돈처럼 느껴지니까. 답은 유사성과 관련성이 다른 개념이라는 데 있다.

 

응집도의 고전적 정의로 돌아가 보자. 구조적 설계 시절 Yourdon과 Constantine은 응집도를 일곱 단계로 나눴는데, 그중 거의 최하위가 논리적 응집(logical cohesion)이다. 논리적 응집이란 요소들이 "논리적으로 같은 범주"라는 이유로 묶인 상태를 말한다. 모든 입출력 함수를 한 모듈에, 모든 핸들러를 한 파일에 모으는 식이다. 50년 전에 이미 "종류로 묶는 것"은 가장 약한 응집으로 분류되어 있었던 것이다.

 

 

useTableFilterBarData("이것들은 모두 데이터다")와 useTableFilterBarHandlers("이것들은 모두 핸들러다")는 논리적 응집의 사례다. 가장 높은 단계인 기능적 응집(functional cohesion)은 모든 요소가 하나의 목적에 기여하는 상태다. 즉 응집도라는 척도 자체가 처음부터 "종류의 동질성"이 아니라 "목적의 단일성"으로 정의되어 있다.

 

유사성으로 묶으면 응집도가 낮아지는 이유는, 변경이 종류를 따라 일어나지 않기 때문이다. "필터에 OR 조건을 추가해 주세요"라는 요구사항은 컴포넌트도, 훅도, 타입도, 핸들러도 한꺼번에 건드린다. 분류 기준으로 정리된 코드에서는 이 하나의 변경이 폴더 네 개로 흩어진다.

 

반대로 변경과 무관한 코드들, 그러니까 서로 다른 기능에 속한 핸들러들은 한 파일 안에서 어깨를 맞대고 있다. 함께 변할 것들은 흩어져 있고 함께 변하지 않을 것들은 모여 있는 상태. 이게 낮은 응집도의 정의 그 자체다.

 

 

그래서 묶음의 기준은 변경의 이유여야 한다. 이건 새로운 통찰이 아니라 소프트웨어 공학에서 가장 오래된 결론 중 하나다. 파나스(Parnas)는 1972년 논문에서 모듈 분해의 기준은 처리 단계가 아니라 "변할 가능성이 있는 설계 결정"이어야 하고, 각 모듈은 하나의 결정을 숨겨야 한다고 했다(정보 은닉). 로버트 마틴의 공통 폐쇄 원칙(CCP)은 "함께 변하는 클래스는 함께 묶어라"라고 말하고, 단일 책임 원칙의 본래 정의도 "모듈은 변경의 이유가 하나여야 한다"이다. 켄트 벡의 표현이 가장 간결하다. "함께 변하는 것은 함께 두라(things that change together stay together)."

 

useTableFilterBarState의 문제가 정확히 이거였다. 패널 disclosure와 wizard step과 검색어는 서로 다른 이유로 변한다. "상태"라는 같은 종류이고 "필터바"라는 같은 화면에 있다는 이유로 한 경계 안에 묶이는 순간, 그 추상화는 모든 변경의 교차로가 된다. 요구사항이 하나 바뀔 때마다 훅이 커지고, 훅이 커질수록 다음 변경은 더 어려워진다.

 

물론 분류 기준 묶음이 항상 틀린 건 아니다. 디자인 시스템의 공용 UI 컴포넌트들은 "종류"로 모여 있지만 문제가 없다. 그곳에서는 종류가 곧 변경의 이유와 일치하기 때문이다. Button과 Input은 "시각적 일관성"이라는 하나의 이유로 함께 변한다. 그러니 규칙은 "분류로 묶지 마라"가 아니다. "변경의 이유로 묶어라. 분류가 우연히 그것과 일치한다면 분류로 보여도 상관없다"이다.

 

존 아우스터하우트가 『A Philosophy of Software Design』에서 말한 깊은 모듈(deep module) 개념도 결국 경계 이야기다. 좋은 모듈은 단순한 인터페이스 뒤에 많은 기능을 감춘다. 인터페이스는 좁고 구현은 깊다. 반대로 얕은 모듈은 인터페이스의 복잡도가 구현의 복잡도와 비슷해서, 감추는 것이 거의 없다. Data/Handlers 분리가 정확히 얕은 모듈이었다.

 

 

반환값이 일곱 개인 훅은 사실상 아무것도 감추지 못한다. 호출하는 쪽이 일곱 개를 전부 알아야 하기 때문이다. 그리고 이건 우연이 아니다. 분류로 묶인 모듈은 구조적으로 얕을 수밖에 없다. 하나의 목적이 없으니 하나의 좁은 인터페이스로 요약될 수 없고, 결국 내용물을 전부 나열해서 내보내는 통로가 된다.

 

3. 세부 구현이 새어 나와서는 안 된다

조엘 스폴스키의 누수 추상화의 법칙(The Law of Leaky Abstractions)이 말하듯, 모든 추상화는 어느 정도 샌다. TCP는 신뢰성 있는 전송을 추상화하지만 네트워크가 끊기면 그 추상화는 깨진다. 완벽한 밀봉은 불가능하다. 하지만 그게 일상적인 누수를 정당화하지는 않는다.

 

훅이 facet source rows나 TanStack의 column 인스턴스를 그대로 반환하는 순간, 호출자는 그 추상화 아래에 TanStack Table이 있다는 사실을 알아야만 한다. 추상화를 썼는데도 그 아래 계층의 지식이 필요하다면, 사용자는 두 개의 계층을 동시에 이해해야 한다. 추상화가 인지 부하를 줄이는 게 아니라 한 겹 더 얹는 것이다. 누수는 추상화의 실패 신호이기 이전에, 경계를 잘못 그었다는 신호다.

 

4. 추상화 수준이 정렬되어야 한다

한 함수, 한 컴포넌트 안에서는 같은 높이의 언어로 말해야 한다. "필터를 추가한다" 옆에 "인덱스 i를 1 증가시킨다"가 나란히 있으면 읽는 사람의 머리는 고도를 오르내리느라 지친다.

 

이게 중요한 실용적 이유는 탐색 속도다. 코드를 읽는 시간의 대부분은 "내가 고쳐야 할 곳이 어디인가"를 찾는 시간이다. 추상화 수준이 정렬된 코드는 목차가 있는 책처럼 읽힌다. 높은 층위에서 대략의 위치를 잡고, 필요한 가지로만 내려간다. 정렬이 깨진 코드는 모든 줄을 읽어야 하는 책이다.

5. 분해는 먼저, 일반화는 나중에 — 타이밍

마지막은 타이밍이다. 흔히 "추상화는 미리 하지 마라", "잘못된 추상화보다 중복이 낫다"고들 한다. 샌디 메츠의 유명한 경고다. 그런데 이 말을 곧이곧대로 받으면 이상해진다. 우리는 복잡한 문제를 마주하면 코드를 한 줄도 쓰기 전에 추상화부터 하지 않나? 이 문제는 이런 구성으로 풀어야겠다고 쪼개고, 각 조각에 이름을 붙이고, 조각 사이의 인터페이스를 그린다. 그게 설계다. 그리고 그건 나쁜 행위이기는커녕, 복잡한 문제를 풀 때 반드시 필요한 행위다.

 

이 모순은 우리가 "추상화"라는 한 단어로 서로 다른 두 행위를 부르기 때문에 생긴다.

 

하나는 문제를 쪼개는 추상화(분해)다. 복잡한 문제를 다룰 수 있는 크기의 부분 문제들로 나누고, 각 부분에 개념적 경계를 긋는 일. 비르트가 말한 단계적 정제(stepwise refinement)가 이것이고, 파나스의 정보 은닉도 사실 "앞으로 변할 설계 결정"을 미리 예측해서 경계를 긋는 선행 행위다. TCP/IP의 계층도, CPU의 명령어 집합도 경험이 쌓인 뒤 귀납된 게 아니라 먼저 설계된 추상화다. 이런 추상화는 정의상 선행할 수밖에 없다. 문제를 쪼개지 않고는 풀기 시작할 수조차 없기 때문이다.

 

다른 하나는 코드를 묶는 추상화(일반화)다. 이미 존재하는 구현들에서 반복을 발견하고 공통 모듈을 뽑아내는 일. Rule of Three가 적용되는 곳이 여기고, 샌디 메츠의 경고도 정확히 여기에만 해당한다. 그의 원문 맥락 자체가 중복 제거 리팩토링이다. 변경의 축을 아직 모르는 상태에서 "언젠가 재사용하겠지" 하며 미리 일반화하는 것, 파울러가 투기적 일반화(speculative generality)라는 코드 냄새로 부르는 것이 진짜 죄목이다.

 

그러니 정확한 명제는 "추상화는 나중에"가 아니라 "분해는 먼저, 일반화는 나중에"다. 경계는 미리 긋되, 재사용은 벌어서 얻는다.

 

그럼 선행하는 분해는 어떻게 틀리지 않을 수 있나? 틀릴 수 있다. 틀린다는 걸 전제하는 게 핵심이다. 앞에서 플라톤과 아리스토텔레스의 두 길을 모두 걷는다고 했는데, 정확히는 이 둘이 루프를 이룬다. 선행 추상화는 문제 구조에 대한 가설이고(플라톤의 길), 구현과 운영의 경험이 그 가설을 검증하고 수정하며(아리스토텔레스의 길), 그렇게 누적된 감각이 다음 설계의 가설을 더 좋게 만든다.

 

아우스터하우트가 "두 번 설계하라(design it twice)"고 말하는 것도 선행 설계를 하지 말라는 게 아니라, 가설을 복수로 세우고 비교하라는 뜻이다. 좋은 엔지니어가 미리 긋는 경계가 자주 맞는 이유는 예언 능력이 있어서가 아니라, 틀려 본 경계의 데이터베이스가 머릿속에 있기 때문이다.

 

이 프레임으로 보면 AI의 문제도 더 정확하게 말할 수 있다. AI의 문제는 먼저 추상화한다는 것이 아니다. 인간도 먼저 한다. 문제는 두 가지다. AI의 선행 추상화는 이 문제의 분석이 아니라 학습된 패턴의 회상에서 나오고(Data/Handlers처럼), 그 추상화가 검증하고 수정할 가설로 다뤄지는 게 아니라 첫 번째로 그럴듯한 분해로 그냥 확정된다.

 

가설 없는 플라톤주의, 귀납 없는 확신. 그리고 "이건 아직 가설이고, 구현하다 보면 경계가 틀렸다는 신호가 올 수 있다"는 메타적 태도야말로 코드 어디에도 적혀 있지 않아서, AI에게 가장 가르치기 어려운 부분이다.

 

AI 시대의 추상화는 누구의 일인가

코드 생성의 한계 비용이 0에 수렴하는 시대에, 추상화는 인간에게 남는 마지막 일 중 하나다.

 

카파시도 비슷한 말을 했다. 에이전트가 코드를 쏟아내는 시대에도 테이스트와 판단, 스펙 설계는 여전히 인간이 병목이라고. 글 앞부분에서 언급한 "How to Be a 30x AI Engineer with a Taste"가 말하는 테이스트도 결국 같은 것이다. 컨벤션이 아니라 지금 이 문제의 맥락에서 근거를 가지고 판단하는 능력. 추상화야말로 그 판단이 가장 농축된 행위다.

 

내 작업 방식도 이 방향으로 바뀌고 있다. 구현은 점점 더 위임하되, 경계는 위임하지 않는다. 어떤 훅이 존재해야 하는지, 그 훅이 무엇을 감추고 무엇을 노출해야 하는지를 먼저 정하고, 그 경계 안의 구현을 AI에게 맡긴다. 앞의 구분으로 말하면, 문제를 쪼개는 추상화는 내가 쥐고, 그 가설의 검증자 역할도 내가 맡는 것이다.

 

추상화는 익숙한 감각을 전달하는 일이라고 했다. 감각은 겪은 자만이 가진다. 그리고 지금의 AI는, 아직 아무것도 겪지 않는다.


참고 자료

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함