추상화와 간접화 계층을 구분하며, 나쁜 추상이 성능과 단순성에 미치는 비용을 설명하고 좋은 추상을 판단하는 기준을 제시한다.
소프트웨어 시스템을 리팩터링하거나 성능을 개선해 본 적이 있다면, 특정한 좌절감을 겪어 봤을 것이다. 바로 추상화가 잔뜩 들어간 코드베이스다. 겉으로 보기에는 잘 정리되고 모듈화된 코드처럼 보이지만, 실제로는 계층 위에 계층이 덕지덕지 쌓인 미로인 경우가 많다. 성능은 굼뜨고, 디버깅은 악몽 같으며, CPU는 실제 문제를 푸는 것보다 추상화 코드를 돌리는 데 더 많은 시간을 쓰는 듯 보인다.
이 지점에서 중요한 사실 하나를 깨닫게 된다. 모든 추상이 똑같이 만들어지는 것은 아니라는 것. 사실, 그중 상당수는 추상이라고 부르기도 애매하다. 그냥 얇게 발라 놓은 겉치레, 아무 가치도 더하지 못한 채 복잡도만 늘려 놓는 간접화 계층일 뿐이다.
추상은 그 아래에 있는 복잡성을 얼마나 잘 숨기느냐에 따라 가치가 결정된다. 정말 훌륭한 추상을 떠올려 보자. 예를 들어 TCP가 그렇다. TCP 덕분에 우리는 마치 신뢰할 수 있는 통신 채널을 가진 것처럼 코딩할 수 있다. 하지만 실제로 그 아래에는 신뢰할 수 없는 IP 프로토콜이 깔려 있다.
TCP는 오류 수정, 재전송, 패킷 순서 보장 같은 복잡한 일을 대신 떠안아 준다. 그래서 우리는 그런 것들을 신경 쓰지 않아도 된다. 그리고 이 일을 너무 잘해 주기 때문에, 개발자인 우리는 거의 TCP의 내부 동작을 들여다볼 필요가 없다. 마지막으로 TCP를 패킷 수준까지 내려가서 디버깅해 본 게 언제였는가? 대부분의 사람들에게 그 답은 아마도 “한 번도 없다”일 것이다.
이게 바로 좋은 추상의 특징이다. 그 추상 덕분에 우리는 마치 그 아래에 있는 복잡성이 존재하지 않는 것처럼 작업할 수 있다. 우리는 추상이 제공하는 이점을 누리고, 추상은 어려운 부분을 시야와 신경 밖으로 밀어낸다.
그렇다면 나쁜 추상은 어떨까? 아니, 더 정확히 말하자면, 추상인 척하는 간접화 계층은 어떨까? 이런 "추상"은 아무 복잡성도 숨기지 못한다. 오히려 추상해야 할 대상을 그대로 드러내면서, 그 의미를 전적으로 거기서만 가져오는 얇은 계층 하나를 덧붙일 뿐이다.
예를 들어, 아무 동작도 추가하지 않은 채 기존 함수를 얇게 감싸기만 한 래퍼를 떠올려 보자. 분명 이런 것들을 본 적이 있을 것이다. 데이터를 이리저리 전달만 하는 클래스, 메서드, 인터페이스들. 시스템을 더 추적하기 어렵게 만들고, 디버깅도 더 힘들게 만들며, 이해하기도 더 어렵게 만든다.
이런 것들은 추상이 아니다. 그냥 간접화 계층일 뿐이다.
간접화 계층의 문제는 인지적 부담을 늘린다는 데 있다. 이런 계층은 종종 유연성이나 모듈성을 위한 것이라며 정당화된다. 하지만 실제로는 그런 이점을 제대로 제공하는 경우가 거의 없다. 대신 코드베이스를 더 복잡하게 만들고, 특히 성능을 쥐어짜거나 버그를 고쳐야 할 때 작업을 훨씬 더 어렵게 만든다.
우리는 흔히 추상은 공짜라고 생각한다. 인터페이스를 하나 더 추가하고, 래퍼를 하나 더 만들고, 그러다 보면 어느새 그런 것들이 층층이 쌓여 있게 된다. 이런 사고방식은 중요한 진실 하나를 무시한다. 추상에는 비용이 있다는 것이다. 추상은 복잡성을 추가하고, 종종 성능 상의 페널티까지 가져온다.
추상은 성능의 적이다. 계층을 많이 쌓을수록 우리는 실제 하드웨어로부터 더 멀어지게 된다. 코드를 최적화하는 일은 수많은 계층을 하나하나 벗겨 내며 마침내 실제 일이 일어나는 지점까지 내려가는 작업이 되어 버린다.
각 계층은 정신적·계산적 부담을 의미한다. 무슨 일이 벌어지는지 이해하는 데 더 오래 걸리고, 중요한 코드가 어디 있는지 찾는 데 더 오래 걸리며, 실제 비즈니스 로직을 실행하는 데도 더 오래 걸린다.
추상은 단순성의 적이기도 하다. 새 추상은 원래 더 단순하게 만들어 주겠다고 약속한다. 하지만 실제로는 각 계층마다 자기만의 규칙, 자기만의 인터페이스, 자기만의 실패 지점을 추가한다. 단순화하기는커녕, 이런 추상들이 복잡성을 차곡차곡 쌓아 올려 시스템을 더 이해하기 어렵고, 유지보수하기 어렵고, 확장하기 어렵게 만든다.
"모든 추상은 샌다"라는 유명한 말이 있다. 사실이다. 아무리 훌륭한 추상이라도, 결국에는 기저 구현을 이해해야만 하는 상황에 부딪히게 된다.
이 누수는 미묘할 수도 있다. 예를 들어, 성능 특성을 이해하려 할 때(빅오 복잡도가 어떻게 되는지 궁금할 때처럼) 그렇다. 또는 훨씬 더 노골적일 수도 있다. 어떤 일이 기대한 대로 동작하지 않을 때, 그 원인을 알아내기 위해 깊이 파고들어 디버깅해야 하는 상황이 그렇다.
좋은 추상은 이런 상황을 최소화한다. 나쁜 추상은 작은 버그 하나를 찾는 일조차 대공사로 만들어 버린다.
추상을 평가하는 데 쓸 수 있는 유용한 경험칙이 하나 있다. 스스로에게 이렇게 물어보는 것이다.
이 추상의 ‘후드 아래’를 들여다봐야 하는 빈도는 얼마나 되나?
하루에 한 번? 한 달에 한 번? 1년에 한 번?
이 환상을 깨야 하는 빈도가 적을수록 추상은 더 좋은 것이다.
추상에는 어떤 비대칭성이 존재한다. 추상을 만든 사람은 그 이득을 즉시 누린다. 자신의 코드가 더 깔끔해 보이고, 쓰기 쉬워지고, 더 우아해 보이거나, 어쩌면 더 유연해진 것처럼 느껴진다.
하지만 그 추상을 유지하는 비용은 종종 다른 사람들에게 전가된다. 미래의 개발자, 유지보수 담당자, 그 코드를 가지고 일해야 하는 성능 엔지니어들이 그들이다. 이들이 계층을 하나하나 벗겨 보고, 간접 참조들을 따라가며, 모든 것이 어떻게 엮여 있는지 이해해야 한다. 이들이 바로 불필요한 추상이 만들어 낸 진짜 비용을 치르는 사람들이다.
이 글의 요지는 추상이 나쁘다는 이야기가 아니다. 전혀 아니다. 좋은 추상은 강력하다. 좋은 추상 덕분에 우리는 복잡한 시스템을 만들면서도 그 복잡성 속에서 길을 잃지 않을 수 있다.
하지만 추상이 공짜가 아니라는 사실은 반드시 인식해야 한다. 추상에는 성능과 복잡성 측면에서 모두 실제 비용이 존재한다. 그리고 어떤 "추상"이 복잡성을 숨기지 못한 채, 그저 간접화 계층만 하나 더 추가하고 있다면, 그것은 추상이 아니다.
다음에 추상을 도입하고 싶어질 때 스스로에게 물어보라.
이건 정말로 시스템을 단순하게 만들고 있는가?
아니면 그저 간접화 계층을 하나 더 쌓고 있는 것뿐인가?
추상은 신중하게 사용하라. 그리고 기억하라. 진짜로 복잡성을 숨기고 있지 않다면, 당신은 그 복잡성에 그냥 한 겹을 더 얹고 있을 뿐이라는 것을.
2025 © Fernando Hurtado Cardenas. RSS