'코드 스멜'은 품질이 좋지 않은 코드에서 자주 나타나는 소프트웨어 설계 패턴을 가리키는 프로그래머들의 비공식 용어입니다. 코드 스멜은 버그나 결함 자체라기보다는 코드 품질이 좋지 않음을 알려주는 경고 신호로 이해하는 것이 적절합니다.
'코드 스멜'이라는 용어는 Kent Beck이 처음 사용했으며, Martin Fowler와 함께 집필한 1999년의 대표 저서 Refactoring: Improving the Design of Existing Code의 'Bad Smells in Code' 장을 통해 널리 알려졌습니다. Fowler는 코드 스멜을 "대개 시스템의 더 깊은 문제를 나타내는 표면적인 징후"라고 설명했습니다.1
실제 소프트웨어 버그와 달리 코드 스멜은 소스 코드의 컴파일이나 실행 또는 정상적인 기능 수행을 방해하지 않으며, 반드시 실제 문제가 존재한다는 의미도 아닙니다. 집에 들어갔는데 퀴퀴하거나 곰팡이 냄새가 난다고 생각해 보세요. 그 냄새는 냉장고 속 상한 음식이나 버려야 할 쓰레기 때문일 수도 있고, 더 심각하게는 벽 속의 곰팡이나 부패한 물질 때문일 수도 있습니다. 하지만 단지 냄새가 강한 치즈 때문일 수도 있으며, 그 치즈는 아무런 문제 없이 먹을 수 있는 음식일 수도 있습니다. 어떤 경우든 그 냄새가 난다면 원인을 확인해 볼 필요는 있습니다.
숙련된 소프트웨어 엔지니어는 특정 코드 패턴에서 직감적으로 '이상한 느낌'을 받을 수 있습니다. 경험이 쌓일수록 이러한 패턴을 복잡성, 비효율성 또는 앞으로 발생할 문제와 자연스럽게 연결하게 됩니다. 대표적인 코드 스멜에 이름을 붙이고 널리 알리면 이러한 비효율적인 패턴, 즉 '안티패턴'과 그것이 위반하는 설계 원칙을 더 쉽게 이해하고 인식할 수 있습니다. 이러한 익숙함은 코드 검토 과정에서 안티패턴을 더 쉽게 발견하고 수정하는 데 도움이 됩니다. Fowler의 표현을 빌리면, "코드 스멜은 정의상 금방 알아차릴 수 있는 것"입니다.
코드 스멜을 그대로 방치하면 기술 부채의 주요 원인이 될 수 있습니다. 한 연구에 따르면 코드 검토에서 발견되는 결함의 75%는 프로그램 실행에는 영향을 주지 않지만 "소프트웨어의 발전 가능성에는 영향을 미칩니다."2 특정 안티패턴이나 잘못된 습관이 지금 당장은 버그를 일으키지 않더라도, 이후 코드베이스에서 버그, 장애 또는 보안 취약점이 발생할 가능성을 크게 높일 수 있습니다. 최소한 알려진 코드 스멜이 존재하면 코드의 가독성과 유지 관리성이 떨어져 이해하고 수정하며 개선하기가 더 어려워집니다.
Think 뉴스레터를 통해 AI, 자동화, 데이터 등 가장 중요하고 흥미로운 업계 동향에 대한 최신 소식을 받아보세요. IBM 개인정보 보호정책을 참조하세요.
많은 코드 스멜이 여전히 Beck과 Fowler가 Refactoring에서 사용한 명칭으로 알려져 있지만, 모든 사람이 공통으로 사용하는 단일한 코드 스멜 목록은 존재하지 않습니다. 특정 코드 스멜을 어떤 이름으로 부를지, 또는 독립적인 안티패턴으로 볼지 다른 안티패턴의 하위 유형으로 분류할지는 크게 중요하지 않습니다. 중요한 것은 코드 스멜의 이름과 설명이 소프트웨어 개발 팀이 바람직한 설계 원칙을 공유하고 실천하는 데 충분히 명확하고 직관적인지 여부입니다.
마찬가지로 코드 스멜의 유형을 분류하는 방식에도 보편적으로 합의된 기준은 없습니다. 이 문서에서는 주로 Jerzyk와 Madeyski가 2023년에 제안한 분류 체계를 따릅니다.3 이들의 연구 결과는 codesmells.org에서 관리하는 카탈로그에 정리되어 있으며, 각 코드 스멜에 대한 추가 설명, 코드 예제 및 적절한 리팩터링 기법을 제공합니다.
Jerzyk와 Madeyski는 가장 널리 인용되는 코드 스멜 분류 체계가 Mäntylä와 Lassenius가 2006년에 제안한 다섯 가지 범주를 따른다고 설명합니다. 이들은 Fowler와 Beck이 소개한 22개의 코드 스멜과 자신들이 추가한 1개를 Bloaters, Object-Orientation Abusers, Change Preventers, Dispensables, Couplers의 다섯 범주로 분류했습니다.4 Jerzyk와 Madeyski는 정식 논문뿐 아니라 블로그, 포럼, 위키와 같은 '회색 문헌'까지 폭넓게 검토한 결과, 총 56개의 코드 스멜을 앞서 언급한 다섯 가지를 포함한 아홉 개의 범주로 정리했습니다.
이 섹션에서는 주로 이들이 제안한 아홉 가지 범주를 따르지만, 이러한 분류는 비공식적이고 주관적이라는 점을 염두에 둘 필요가 있습니다. 가장 유용한 분류 체계는 자신이 가장 이해하기 쉬운 방식입니다. 개별 코드 스멜을 단순히 암기하는 것보다, 각각이 어떤 문제나 비효율을 초래하는지 이해하는 것이 코드 스멜을 더 효과적으로 이해하는 데 도움이 됩니다. Mäntylä의 말처럼 "코드 스멜을 단순히 긴 목록으로 나열하면 전체적인 그림을 놓치기 쉽습니다."5
Bloaters는 메서드, 클래스 또는 코드 블록이 지나치게 커져 다루기 어려워지는 코드 스멜입니다. 이로 인해 가독성이 떨어지고 유지 관리와 수정이 어려워집니다.
Bloaters의 예는 다음과 같습니다.
데이터 뭉치: 여러 곳에서 항상 함께 나타나는 변수 그룹입니다.
거대한 클래스: 너무 많은 역할을 수행하려 하고 변수도 과도하게 포함하여 응집성이 부족한 클래스입니다.
긴 함수(긴 메서드): 너무 많은 코드 라인을 포함하는 메서드입니다.
긴 매개변수 목록: 올바르게 동작하기 위해 너무 많은 인수를 요구하는 함수입니다.
원시 타입 집착: 특화된 작은 객체 대신 원시 데이터 타입을 사용하는 경우입니다.
Change Preventers는 이름 그대로 소프트웨어를 수정하거나 기능을 추가하고 확장하는 것을 어렵게 만드는 코드 스멜입니다. 대표적인 예는 단순한 변경 하나를 적용하기 위해 여러 위치를 동시에 수정해야 하는 코드 구조입니다.
이러한 코드 스멜은 Robert C. Martin의 단일 책임 원칙(SRP)을 위반합니다. 이 원칙은 하나의 코드 모듈에 대한 변경은 하나의 이유에서만 발생해야 한다고 설명합니다. Martin은 SRP를 설명하는 글에서 "같은 이유로 변경되는 것은 함께 모으고, 다른 이유로 변경되는 것은 분리하라"고 말했습니다.6
Change Preventers의 예는 다음과 같습니다.
Shotgun surgery: 하나의 변경을 적용하기 위해 여러 모듈을 동시에 수정해야 하는 경우(사실상 Large Class의 반대 개념)
Divergent change: Shotgun surgery와 반대로 하나의 변경을 위해 하나의 클래스 내부를 여러 곳 수정해야 하는 경우
Callback hell: 여러 메서드가 깊게 중첩되어 원인과 결과를 파악하기 어렵고 코드의 가독성과 유지 관리성이 크게 떨어지는 구조
Couplers는 클래스 간에 과도한 종속성을 만들어 낮은 결합도라는 핵심 설계 원칙을 위반하는 코드 스멜입니다. 이로 인해 코드 재사용성이 낮아지고 독립적인 단위 테스트가 어려워지거나 불가능해질 수 있습니다.
Couplers의 예는 다음과 같습니다.
Feature envy: 하나의 메서드가 다른 객체의 데이터에 과도하게 의존하는 경우
Insider trading(또는 inappropriate intimacy): 한 클래스가 다른 클래스의 내부 필드나 메서드에 과도하게 의존하는 경우
Message chains: 객체 간 메서드 호출이 길게 연쇄적으로 이어지는 구조
Data Dealers는 필요한 것보다 훨씬 많은 클래스나 함수를 거쳐 데이터를 전달하는 코드 스멜입니다. 이로 인해 불필요한 종속성과 복잡성이 생기며, 의미 있는 동작 없이 데이터를 전달하거나 보관만 하는 코드가 만들어지는 경우가 많습니다.
Data Dealers의 예는 다음과 같습니다.
Middle man: 다른 객체에 작업을 전달하는 역할만 수행하는 클래스
Tramp data: 실제로 사용되지 않는 데이터가 여러 메서드를 거쳐 함께 전달되는 경우
Global data: 코드베이스 어디에서나 변수를 수정할 수 있어 문제가 발생했을 때 모든 함수가 원인이 될 수 있는 구조
Dispensables는 말 그대로 없어도 되는 코드 요소를 의미합니다. 이러한 요소를 제거하면 전체 기능에는 큰 영향을 주지 않으면서 코드베이스를 더 깔끔하고 읽기 쉽게 만들 수 있습니다.
Dispensables의 예는 다음과 같습니다.
Comments: 주석은 일반적으로 도움이 되지만, 때로는 코드를 개선하기보다 코드 스멜을 설명하는 '탈취제' 역할만 하는 경우가 있습니다. 예를 들어 특정 코드가 무엇을 하는지 주석으로 설명해야 한다면, 이는 코드 자체만으로는 충분히 직관적이거나 읽기 쉽지 않다는 것을 감추고 있는 것일 수 있습니다. 또한 주석은 시간이 지나면서 불필요해지거나 실제 코드와 맞지 않는 내용이 될 수도 있습니다. 주석과 관련된 보다 구체적인 문제는 다른 코드 스멜 유형에서도 다룹니다.
Data class: 필드와 접근자만 있을 뿐 의미 있는 동작을 수행하지 않는 클래스
Dead code: 리팩터링이나 기타 변경으로 더 이상 실행되지 않게 된 코드
Duplicate code: 동일하거나 매우 유사한 코드가 여러 곳에 반복되는 경우
Lazy class(또는 Lazy elements): 존재할 만큼의 역할이나 기능을 수행하지 않는 클래스 또는 함수
Speculative generality: 앞으로 필요할지도 모른다는 이유만으로 미리 추가된 불필요한 코드
Functional Abusers는 객체 지향 코드베이스에 함수형 프로그래밍 패턴을 무리하게 적용하여 객체 지향 설계 원칙을 벗어나는 코드 스멜입니다.
Functional Abusers의 예는 다음과 같습니다.
Loops: 최신 파이프라인 연산 대신 전통적인 반복문을 사용하는 경우 Fowler는 대부분의 반복문을 오래된 방식으로 보았지만, Jerzyk는 특히 명령형 반복문이 핵심적인 문제라고 주장했습니다.
Mutable data: 변수의 상태가 예상치 않게 변경되어 예측하기 어려운 부작용을 일으키는 경우
Side effects(또는 Impure functions): 이름이나 본래 목적 이상의 작업을 수행하는 메서드
Lexical Abusers는 부적절한 이름 규칙, 일관성 없는 서식 또는 이해하기 어려운 문법에서 비롯되는 코드 스멜입니다. 쉽게 말해 코드의 이름이나 표현이 실제 동작과 직관적으로 연결되지 않아 가독성을 떨어뜨리는 경우입니다.
Lexical Abusers의 예는 다음과 같습니다.
Magic number: 충분한 설명이나 맥락 없이 코드에 직접 사용된 숫자 상수
Fallacious comment: 주변 코드가 변경되어 더 이상 실제 코드와 맞지 않는 주석 주석은 실제로 실행되지 않기 때문에 린터나 기타 자동 검사에서도 쉽게 놓칠 수 있습니다.
Mysterious name: 함수나 변수의 이름이 부적절하여 실제 의도를 파악하기 어려운 경우
Fallacious method name: 일반적인 명명 규칙이나 기대와 달리 실제 동작을 오해하게 만드는 함수 이름 예를 들어 getSomething이라는 이름을 사용하지만 실제로는 아무 값도 반환하지 않는 함수가 이에 해당합니다.
Obfuscators는 불필요하게 복잡하거나 지나치게 '영리한' 방식으로 작성되어, 과도한 추상화 때문에 코드의 본래 의도가 드러나지 않는 코드 요소를 의미합니다. 이러한 코드 스멜은 소스 코드의 이해를 어렵게 만들어 이후 개발자가 코드를 파악하고 수정하기 어렵게 만듭니다.
Obfuscators의 예는 다음과 같습니다.
Vertical separation: 서로 관련 있는 코드 요소가 불필요하게 멀리 떨어져 있는 경우. 예를 들어 메서드 맨 위에서 선언한 변수가 50줄 뒤에야 처음 사용되는 경우입니다.
Obscured intent: 함수, 변수, 이름 또는 숫자의 목적이 직관적이지도 않고 문맥상으로도 명확하지 않은 경우를 포괄하는 유형
복잡한 불리언 표현식: 이중 부정이나
Clever code: 동작은 하지만 이미 널리 사용되는 내장 기능이나 일반적인 해결 방법이 있는데도 이해하기 어려운 독창적인 구현을 사용하는 코드
Object-Oriented Abusers는 객체 지향 설계 원칙을 충분히 또는 올바르게 적용하지 못해 발생하는 코드 스멜입니다. 예를 들어 switch 문은 절차적 프로그래밍에서는 유용하지만, 객체 지향 프로그래밍에서는 가능한 한 사용을 피하는 것이 좋습니다.
Object-Oriented Abusers의 예는 다음과 같습니다.
Alternative class with different interfaces: 비슷한 기능을 수행하면서도 메서드 이름이 완전히 달라 일관성이 없는 클래스
Refused bequest: 상속받은 메서드를 실제로 사용하지 않거나 필요로 하지 않는 하위 클래스
Switch statements(또는 Conditional Complexity, Repeated Switches): 동일한 switch 문이 코드베이스 여러 곳에서 반복되는 경우
Temporary field: 특정 상황에서만 사용되는데도 평소부터 선언되어 있는 변수
코드 스멜을 이해하는 가장 큰 장점은 이를 식별함으로써 리팩터링을 더욱 효과적으로 수행할 수 있다는 것입니다. 리팩터링은 소프트웨어의 외부 동작이나 기능은 그대로 유지하면서 소스 코드를 개선하는 작업을 의미합니다. 리팩터링을 통해 코드를 지속적으로 정리하면 기술 부채를 줄일 수 있으며, 시간이 지나도 코드베이스를 빠르고 효율적으로 개선하고 기능을 추가할 수 있습니다.
리팩터링은 결국 코드 스멜을 찾아 해결하는 과정이라고 볼 수 있습니다. 리팩터링의 목적은 문제가 기능에 영향을 미치기 전에 미리 해결하는 것입니다. 문제가 실제 동작에 영향을 주기 시작했다면 그것은 리팩터링보다는 디버깅에 가깝습니다.
IBM Bob과 같은 최신 에이전틱 엔지니어링 플랫폼은 실시간으로 자동 리팩터링 제안을 제공합니다. 코드 스멜의 정의를 단순히 암기하는 것이 아니라 실제 코드베이스에서 나타나는 일반적인 코드 스멜을 폭넓게 학습한 이러한 플랫폼의 AI 코드 리팩터링 기능은, 나중에 이해하거나 수정하기 어려운 복잡한 AI 생성 코드가 함께 늘어나지 않으면서도 개발자의 생산성을 높일 수 있도록 지원합니다.
IBM® Bob와 함께 안전하고 의도를 이해하는 AI 개발 파트너를 활용하여 소프트웨어 딜리버리를 가속화하세요.
엔터프라이즈 환경에 적합한 툴을 사용하여 AI 애플리케이션을 더욱 빠르게 개발, 배포 및 관리하세요.
지능형 AI 현대화로 레거시 시스템을 새롭게 혁신하세요.