코드스멜: 유지보수를 어렵게 만드는 설계 경고 신호
코드스멜의 유형과 리팩토링 방향을 정리한다. 애플리케이션·클래스·메서드 수준에서 품질과 유지보수성을 해치는 신호를 다룬다.
2026-08-14 · 최초 발행 2025-05-23
코드가 지금은 문제없이 실행되더라도, 다음 변경을 어렵게 만드는 구조라면 이미 경고 신호가 나타난 셈이다. 코드스멜은 유지보수성과 확장성을 해치는 잠재적 설계 문제를 가리킨다. 음식의 이상한 냄새가 부패 가능성을 알리듯, 코드스멜도 더 큰 결함으로 이어질 수 있는 징후다.
코드스멜을 일찍 식별하면 품질 저하와 기술 부채의 축적을 막는 데 도움이 된다. 리팩토링이 필요한 지점을 찾고 유지보수 비용을 줄이는 기준으로도 쓸 수 있다.
애플리케이션 전반에서 드러나는 신호
의도를 알 수 없는 이름
변수, 함수, 클래스의 이름이 역할과 목적을 설명하지 못하는 경우다. doStuff()나 processData()처럼 모호한 함수명은 코드를 읽는 사람에게 추가 해석을 요구하고 가독성을 떨어뜨린다.
복제된 로직
같거나 비슷한 코드가 여러 위치에 존재하면 변경할 때 모든 복제본을 함께 고쳐야 한다. 일부만 수정될 가능성이 커지며 오류도 발생하기 쉬워진다.
// 중복 코드의 예
public void processOrder() {
// 주문 처리 로직 100줄
}
public void cancelOrder() {
// 동일한 주문 처리 로직 100줄 복사 후 약간 수정
}
필요 이상으로 복잡한 해결책
단순한 문제에 불필요한 추상화나 패턴을 덧씌우면 이해와 유지보수가 모두 어려워진다. 복잡성 자체가 목적이 되는 구조는 가독성을 낮춘다.
변경이 여러 곳으로 흩어지는 구조
하나의 변경을 위해 다수의 클래스를 수정해야 한다면 산탄총 수술의 징후다. 높은 결합도를 나타내며, 변경 영향 범위를 파악하기 어려워 오류 가능성도 커진다.
예측하지 못한 상태 변경
함수가 기대하지 않은 상태를 바꾸는 경우에는 호출 결과를 추론하기 어렵다. 이는 함수형 프로그래밍 원칙을 벗어나며 디버깅 난이도를 높이는 요인이 된다.
추적하기 어려운 변수 변경
변수 값이 여러 위치에서 예측하기 어렵게 바뀌면 프로그램 흐름을 따라가기 힘들어진다. 불변성(Immutability) 원칙을 위배하는 상황이기도 하다.
불리언으로 표현한 복잡한 상태
불리언 값만으로 복잡한 상태를 나타내려 하면 코드의 의도가 흐려진다. 이런 경우에는 열거형이나 상태 객체로 표현을 바꾸는 편이 적합하다.
클래스의 책임과 관계에서 생기는 스멜
책임을 과도하게 떠안은 클래스
하나의 클래스가 너무 많은 일을 맡으면 단일 책임 원칙(SRP)을 위배한다. 수백/수천 라인의 코드가 한 클래스에 몰린 "갓 클래스(God Class)"가 대표적이다.
다른 객체의 데이터에 집착하는 코드
기능 욕심은 한 클래스의 메서드가 자신의 데이터보다 다른 클래스의 데이터에 더 많이 의존할 때 나타난다. 책임 배분이 적절하지 않다는 신호다.
지나치게 가까운 클래스 관계
클래스 사이의 결합이 과도하면 캡슐화가 무너지고 의존성이 늘어난다. 한쪽의 변경이 다른 쪽을 쉽게 흔들어 변경 용이성이 낮아진다.
쓰이지 않는 상속
서브클래스가 상속받은 메서드나 필드를 사용하지 않는다면 거부된 유산을 의심할 수 있다. "is-a" 관계가 아닌데 상속을 적용한 경우이며, 컴포지션으로 대체할 필요가 있다.
역할이 거의 없는 클래스
게으른 클래스 또는 Freeloader는 존재 가치에 비해 제공하는 기능이 너무 적은 클래스를 말한다. 불필요한 복잡성을 늘리고 코드 이해를 방해한다.
매직 넘버와 문자열 리터럴의 반복
리터럴을 과도하게 사용하면 의미를 파악하기 어렵고 수정도 번거로워진다. 매직 넘버나 문자열 리터럴은 상수 또는 열거형으로 바꾸는 방식을 검토할 수 있다.
분기가 지나치게 많은 코드
조건문과 분기가 과도한 순환 복잡도는 테스트를 어렵게 하고 버그 가능성을 높인다. 함수를 분해해 단순화하는 접근이 필요하다.
명시적 다운캐스팅
상위 타입을 하위 타입으로 명시적으로 변환하는 다운캐스팅은 객체지향 다형성 원칙을 위배할 수 있다. 런타임 오류 가능성도 함께 커진다.
데이터만 남은 클래스
고아 변수 또는 상수 클래스는 데이터만 담고 행위가 없는 클래스를 뜻한다. 객체지향 원칙이 부분적으로만 적용됐거나 책임 할당이 적절하지 않을 수 있다.
함께 움직이는 데이터 그룹
여러 위치에서 동일한 데이터 묶음이 반복된다면 데이터 뭉치다. 응집도 있는 클래스로 추출하면 매개변수 목록을 줄이는 데 도움이 된다.
메서드 안에서 확인할 구조적 문제
호출부를 복잡하게 만드는 매개변수 목록
함수가 과도한 수의 매개변수를 받으면 호출이 복잡해지고 가독성이 떨어진다. 관련된 값은 객체로 묶어 전달할 수 있다.
// 나쁜 예
void createUser(String firstName, String lastName, String email,
String street, String city, String zipCode,
String country, String phone, Date birthDate);
// 좋은 예
void createUser(Person person, Address address);
한 메서드에 몰린 작업
긴 메서드는 너무 많은 작업을 수행해 단일 책임 원칙을 위배한다. 이해와 테스트가 모두 어려운 코드가 되기 쉽다.
지나치게 긴 식별자와 지나치게 짧은 식별자
장황한 이름은 가독성을 떨어뜨리고, 너무 짧은 이름은 의미 전달에 실패한다. 필요한 정보만 담되 목적과 의도가 드러나는 이름이 필요하다.
필요 이상의 데이터를 돌려주는 메서드
클라이언트가 필요로 하지 않는 데이터까지 반환하면 정보 은닉 원칙을 위배할 수 있다. 불필요한 의존성도 늘어난다.
코드를 대신 설명하는 주석
주석이 지나치게 많다면 코드 자체가 명확하지 않을 가능성이 있다. 자기 설명적 코드(Self-documenting code)가 되도록 이름과 구조를 다듬는 편이 낫다.
한 줄에 과도하게 담긴 논리
너무 긴 코드 라인은 분석과 디버깅을 어렵게 한다. 연산과 논리를 여러 단계로 나누어 표현할 필요가 있다.
감지 결과를 리팩토링으로 연결하기
SonarQube는 정적 코드 분석과 품질 측정에, PMD는 다양한 프로그래밍 언어의 정적 분석에 활용할 수 있다. JavaScript의 코드 품질 관리는 ESLint, Java 코드 스타일 점검은 Checkstyle이 맡을 수 있다.
스멜을 발견했다고 즉시 큰 폭의 구조 변경을 시작할 필요는 없다. 긴 메서드는 추출 메서드(Extract Method)로 나누고, 관련 책임은 클래스 추출(Extract Class)로 옮길 수 있다. 관련 매개변수는 매개변수 객체(Parameter Object)로 그룹화하며, 잘못 놓인 동작은 메서드 이동(Move Method)으로 적절한 클래스로 옮긴다. 상속 관계가 부적절하다면 상속을 컴포지션으로 대체(Replace Inheritance with Composition)한다.
발견과 개선이 이어지는 팀 운영
코드스멜 관리는 개인의 판단만으로 끝나기 어렵다. 지속적인 코드 리뷰 문화를 만들고 정적 분석 도구를 CI/CD 파이프라인에 통합할 수 있다. 코드를 발견했을 때보다 더 깨끗하게 남기는 "보이 스카우트 규칙"도 유용한 기준이다.
팀 안에서 코딩 표준과 가이드라인을 정하고, 기술 부채 관리를 위한 정기적인 리팩토링 시간을 확보해야 한다. 코드스멜을 인식하고 적절한 리팩토링으로 다루는 역량은 프로젝트의 장기적인 품질과 개발 생산성에 직접 영향을 준다.