Code Smell
코드 스멜(Code Smell)은 컴퓨터 프로그래밍 코드에서 심오한 문제를 일으킬 가능성이 있는 프로그램 소스 코드의 특징을 가리킵니다. 무엇이 코드 스멜인지 아닌지의 여부를 결정하는 일은 주관적인 것으로 언어와 개발자, 개발 방법에 따라서 다양하게 나타납니다. 자동으로 특정한 종류의 코드 스멜을 확인하기 위한 도구들이 있으며, 그 예로는 체크스타일, PMD, 파인드벅스 등을 들 수 있습니다.
즉, 코드스멜이란 읽기 어렵거나 중복된 로직을 가진 메서드, 개발자가 이해하거나 유지보수하기 어려워 리팩토링의 대상이 되는 소스코드, 좋은 소프트웨어 개발 관행을 위반하거나 설계원칙을 어기는 코드들이라고 볼 수 있습니다.
이렇기에 코드스멜은 가독성 저하, 유지보수 어려움, 성능 저하 등 부정적인 영향을 유발할 수 있기에 개선이 필요한 부분으로 간주됩니다.
일반적인 코드 스멜 (General Code Smells) & 원인 및 리팩토링 기법
- 신비로운 이름 (Mystery Name): 의미를 알기 어려운 변수, 함수, 클래스 이름 사용
- 🔹 원인: 직관적이지 않은 이름, 약어 사용, 의미 없는 변수명
- 🔧 리팩토링: 명확한 이름 사용 (Rename Variable/Method/Class) → 의미를 쉽게 이해할 수 있도록 직관적인 이름으로 변경
- 중복 코드 (Duplicate Code): 동일하거나 유사한 코드가 여러 곳에 존재
- 🔹 원인: 복사-붙여 넣기 개발, 재사용을 고려하지 않은 코드 작성
- 🔧 리팩토링: 코드 통합 (Extract Method, Extract Class) → 반복되는 코드 블록을 별도의 메서드나 클래스로 분리
- 복잡성 (Unnecessary Complexity): 불필요하게 복잡한 구조나 로직
- 🔹 원인: 과도한 조건문, 불필요한 연산, 비효율적인 알고리즘
- 🔧 리팩토링: 단순화 (Simplify Conditionals, Extract Method) → 불필요한 로직을 제거하고 가독성이 좋은 구조로 변경
- 산탄총 수술 (Shotgun Surgery): 작은 변경이 여러 곳의 수정을 필요로 함
- 🔹 원인: 관련 기능이 여러 파일/클래스에 분산됨
- 🔧 리팩토링: 기능 통합 (Move Method, Move Field) → 자주 변경되는 코드들을 하나의 클래스나 모듈로 합침
- 제어할 수 없는 부작용 (Side Effects): 함수 호출이 예상치 못한 영향을 미침
- 🔹 원인: 전역 변수 사용, 예상치 못한 상태 변경
- 🔧 리팩토링: 명확한 출력 구조 (Command-Query Separation) → 메서드가 상태를 변경하는 기능과 값을 반환하는 기능을 분리
- 변수 용도 변경 (Variable Reuse): 동일한 변수를 여러 용도로 사용
- 🔹 원인: 메모리 절약을 위한 변수 재사용, 명확하지 않은 코드 설계
- 🔧 리팩토링: 명확한 변수 사용 (Introduce Explaining Variable) → 한 변수에 여러 의미를 담지 않고, 명확한 변수명을 가진 새로운 변수를 사용
- 헷갈리는 Bool 값 (Boolean Trap): 논리적으로 이해하기 어려운 불리언 값 사용
- 🔹 원인: 불필요한 다중 불리언 플래그, 상태가 많아지면서 가독성이 저하됨
- 🔧 리팩토링: Enum 또는 명확한 메서드 사용 (Replace Boolean with Enum) → 불리언 대신 의미를 명확히 할 수 있는 Enum 또는 개별 메서드를 활용
- 신의 객체 (God Object): 하나의 객체가 지나치게 많은 기능을 담당
- 🔹 원인: 단일 객체에 너무 많은 데이터와 기능이 집중됨
- 🔧 리팩토링: 클래스 분할 (Extract Class, Extract Module) → 책임을 여러 클래스로 나누어 단일 책임 원칙(SRP) 적용
- 마법 숫자 (Magic Number): 의미 없이 숫자(또는 문자열)를 하드코딩
- 🔹 원인: 하드코딩된 상수값 사용, 코드 이해가 어려움
- 🔧 리팩토링: 상수화 (Replace Magic Number with Constant) → 의미 있는 상수(Constant)로 변경하여 가독성 향상
- 하드코딩된 의존성 (Hardcoded Dependency): 외부 요소를 직접 코드에 결합
- 🔹 원인: 직접적인 클래스 인스턴스화, 의존성 주입 미사용
- 🔧 리팩토링: 의존성 주입 (Dependency Injection) → 인터페이스를 활용하여 유연한 구조로 변경
- 죽은 코드 (Dead Code): 사용되지 않는 코드가 남아 있음
- 🔹 원인: 과거 기능이 제거되지 않음, 미사용 변수/메서드 존재
- 🔧 리팩토링: 제거 (Remove Dead Code, Inline Variable) → 불필요한 코드 삭제
- 과도한 일반화 (Speculative Generality): 필요하지 않은 유연성을 고려한 설계
- 🔹 원인: 미래 확장을 고려해 필요 없는 인터페이스/추상 클래스 추가
- 🔧 리팩토링: 단순화 (Remove Unused Abstraction, Inline Class) → 현재 필요하지 않은 일반화된 구조 제거
클래스 수준 코드 스멜 (Class-Level Code Smells) & 원인 및 리팩토링 기법
- 커다란 클래스 (Large Class): 너무 많은 기능을 가진 거대한 클래스
- 🔹 원인: 단일 클래스에서 여러 책임을 처리하려는 경향
- 🔧 리팩토링: 클래스 분할 (Extract Class, Extract Module) → 관련된 기능을 별도의 클래스로 분리
- 기능에 대한 욕심 (Feature Creep): 한 클래스가 너무 많은 책임을 가짐
- 🔹 원인: 단일 책임 원칙(SRP) 위반, 지속적인 기능 추가
- 🔧 리팩토링: 단일 책임 원칙 준수 (Single Responsibility Principle) → 한 클래스가 여러 책임을 갖지 않도록 기능을 나누고, 관련 클래스로 이동
- 부적절한 관계 (Inappropriate Relationship): 잘못된 상속 또는 의존 관계
- 🔹 원인: 필요 없는 상속, 의존성이 강한 코드 구조
- 🔧 리팩토링: 상속 대신 조합 사용 (Replace Inheritance with Composition) → 불필요한 상속 구조를 제거하고 객체 조합(Composition)으로 변경
- 거부된 유산 (Refused Bequest): 상속받은 기능을 사용하지 않음
- 🔹 원인: 잘못된 클래스 계층 구조
- 🔧 리팩토링: 상속 구조 수정 (Replace Inheritance with Delegation) → 상속 대신 위임(Delegation) 사용
- 리터럴의 과도한 사용 (Excessive Literal Usage): 하드코딩된 값이 지나치게 많음
- 🔹 원인: 의미 없는 숫자/문자열 하드코딩
- 🔧 리팩토링: 상수화 (Replace Magic Number with Constant) → 하드코딩된 값을 의미 있는 상수(Constant)로 변경
- 순환 복잡도 (Cyclomatic Complexity): 너무 많은 조건문과 루프 사용
- 🔹 원인: 복잡한 조건문(중첩 if, switch), 과도한 분기
- 🔧 리팩토링: 다형성 적용 (Replace Conditional with Polymorphism) → 조건문을 다형성(Polymorphism)으로 대체
- 다운캐스팅 (Downcasting): 부모 클래스를 자식 클래스로 강제 변환
- 🔹 원인: 잘못된 상속 설계, 유연하지 않은 코드 구조
- 🔧 리팩토링: 다형성 활용 (Replace Type Code with Subclasses/Strategy Pattern) → 다운캐스팅 대신 다형성을 적용하여 처리
- 고아 변수 또는 컨스턴트 클래스 (Orphan Variable or Constant Class): 기능 없이 값만 포함된 클래스
- 🔹 원인: 단순한 값 저장용 클래스로 인해 유지보수 어려움
- 🔧 리팩토링: 필요한 위치로 이동 (Move Field, Inline Class) → 해당 필드를 적절한 도메인 클래스에 포함
- 데이터 덩어리 (Data Class): 데이터만 포함된 비효율적인 구조
- 🔹 원인: 행동 없이 속성만 있는 클래스
- 🔧 리팩토링: 객체에 기능 추가 (Encapsulate Field, Move Method) → 데이터를 처리하는 메서드를 클래스 내부로 이동
- 부적절한 친밀함 (Inappropriate Intimacy): 클래스가 너무 밀접하게 결합
- 🔹 원인: 다른 클래스의 내부 상태나 기능을 과도하게 참조
- 🔧 리팩토링: 의존성 줄이기 (Move Method, Extract Class) → 메서드나 필드를 적절한 클래스로 이동
- 다양한 변경 이유 (Divergent Change): 한 클래스가 여러 가지 이유로 자주 변경됨
- 🔹 원인: 단일 책임 원칙(SRP) 위반, 너무 많은 역할 수행
- 🔧 리팩토링: 책임 분리 (Extract Class, Extract Module) → 하나의 클래스가 여러 책임을 가지지 않도록 기능을 분리
- 기능 질투 (Feature Envy): 특정 클래스의 기능을 다른 클래스가 지나치게 사용
- 🔹 원인: 특정 객체의 데이터나 메서드를 다른 클래스가 많이 호출
- 🔧 리팩토링: 기능 이동 (Move Method, Extract Function) → 해당 기능을 적절한 클래스 안으로 이동
메서드 수준 코드 스멜 (Method-Level Code Smells) & 원인 및 리팩토링 기법
- 너무 많은 매개변수 (Long Parameter List): 함수나 메서드가 너무 많은 인자를 요구
- 🔹 원인: 관련 데이터가 그룹화되지 않음
- 🔧 리팩토링: 객체화 (Introduce Parameter Object) → 여러 개의 관련된 매개변수를 하나의 객체로 묶음
- 긴 메서드 (Long Method): 한 메서드가 너무 길어 가독성이 떨어짐
- 🔹 원인: 여러 기능을 한 메서드에서 처리
- 🔧 리팩토링: 메서드 분리 (Extract Method, Extract Function) → 하나의 메서드를 여러 개의 작은 메서드로 분리
- 과도하게 긴 식별자 (Overly Long Identifier): 이해하기 어려운 지나치게 긴 변수/함수 이름
- 🔹 원인: 필요 이상으로 상세한 변수/메서드명 사용
- 🔧 리팩토링: 간결한 네이밍 (Rename Variable, Shorten Identifier) → 직관적이면서도 간결한 이름 사용
- 과도하게 짧은 식별자 (Overly Short Identifier): 의미를 알기 어려운 너무 짧은 변수/함수 이름
- 🔹 원인: 의미를 전달하지 못하는 단축형 변수명 사용
- 🔧 리팩토링: 명확한 이름 사용 (Rename Variable, Expand Identifier) → 직관적인 변수명 적용
- 과도한 데이터 반환 (Excessive Data Return): 필요 이상으로 많은 데이터를 반환
- 🔹 원인: 메서드가 너무 많은 데이터를 클라이언트에 제공
- 🔧 리팩토링: 필요한 데이터만 반환 (Encapsulate Collection, Reduce Data Scope) → 불필요한 데이터 반환을 최소화
- 과도한 주석 (Excessive Comments): 코드 자체로 이해할 수 없는 과도한 주석 사용
- 🔹 원인: 가독성이 낮은 코드
- 🔧 리팩토링: 코드 자체를 이해하기 쉽게 개선 (Self-Documenting Code) → 의미 있는 변수명과 함수명을 사용하여 주석 없이도 코드가 이해되도록 작성
- 과도하게 긴 줄로 된 코드 (Overly Long Lines of Code): 한 줄에 너무 많은 내용이 포함되어 가독성이 낮아짐
- 🔹 원인: 한 줄에 너무 많은 연산 수행, 가독성 고려 부족
- 🔧 리팩토링: 줄바꿈 및 가독성 개선 (Reformat Code, Extract Variable) → 적절한 줄 바꿈 적용 및 가독성 향상
- 메시지 체인 (Message Chain): 여러 객체를 거쳐 메서드를 호출하는 패턴 (A.getB().getC().doSomething())
- 🔹 원인: 디미터 법칙(Law of Demeter) 위반
- 🔧 리팩토링: 디미터 법칙 준수 (Hide Delegate) → 중간 객체를 숨기고 필요한 정보만 노출
- 중개자 클래스 (Middle Man): 클래스가 단순히 다른 클래스의 기능을 전달하기만 함
- 🔹 원인: 불필요한 위임, 객체 간 불필요한 간접 호출 증가
- 🔧 리팩토링: 중개자 제거 (Remove Middle Man, Inline Method) → 직접 호출이 가능하도록 중개자를 제거하거나, 필요한 로직만 유지
- 과부하된 메서드 (Overloaded Method): 지나치게 많은 역할을 하는 단일 메서드
- 🔹 원인: 하나의 메서드가 다양한 기능을 수행하려 함
- 🔧 리팩토링: 메서드 분리 (Split Method, Extract Function) → 메서드를 작은 단위로 나누어 각각의 역할을 명확하게 분리
- 숨겨진 시간적 결합 (Hidden Temporal Coupling): 특정 메서드를 실행하기 전에 다른 메서드를 먼저 호출해야 하는 상황
- 🔹 원인: 실행 순서가 암묵적으로 정해져 있음
- 🔧 리팩토링: 명시적인 실행 순서 제공 (Introduce Explicit Dependency) → 실행 순서를 명확하게 하는 구조로 변경
테스트 코드 관련 코드 스멜 (Test Code Smells) & 원인 및 리팩토링 기법
- 취약한 테스트 (Fragile Test): 코드 변경 시 너무 많은 테스트가 깨짐
- 🔹 원인: 특정 구현 세부 사항에 의존하는 테스트
- 🔧 리팩토링: 독립적인 테스트 작성 (Introduce Mocking, Improve Test Design) → 코드 변경과 무관하게 독립적인 테스트 구성
- 과도한 테스트 조건 (Overspecified Test): 너무 구체적인 테스트로 인해 유지보수가 어려움
- 🔹 원인: 특정 값이나 내부 로직에 대한 지나친 의존
- 🔧 리팩토링: 핵심 로직만 테스트 (Relax Test Assertions, Use Parameterized Tests) → 핵심 기능만 테스트하도록 단순화
- 프로덕션 코드에 있는 테스트 로직 (Test Logic in Production): 테스트 코드가 프로덕션 코드에 남아 있음
- 🔹 원인: 디버깅 목적으로 테스트 코드가 포함됨
- 🔧 리팩토링: 테스트 코드 분리 (Move Test Code to Test Class) → 프로덕션 코드에서 테스트 코드를 제거하고 별도의 테스트 파일로 이동
'Software Engineering > Software Development Principles' 카테고리의 다른 글
동적 연결 라이브러리 (DLL, Dynamic Link Library) (0) | 2025.04.28 |
---|---|
의존성 주입(DI, Dependency Injection) (1) | 2025.03.05 |
Refactoring(리팩토링) (0) | 2025.03.03 |
Compiler (0) | 2025.03.03 |