코드 줄 수는 코드 복잡성과 소프트웨어 비용을 측정하는 데 왜 유용한지, 그리고 생산성 지표로서는 어디까지 쓸 수 있는지에 대한 고찰.
kqr 작성, 2026-03-24 게시
태그:

인터넷에는 _코드 줄 수_를 측정 기준으로 깎아내리는 사람들이 넘쳐난다. 사람들은 이런 말을 한다.
작성된 코드 줄 수는 수십 년에 걸쳐 대체로 무의미한 지표라는 것이 확고히 입증되었다.
그리고
지표와 KPI가 코드 줄 수 같은 멍청한 측정값을 기반으로 설정되고 있다.
그리고
코드 줄 수는 멍청한 지표이며, 그것으로 의미 있는 무언가를 말하려는 사람은 현실과 동떨어져 있다.
그리고
우리는 코드 줄 수가 생산성을 측정하는 최악의 지표 중 하나라는 사실을 집단적으로 잊어버린 듯하다.
그리고
나는 코드 줄 수에 대한 집착이 가장 비생산적인 관행 중 하나라고 생각한다.
그리고
코드 줄 수는 무엇을 측정하든 매우 나쁜 척도로 악명이 높다.
나는 이런 주장들이 이상하게 느껴진다. 왜냐하면 이것들은 사실이 아니기 때문이다.
코드 줄 수는 코드 복잡성을 측정한다. 이것은 잘 확립된 사실이다. 내 말을 그대로 믿을 필요는 없다.
우리가 실제로 코드 복잡성 지표를 시험하려는 수고를 들일 때마다, 결과는 단순한 코드 줄 수와 같은 예측을 하거나 그보다 못했다. 이것이 더 나은 복잡성 지표가 존재할 수 없다는 뜻은 아니다. 다만 기본적으로는 회의적으로 봐야 한다는 뜻이다. 지금까지 등장한 복잡성 지표들이 모두 외투를 입은 코드 줄 수 측정이었다면, 다음 지표도 그럴 가능성이 크다.
Basili and Hutchens는 이를 특히 잘 표현했다.
줄 수는 계산하기가 매우 쉽고, 많은 연구자들이 이것이 복잡성을 측정하는 데 신뢰할 만한 역할을 한다는 사실을 발견했기 때문에, 이런 종류의 연구에서는 반드시 이겨야 할 지표로 간주되어야 한다. 우리는 줄 수보다 유의미하게 더 나은 지표를 찾는 데 실패했다.
줄 수는 무의미한 측정값이 아니다. 우리가 아는 한 코드 복잡성을 측정하는 가장 좋은 방법이다.
그리고 복잡성은 소프트웨어를 구축하고 유지하는 비용, 그리고 어느 정도는 그것이 얼마나 유용한지를 결정한다. 다른 조건이 같다면, 더 복잡한 소프트웨어는 (a) 비용이 더 많이 들고, (b) 더 유용한 작업을 수행할 수 있다.
여기서 Fred Brooks가 말한 본질적 복잡성과 우발적 복잡성을 구분하는 것이 중요하다.1 - [x] 1 No Silver Bullet: Essence and Accident in Software Engineering; Brooks; Proceedings of the ifip Tenth World Computing Conference; 1986.
우리는 본질적 복잡성을 해결해야 할 문제 때문에 존재하는 복잡성으로 생각할 수 있다.2 - [x] 2 Brooks는 소프트웨어에서 다뤄야 하는 본질적 복잡성의 다른 몇 가지 형태도 언급하는데, 이는 소프트웨어의 보이지 않는 성격과 프랙탈적 성격에 더 관련되어 있다. 이런 것들은 서로 다른 두 프로그램의 복잡성을 비교할 때는 중요하지 않다. 모든 소프트웨어에 똑같이 적용되기 때문이다. 화성에 탐사선을 착륙시키는 것은 복잡한 문제이며, 화성에 탐사선을 착륙시키는 소프트웨어는 그 문제에 내재한 복잡성보다 더 단순할 수 없다. Brooks의 표현을 빌리면,
소프트웨어 엔지니어가 다뤄야 하는 복잡성의 많은 부분은 임의적 복잡성으로, 그가 인터페이스를 맞춰야 하는 수많은 시스템들에 의해 강제된다. […] 이것은 소프트웨어 자체만 재설계한다고 해서 단순화할 수 없다.
기능을 유지하면서 본질적 복잡성을 제거하는 것은 불가능하다. 본질적 복잡성을 줄이려면 기능을 제거해야 한다.
반면 우발적 복잡성은 해결해야 할 문제에서 나오지 않는다. 대신, 그 문제를 소프트웨어로 번역하는 과정에서 도입되는 복잡성이다. Brooks는 우발적 복잡성을 직접 정의하지는 않지만, 도구의 발전이 그 일부를 제거해 왔다고 논의한다. 예를 들어 그는 이렇게 말한다.
추상 데이터 타입은 […] 설계자가 새로운 정보 내용을 더하지 않는 방대한 양의 구문적 재료 없이 자기 설계의 본질을 표현할 수 있게 하여, 과정에서 또 하나의 우발적 어려움을 제거한다. 그 결과 설계를 더 높은 차원에서 표현할 수 있게 된다.
그렇다면 우발적 복잡성이란 코드 안에 굳이 있을 필요가 없는데도 우리가 집어넣는 복잡성이다. 어떤 우발적 복잡성은 우리가 사용하는 프로그래밍 언어에 필요한 추상화가 없기 때문에 코드에 강제로 들어가고, 어떤 것은 우리가 모두 rockstar 10× developer ninjas가 아니기 때문에 들어간다.
이 구분이 중요한 이유는 다음과 같기 때문이다.
좋든 싫든, 코드 줄 수는 총복잡성만 측정한다. 줄 수는 본질적 복잡성과 우발적 복잡성을 구분할 수 없다. 이 점의 장점은 코드 줄 수가 소프트웨어 비용과 밀접하게 대응한다는 것이다. 예를 들어 Blender가 nginx보다 코드 줄 수가 더 많다면(실제로 그렇다), 우리는 Blender가 개발하는 데 더 비쌌을 것이라고 예상한다(실제로 그랬다). 또한 그 유지보수의 지속 비용도 더 높다고 예상한다(실제로 그렇다). 이와 관련된 흥미로운 관찰은 부록 A를 보라.
코드 줄 수와 복잡성 사이의 이 관계는 소프트웨어 프로젝트의 전체 크기(더 큰 프로젝트가 더 비싸다)에 대해서도 사실이지만, 소프트웨어 크기의 _변화_에 대해서도 사실이다. 하루에 코드 줄 수가 얼마씩 증가하는 프로젝트는 지속적인 유지보수 비용도 하루에 얼마씩 증가한다. 작은 변화에서는 영향도 작다. 그러나 시간이 지나면 누적된다. 코드 줄 수는 유지보수 비용의 이런 증가를 측정하는 방법이다.
구체적인 측정값이 전혀 없더라도, 우리는 계획 수립을 위해 이것을 경험칙으로 사용할 수 있다. 어떤 팀이 오늘은 시간의 4분의 1을 유지보수에 쓰고 있고, 내년에 프로젝트를 두 배 크기로 키울 것으로 예상한다면, 새로운 개발의 속도는 추가 코드의 유지보수 부담을 감안해 3분의 1 감소할 것이다.
이쯤 되면 우리는 Dijkstra가 다음과 같이 썼을 때 처음부터 옳았다고 인정하고 싶어질 수 있다. 3 - [x] 3 ewd 1036: On the cruelty of really teaching computer science; Dijkstra; 1998. 온라인에서 볼 수 있다.
오늘 내가 말하고 싶은 요점은, 코드 줄 수를 세고자 한다면 그것을 “생산된 줄 수”가 아니라 “소비된 줄 수”로 보아야 한다는 것이다. 현재의 통념은 너무도 어리석어서 그 수치를 대차대조표의 반대편에 기입하고 있다.
하지만 그렇게 단순한 문제도 아니다. 작성된 줄은 소비된 줄이지만, 그중 일부는 동시에 생산된 줄이기도 하다. 추가된 복잡성 중 일부는 우발적이고, 일부는 본질적이다. 후자는 실제로 소프트웨어의 가치를 증가시킨다.
만약 어떤 프로젝트에서 본질적 복잡성과 우발적 복잡성의 비율이 비교적 일정하다면(적어도 중간 정도 기간을 집계해서 볼 때는 그렇게 믿는 것이 합리적으로 보인다), 코드 줄 수는 본질적 복잡성과도 약하게 상관되는 측정값이 된다. 즉, 소프트웨어가 제공할 수 있는 가치의 양과도 약하게 상관된다.
이것은 개인의 생산성에 대해서도 마찬가지일 것이다. 어떤 사람의 기여에서 한 달과 다음 달 사이 본질적 복잡성과 우발적 복잡성의 비율이 비교적 안정적이라면, 그가 프로젝트에 추가한 코드 줄 수는 소프트웨어가 추가로 제공하게 만든 가치의 대리 지표가 된다. 그러나 작업 유형마다 본질적 복잡성과 우발적 복잡성의 비율이 다르므로, 많은 기여를 평균내지 않는 한 그 비율이 안정적이라고 가정하는 것은 결코 안전하지 않다.4 - [x] 4 Goodhart의 법칙도 분명한 문제다. 개인이 자신이 코드 줄 수로 평가받는다는 사실을 알게 되면, 가치 증가 없이 비용만 늘리는 우발적 복잡성을 더 많이 만들어내기 시작할 가능성이 높다.
이 논리는 코드 제거가 결코 좋은 일이 아니라는 듯 들리게 만든다. 왜냐하면 그것은 부정적 생산성과 상관되기 때문이다. 하지만 내가 하는 말은 그게 아니다! 코드를 제거하면 소프트웨어의 유지 비용이 줄어든다. 그러나 우발적 복잡성을 제거하는 것(순전히 좋은 일)과 본질적 복잡성을 제거하는 것(때로는 좋지만 평가하기 더 어렵다) 사이에는 중요한 차이가 있다.
문제를 더 복잡하게 만드는 것은, 모든 본질적 복잡성이 같은 가치를 더하지는 않는다는 사실이다. 어떤 가치 있는 기능은 근본적으로 단순하지만, 다른 기능은 복잡하면서도 별로 바람직하지 않을 수 있다. 이것은 줄 수와 가치 사이의 상관관계를 더 약하게 만든다.
위의 내용을 보면 결국 코드 줄 수가 쓸모없는 지표처럼 들릴 수도 있지만, 우리가 이를 지표로 사용하는 두 가지 방식을 구분하는 것이 중요하다.
자, 이 글을 여는 인용문들보다는 조금 더 미묘한 이야기다.
이 글에서는 Blender와 nginx를 예로 들었다. Blender는 280만 줄의 코드를 가지고 있고, nginx는 25만 줄의 코드를 가지고 있다. Blender가 nginx보다 유지보수 비용이 더 많이 든다고 추측하는 것은 합리적으로 보이며, 적어도 한 가지 의미에서는 실제로도 그렇다. 유지보수 비용의 대리 지표로, 지난 6개월 동안 5회 초과의 커밋을 한 저자의 수로 정의한 유지보수자 수를 사용하겠다. 다음은 GitHub에서 인기 있는 몇몇 프로젝트의 데이터다.
| 프로젝트 | 코드 줄 수 | 유지보수자 |
|---|---|---|
| Rust | 3,800,000 | 229 |
| Blender | 2,800,000 | 71 |
| Kubernetes | 2,300,000 | 89 |
| VSCode | 2,000,000 | 81 |
| Node.js | 1,300,000 | 39 |
| PowerToys | 760,000 | 15 |
| React | 550,000 | 10 |
| NeoVim | 410,000 | 25 |
| Transmission | 300,000 | 8 |
| nginx | 250,000 | 4 |
| yt-dlp | 240,000 | 6 |
| Redis | 230,000 | 12 |
| Audacity | 180,000 | 9 |
| Excalidraw | 160,000 | 4 |
| tmux | 100,000 | 3 |
| Fish | 90,000 | 10 |
| htop | 49,000 | 4 |
| i3 | 29,000 | 3 |
| scc | 24,000 | 3 |
매우 거친 가이드로 보자면, 이 데이터는 오픈 소스 코드 25,000줄마다 유지보수자 1명이 더 필요하다는 점을 시사한다.6 - [x] 6 더 자세히 보면 유지보수자 수는 코드 크기의 이차 함수처럼 보이는데, 이는 전체 코드 크기가 클수록 코드 한 줄의 비용이 더 커진다는 뜻이다. 이것은 타당하다. 구성요소 간 상호작용은 구성요소 수에 따라 제곱으로 증가하기 때문이다. n개의 것이 있을 때 대략 n²개의 쌍이 있기 때문이다. 이 유지보수자들이 평균적으로 하루에 한 시간씩 유지보수를 한다고 해보자.7 - [x] 7 이 프로젝트들 중 일부는 여가 시간에 이루어지는 작업이고, 일부는 기업 후원을 받는다. 하루 한 시간이라는 수치는 내가 대충 꺼낸 것이다. 틀렸다고 생각한다면, 더 동의할 수 있는 숫자로 이 계산을 다시 해보면 된다. 그러면 다음이 뜻된다.
이 구체적인 숫자들에는 많은 가정이 들어가 있으므로, 아마 보편적으로 참은 아닐 것이다. 그래도 생각해볼 만한 흥미로운 내용이며, 내 직업적 경험과도 충분히 가까워서 유용한 경험칙이 된다.
Basili and Hutchens는 또 하나의 흥미로운 관찰을 했지만, 그들 스스로도 표본이 너무 작아 일반화할 수는 없다고 인정한다. 프로그램을 작성하기 얼마나 어려운지를 가늠하는 대리 지표로, 그들은 프로그램이 명세를 성공적으로 충족하기 위해 필요했던 변경 횟수를 측정했다. 그들은 완성된 프로그램의 개별 구성요소 크기를 바탕으로 이를 모델링하려 했고, 프로그램 내 구성요소 복잡성의 높은 백분위수에 대해 선형 적합과 지수 적합을 시험했다.
유지보수성을 해치지 않으려면 함수는 짧아야 한다고 제안하는 Uncle Bob 같은 고전적 조언을 따른다면 8 - [x] 8 Clean Code: A Handbook of Agile Software Craftsmanship; Martin; Pearson; 2008., 우리는 지수 적합이 가장 좋을 것이라고 예상할 것이다. 이는 개별적으로 복잡한 구성요소가 불균형적으로 큰 유지보수 수요를 초래한다는 가설에 해당한다.
하지만 Basili and Hutchens는 선형 적합이 더 낫다는 사실을 발견했다! 그들의 데이터에서는 복잡한 구성요소 하나가, 각각 크기가 1/5인 구성요소 다섯 개보다 더 큰 유지보수 수요를 유발하지 않았다. 이것은 서브프로그램을 인라인으로 넣는 것이 더 큰 명료성을 낳는다고 제안하는 John Carmack류의 조언과 더 잘 맞아떨어진다.
다시 말하지만, 그들의 표본은 일반화하기에는 너무 작다. 더 다양한 데이터로 이 결과를 누가 재현했는지는 나는 모른다. 그래도 정말 훌륭한 과학이다!
Halstead/McCabe 지표의 목적은 관리자가 자신이 인상을 줘야 할 사람들에게 인상을 주기 위한 복잡한 공식을 제공하는 것이다. Halstead 지표에는 수학적 문제가 있다.
당신의 글에 있는 부록 A는 유지보수에서 유행이 미치는 큰 영향을 무시하고 있다. Rust에 유지보수자가 훨씬 더 많은 것은 그것이 유행하기 때문이다.
최적 함수 길이에 대한 일부 분석은 허술한 분석에 의존한다.
No Silver Bullet: Essence and Accident in Software Engineering; Brooks; Proceedings of the ifip Tenth World Computing Conference; 1986.
Brooks는 소프트웨어에서 다뤄야 하는 본질적 복잡성의 다른 몇 가지 형태도 언급하는데, 이는 소프트웨어의 보이지 않는 성격과 프랙탈적 성격에 더 관련되어 있다. 이런 것들은 서로 다른 두 프로그램의 복잡성을 비교할 때는 중요하지 않다. 모든 소프트웨어에 똑같이 적용되기 때문이다.
ewd 1036: On the cruelty of really teaching computer science; Dijkstra; 1998. 온라인에서 볼 수 있다.
Goodhart의 법칙도 분명한 문제다. 개인이 자신이 코드 줄 수로 평가받는다는 사실을 알게 되면, 가치 증가 없이 비용만 늘리는 우발적 복잡성을 더 많이 만들어내기 시작할 가능성이 높다.
하지만 “가치와 본질적 복잡성 사이의 안정적 관계”란 여러분의 제품 조직이 자기 일을 점점 더 잘하게 되지 않고 있다는 뜻이기도 하다. 그것을 목표로 삼고 싶지는 않을 것이다.
더 자세히 보면 유지보수자 수는 코드 크기의 이차 함수처럼 보이는데, 이는 전체 코드 크기가 클수록 코드 한 줄의 비용이 더 커진다는 뜻이다. 이것은 타당하다. 구성요소 간 상호작용은 구성요소 수에 따라 제곱으로 증가하기 때문이다. n개의 것이 있을 때 대략 n²개의 쌍이 있기 때문이다.
이 프로젝트들 중 일부는 여가 시간에 이루어지는 작업이고, 일부는 기업 후원을 받는다. 하루 한 시간이라는 수치는 내가 대충 꺼낸 것이다. 틀렸다고 생각한다면, 더 동의할 수 있는 숫자로 이 계산을 다시 해보면 된다.
Clean Code: A Handbook of Agile Software Craftsmanship; Martin; Pearson; 2008.
이 글이 마음에 들었고 더 많은 글을 원한다면 커피 한 잔 사주세요. 덕분에 내 170개가 넘는 아이디어 백로그를 글로 바꿀 수 있다.
놀라운 아내에게 감사한다. 그녀의 지원이 없었다면 나는 첫 문장도 넘기지 못했을 것이다. ♥
S = k log W.
의견이 있나요? 이메일을 보내주세요.
새 글을 위해 구독할 수도 있다.