기술 부채
고친 사람 github-actions[bot]
기술 부채는 지금 일정을 맞추려고 고른 지름길이 나중에 더 많은 일로 돌아오는 것을 빚에 빗대어 부르는 말입니다. 빚처럼 갚기 전까지는 이자가 붙습니다. 그 코드를 손댈 때마다 시간이 조금씩 더 듭니다. 이 이름 덕분에 개발자가 아닌 사람에게도 정리 작업이 왜 필요한지 설명할 수 있습니다.
쉽고 빠른 이해
기술 부채는 급하게 고른 지름길이 나중에 일로 돌아오는 것입니다. 금요일 마감에 맞추려고 할인 규칙을 결제 코드에 바로 박아 넣으면, 다음 할인을 넣을 때마다 같은 코드를 여러 곳에서 고칩니다.
이 이름이 없으면 나중에 더 드는 일이 안 보입니다. 기능은 멀쩡히 돌기 때문에 정리할 시간이 일정에서 밀립니다.
어떻게 쌓이고 갚나:
- 일정 때문에, 또는 몰라서 지름길을 고릅니다
- 그 코드를 손댈 때마다 시간이 더 듭니다. 이것이 이자입니다
- 속 구조를 고쳐 지름길을 걷어 내면 갚습니다
대가도 있습니다. 갚는 동안에는 그만큼 새 기능이 늦게 나갑니다. 안 갚으면 기능마다 드는 시간이 계속 늘어납니다.
언제 져도 되나: 곧 버릴 코드라면 져도 됩니다. 결제처럼 많은 기능이 기대는 코드는 처음부터 구조를 세웁니다.
상세
이 절은 기술 부채가 무엇을 가리키는지, 어떻게 쌓이고 어떻게 갚는지를 봅니다. 쇼핑몰 결제 코드에 할인 규칙 하나를 급하게 넣는 장면을 줄곧 예로 씁니다.
가게를 열 돈이 모자라 대출을 받는다고 해 봅니다. 대출 덕분에 가게를 한 달 먼저 엽니다. 대신 다달이 이자가 나갑니다. 원금을 갚기 전까지 이자는 멈추지 않습니다.
기술 부채는 개발에서 이 빚과 같은 일을 가리킵니다. 지금 일정을 맞추려고 손쉬운 방법을 고르면 그만큼 일찍 내놓습니다. 그 대신 나중에 그 코드를 고치거나 넓힐 때 일이 더 듭니다.
쇼핑몰이 금요일까지 골드 회원 할인을 열어야 한다고 해 봅니다. 할인 규칙을 따로 설계할 시간이 없어서 결제 코드 한가운데에 조건문 하나를 박아 넣습니다. 할인은 금요일에 열립니다. 이 조건문이 부채의 시작입니다.
기술 부채는 버그와 다릅니다. 버그는 코드가 지금 틀리게 도는 것입니다. 부채가 낀 코드는 지금 맞게 돕니다. 나중에 더 드는 일은 그 코드를 바꾸려 할 때 드러납니다.
원금과 이자
이 소절은 원금과 이자가 코드에서 무엇인지를 앞의 할인 조건문으로 봅니다. 금요일에 넣은 코드는 이렇습니다.
// 마감 때문에 결제 코드에 박은 할인
int price = 10000;
if (user.grade == 3) { // 3은 골드 회원
price = price * 9 / 10; // 9000
}
등급 번호 3이 골드 회원이라는 것과 할인율이 결제 코드 안에 박혀 있습니다. 할인 규칙은 이 코드 말고는 어디에도 적혀 있지 않습니다.
원금은 이 지름길을 제대로 된 구조로 바꾸는 데 드는 일입니다. 할인 규칙을 한 곳에 모읍니다. 결제 코드는 그곳만 부르게 고칩니다.
이렇게 겉으로 하는 일은 바꾸지 않고 코드의 속 구조만 고치는 작업을 리팩터링이라고 부릅니다. 원금은 리팩터링으로 갚습니다.
이자는 원금을 갚지 않은 채로 그 코드 둘레에서 일할 때마다 더 드는 시간입니다. 다음 달에 신규 회원 할인이 생기면 조건문이 하나 늘어납니다. 주문 확인 메일과 영수증에도 할인 금액을 찍어야 해서, 같은 조건문이 두 곳에 더 복사됩니다.
이제 할인율 하나를 바꾸려면 세 곳을 찾아 고쳐야 합니다. 한 곳을 빠뜨리면 결제 금액과 영수증 금액이 어긋납니다. 부채 위에 새 코드가 붙을수록 이자가 커지는 까닭이 이것입니다.
그림은 정리할 시간을 떼느냐 미루느냐에 따라 길이 갈리는 모습입니다. 미루는 쪽은 다시 위로 돌아가 그 위에 새 코드가 또 붙습니다.
flowchart TD
A["마감에 맞춰 지름길을 고른다"] --> B["그 위에 새 코드가 붙는다"]
B --> C{"정리할 시간을 떼나"}
C -->|뗀다| D["리팩터링으로 원금을 갚는다"]
C -->|미룬다| E["고칠 때마다 이자를 낸다"]
E --> B
원금을 갚고 나면 결제 코드는 할인 규칙을 모은 곳을 부르는 한 줄로 줄어듭니다.
int price = 10000;
price = rules.apply(user, price); // 9000
할인이 몇 개로 늘어도 결제·메일·영수증 코드는 이 한 줄만 부릅니다. 할인율을 바꿀 때 고칠 곳도 규칙을 모은 한 곳뿐입니다.
빚이라는 이름을 쓰는 까닭
이 소절은 개발자들이 왜 굳이 금융 낱말을 빌려 쓰는지를 봅니다. 이름이 없을 때 무엇이 곤란한지부터 짚습니다.
코드 속 지름길은 화면에 드러나지 않습니다. 할인은 금요일에 열렸습니다. 사용자는 아무 문제를 못 느낍니다. 결제 코드 속 조건문이 나중에 일을 늘린다는 것은 개발자만 압니다.
그래서 정리 작업은 일정표에서 자꾸 밀립니다. 「코드가 지저분해서 정리해야 한다」는 말은 기획자나 관리자에게 취향처럼 들립니다. 새 기능과 견주면 늘 뒤로 갑니다.
빚이라는 이름은 나중에 늘어날 이 일을 누구나 아는 셈으로 바꿉니다. 「지금 이자를 내고 있어서 기능마다 이틀씩 더 든다」는 말은 개발자가 아니어도 알아듣습니다. 원금을 갚을 시간을 일정에 넣자고 말할 근거가 생깁니다.
부채가 생기는 세 경로
모든 부채가 마감 때문에 생기지는 않습니다. 이 소절은 부채가 들어오는 경로 셋을 표로 먼저 봅니다. 그다음 하나씩 풉니다.
| 경로 | 어떻게 생기나 | 예 |
|---|---|---|
| 알고 진 부채 | 지름길인 줄 알면서 일정 때문에 고른다 | 금요일에 박은 할인 조건문 |
| 모르고 진 부채 | 그때는 최선이었는데 뒤에 더 나은 구조를 깨닫는다 | 주문 한 건에 배송지가 하나라고 보고 짠 테이블 |
| 낡아서 생긴 부채 | 코드는 그대로인데 둘레가 바뀐다 | 보안 패치가 끊긴 옛 라이브러리 · 다 켜고도 안 걷은 기능 스위치(기능 플래그) |
알고 진 부채는 적어도 어디에 빚이 있는지 압니다. 그래서 지는 순간에 갚을 계획도 같이 세울 수 있습니다.
모르고 진 부채는 팀이 업무를 더 잘 알게 되면서 드러납니다. 주문 한 건에 배송지가 여럿일 수 있다는 것을 나중에 알면, 배송지를 한 칸으로 둔 테이블이 부채가 됩니다. 누가 잘못해서 생기는 부채가 아니라 배우면서 생기는 부채입니다.
낡아서 생긴 부채는 아무도 손대지 않아도 쌓입니다. 코드를 짤 때 가져다 쓴 라이브러리가 새 판을 거듭 내면, 옛 판에 머문 코드는 보안 패치를 받지 못합니다. 뒤늦게 올리려 하면 그사이 바뀐 호출 방식을 한꺼번에 따라가야 합니다.
다 켜고도 안 걷은 기능 플래그도 같은 경로로 쌓입니다. 기능 플래그는 코드를 배포해 둔 채 설정값으로 기능을 켜고 끄는 스위치입니다.
기능을 다 켠 뒤에도 스위치와 옛 분기를 걷지 않으면 옛 분기가 그대로 남습니다. 이렇게 더는 실행되지 않는 코드를 죽은 코드라고 부릅니다.
부채가 쌓이는 곳
부채는 코드 한 줄에만 붙지 않습니다. 이 소절은 부채가 쌓이는 곳 넷을 표로 봅니다. 곳마다 이자가 언제 붙는지도 함께 적습니다.
| 곳 | 흔한 모양 | 이자가 붙는 때 |
|---|---|---|
| 코드 | 복사해 붙인 중복 코드 · 한 함수에 몰린 수백 줄 | 같은 규칙을 고칠 때 |
| 설계 | 잘못 그은 모듈 경계 · 모듈끼리 서로를 부르는 순환 의존 | 기능 하나에 여러 모듈을 같이 고칠 때 |
| 테스트 | 자동 테스트가 없는 코드 | 고친 뒤 다른 곳이 깨졌는지 손으로 확인할 때 |
| 의존성 | 낡은 판에 머문 라이브러리 | 판을 올리거나 보안 패치를 받을 때 |
이자가 가장 크게 붙는 곳은 설계입니다. 코드에 낀 부채는 그 함수 하나를 고치면 갚을 수 있습니다. 설계에 낀 부채는 그 설계 위에 올린 코드까지 함께 옮겨야 갚을 수 있습니다. 소프트웨어 아키텍처를 정할 때 부채를 따지는 까닭이 이것입니다.
테스트에 낀 부채는 다른 부채를 갚기 어렵게 만듭니다. 구조를 고친 뒤 겉 동작이 바뀌지 않았는지 확인할 길이 없어서입니다. 이 이야기는 아래 「부채를 갚는 방법」 소절에서 이어집니다.
부채를 알아보는 신호
부채는 장부에 적히지 않아서 따로 찾아야 합니다. 이 소절은 팀이 먼저 느끼는 신호와, 도구가 코드에서 부채를 찾는 방법을 봅니다.
팀이 먼저 느끼는 신호는 이렇습니다.
- 비슷한 크기의 기능인데 넣는 데 드는 날이 점점 늘어납니다
- 작은 수정 하나가 여러 파일로 번집니다
- 고친 뒤 엉뚱한 곳이 깨지는 일이 잦습니다
- 팀 안에 「그 파일은 건드리지 마라」는 말이 돕니다
코드 안에도 신호가 있습니다. 당장 틀리지는 않지만 고치기 어렵게 만드는 코드의 모양을 코드 스멜이라고 부릅니다. 복사해 붙인 코드, 수백 줄짜리 함수, 인자가 열 개인 메서드가 그런 모양입니다.
정적 분석은 이런 모양을 기계로 찾습니다. 정적 분석은 프로그램을 실행하지 않고 소스 코드만 읽어 문제를 찾는 일입니다. 사람이 파일을 하나하나 열지 않아도 저장소 전체를 훑을 수 있습니다.
도구가 흔히 세는 값 하나가 사이클로매틱 복잡도입니다. 함수 안에서 코드가 갈라지는 길의 수입니다. 이 값이 클수록 테스트로 확인할 경로가 많아집니다. 함수를 읽기도 어려워집니다.
도구에 따라서는 찾은 문제마다 고치는 데 드는 시간을 매겨 더합니다. 그러면 「이 저장소의 부채는 며칠어치」처럼 부채를 시간으로 보여 줄 수 있습니다. 도구가 보는 것은 코드의 모양뿐입니다. 잘못 그은 설계나 낡은 가정은 사람이 찾아야 합니다.
부채를 갚는 방법
갚는 일의 중심은 리팩터링입니다. 이 소절은 갚기 전에 갖출 것과, 갚을 시간을 만드는 방법을 봅니다.
리팩터링은 겉 동작을 바꾸지 않는 것이 약속입니다. 그 약속을 지켰는지 알려면 고치기 전과 뒤에 같은 테스트를 돌려 봐야 합니다. 전에 되던 동작이 여전히 되는지 확인하는 테스트를 회귀 테스트라고 부릅니다. 테스트가 없는 코드는 이것부터 붙이고 갚습니다.
갚을 시간은 새 기능과 다투기 때문에 따로 만들어야 합니다. 흔히 쓰는 방법 셋을 표로 견줍니다.
| 방법 | 어떻게 하나 | 대가 |
|---|---|---|
| 손대는 김에 갚기 | 기능을 넣으려고 연 파일을 조금 더 정리해 두고 나온다 | 아무도 안 여는 파일의 부채는 줄지 않는다 |
| 시간 떼어 두기 | 개발 주기마다 일정한 몫을 부채 갚기에 쓴다 | 그 몫만큼 새 기능이 늦게 나간다 |
| 일감으로 적기 | 알고 진 부채를 이슈 트래커에 일감으로 남긴다 | 적기만 하고 순서를 안 정하면 목록만 길어진다 |
셋은 함께 씁니다. 자잘한 부채는 손대는 김에 갚아 줄입니다. 큰 부채는 일감으로 적어 두고 떼어 둔 시간에 갚습니다.
부채가 크면 전부 버리고 다시 짜고 싶어집니다. 다시 짜는 동안에는 새 기능이 멈춥니다. 옛 코드가 오랫동안 모아 온 예외 처리도 잃기 쉽습니다.
그래서 옛 코드를 조금씩 새 코드로 갈아 끼우는 쪽을 흔히 고릅니다. 새 기능은 새 코드로 짭니다. 옛 기능은 하나씩 옮긴 뒤 옛 코드를 지웁니다. 이 방식에는 스트랭글러 무화과라는 이름이 있습니다.
일부러 지는 조건
기술 부채는 늘 피할 것이 아닙니다. 이 소절은 부채를 져도 되는 조건과 지면 안 되는 조건을 견줍니다.
부채를 지는 까닭은 시간입니다. 코드를 다듬기보다 먼저 내놓아 사용자의 반응을 보는 쪽이 값질 때가 있습니다. 반응을 보려고 만드는 프로토타입이나, 행사가 끝나면 지울 코드가 그렇습니다. 곧 버릴 코드라면 원금을 갚을 일도 없습니다.
반대로 오래 살고 여러 곳에서 부르는 코드는 이자가 빨리 불어납니다. 결제나 권한처럼 많은 기능이 기대는 코드가 그렇습니다. 부르는 곳이 늘 때마다 지름길을 따라 짠 코드가 함께 늘기 때문입니다.
데이터베이스 스키마도 여기에 듭니다. 스키마는 테이블과 칸의 생김새를 정한 틀입니다. 스키마를 고치려면 이미 쌓인 데이터를 새 모양으로 옮겨야 해서, 원금이 데이터와 함께 커집니다.
아래 표는 조건마다 무엇을 고르는지 모읍니다.
| 조건 | 고르는 쪽 |
|---|---|
| 곧 버릴 코드 · 반응을 보려는 시제품 | 지름길을 고르고 갚지 않는다 |
| 마감은 못 박혀 있고 코드는 오래 산다 | 지름길을 고르고 갚을 일감을 같이 적는다 |
| 많은 기능이 기대는 코드 · 스키마 | 처음부터 구조를 세운다 |
어느 쪽이든 부채를 알고 지는 것이 기준입니다. 어디에 빚이 있는지 적혀 있으면 갚을 순서를 정할 수 있습니다. 모르는 사이 쌓인 빚은 개발 속도가 떨어지고 나서야 드러납니다.
관련 항목
기술 부채를 갚는 작업
리팩터링 · 스트랭글러 무화과 · 회귀 테스트 · 단위 테스트 · 테스트 커버리지 · 코드 리뷰
기술 부채를 드러내는 코드의 모양
코드 스멜 · 중복 코드 · 매직 넘버 · 하드코딩 · 긴 메서드 · 신 객체 · 죽은 코드
설계에 쌓이는 기술 부채
순환 의존 · 결합도 · 응집도 · 모듈 · 분산 모놀리스 · 소프트웨어 아키텍처
기술 부채를 재는 도구와 지표
정적 분석 · 린터 · 사이클로매틱 복잡도 · 코드 품질 · 유지보수성
기술 부채가 새로 쌓이는 경로
기능 플래그 · 라이브러리 · 의존성 · 보안 패치 · 소프트웨어 부패 · 레거시 코드 · 스키마
기술 부채를 일감으로 다루는 개발 방식
이슈 트래커 · 애자일 소프트웨어 개발 · 프로토타입 · 지속적 통합 · 트렁크 기반 개발
기술 부채와 맞세워지는 설계 태도
과잉 설계 · YAGNI · 보이스카우트 규칙 · 아키텍처 결정 기록
다른 이름: technical debt · 기술 빚 · 테크 부채