모바일 개발에서의 Code Smell: 본질, 유형 및 수정 원칙

저자: IT Sectr 게시일: 2026-05-13 읽는 시간: 9 분

Code Smell은 애플리케이션의 디자인이나 아키텍처에 잠재적인 문제가 있음을 알리는 코드의 표면적 징후입니다. 이 용어는 Kent Beck이 만들었고 Martin Fowler가 저서 “Refactoring: Improving the Design of Existing Code”에서 대중화했습니다. Martin Fowler에 따르면, 코드 냄새가 반드시 버그를 의미하는 것은 아니지만 유지보수성을 개선하기 위한 리팩토링의 필요성을 거의 항상 나타냅니다.

핵심 사항

  • Code Smell — 오류는 아니지만 유지보수와 개발을 복잡하게 만드는 코드 문제의 표면적 지표
  • Long Method — 가장 흔한 냄새: 너무 많은 일을 하는 메서드로, 여러 개로 분할해야 함
  • Large Class — 단일 책임 원칙을 위반하고 여러 도메인의 로직을 포함하는 클래스
  • Duplicate Code — 변경 시 여러 곳에서 수정이 필요한 반복되는 코드 조각
  • Feature Envy — 자신의 클래스보다 다른 클래스의 데이터를 더 많이 사용하는 메서드

Code Smell이란

Code Smell은 높은 확률로 더 깊은 문제를 나타내는 소스 코드 증상에 대한 은유입니다. 이 용어 자체에는 공식적인 정의가 없습니다 — 이는 개발자의 경험에 기반한 휴리스틱입니다. Martin Fowler와 Kent Beck은 1999년 저서 “Refactoring”에서 처음으로 22가지 냄새를 체계화했으며, 대부분은 수십 년이 지난 지금도 여전히 유효합니다.

Code Smell과 버그의 차이를 이해하는 것이 중요합니다. 냄새는 오류가 아닙니다. 코드는 컴파일되고 작동하며 올바른 결과를 생성합니다. 문제는 그러한 코드를 읽고, 변경하고, 테스트하기 어렵다는 것입니다. 시간이 지남에 따라 각 변경의 비용이 증가하고 리팩토링의 정확성에 대한 신뢰가 감소합니다. 정적 분석 도구(SonarQube, Detekt, SwiftLint)는 많은 냄새를 자동으로 감지합니다.

Code Smell의 휴리스틱 특성은 모든 긴 메서드를 분할해야 하는 것도 아니고 모든 큰 클래스에 리팩토링이 필요한 것도 아님을 의미합니다. 결정은 개발자가 컨텍스트(변경 빈도, 모듈 중요도, 개발 계획)를 평가하여 내립니다. 경험 많은 엔지니어는 직관적으로 냄새를 감지합니다 — 모든 형식적 규칙이 지켜져도 코드에서 “나쁜 냄새”가 납니다.

Code Smell의 주요 유형

Fowler는 여러 범주로 그룹화된 22가지 냄새를 식별했습니다. 모바일 개발의 경우 구조적 냄새, 객체 지향 디자인 냄새 및 플랫폼 제약과 관련된 특정 문제가 가장 관련성이 높습니다. 실제 사례를 통해 각 그룹을 살펴보겠습니다.

구조적 냄새

Long Method는 모바일 애플리케이션에서 가장 흔한 냄새입니다. 등록 양식 화면에는 모든 View를 생성하고, 제약 조건을 설정하고, 이벤트를 구독하고, 오류를 처리하는 200줄 이상의 단일 setupUI 메서드가 있는 경우가 많습니다. 해결책: 논리적 블록별로 메서드 분할 — configureEmailField, configurePasswordField, setupConstraints, bindViewModel.

Large Class — 표시, 탐색, 비즈니스 로직 및 네트워크 상호 작용을 모두 담당하는 Activity 또는 ViewController. 이러한 클래스는 단일 책임 원칙을 위반하고 수십 개의 필드와 메서드를 포함합니다. Android에서는 다른 화면의 로직을 포함하는 1000줄 이상의 Fragment인 경우가 많습니다. 해결책: presenter/ViewModel 추출, 네트워크 코드를 리포지토리로, 탐색을 코디네이터로 이동.

Duplicate Code — 애플리케이션의 다른 부분에서 동일한 블록을 복사하는 것. 일반적인 예: 카탈로그와 즐겨찾기에서 제품 카드를 표시하는 두 화면. 표시 로직이 복사된 경우 한 곳에서 버그를 수정해도 다른 곳에서는 수정되지 않습니다. 해결책: 공통 로직을 재사용 가능한 컴포넌트나 확장으로 추출.

객체 지향 디자인 냄새

Feature Envy — 한 클래스의 메서드가 다른 클래스의 데이터를 집중적으로 사용합니다. Android에서는 ViewModel이 모델의 메서드를 호출하는 대신 User 모델의 필드에 직접 접근할 때 나타납니다. 신호: 메서드를 사용하는 데이터의 클래스로 이동할 수 있다면 — 이동하세요. Switch Statements(조건 체인) — 객체 유형을 확인하는 switch 구문 또는 if-else 체인. 대신 다형성이나 전략 패턴을 사용하세요.

Data Class — 데이터만 저장하고 동작은 포함하지 않는 클래스. Kotlin의 data class나 Swift의 구조체는 그 자체로 냄새가 아닙니다. 문제는 해당 데이터로 작업하는 비즈니스 로직이 캡슐화되지 않고 코드베이스 전체에 흩어져 있을 때 발생합니다. Refused Bequest — 하위 클래스가 부모의 대부분의 메서드를 사용하지 않고 빈 스텁으로 재정의합니다. 잘못된 상속의 징후: 상속을 컴포지션으로 대체하세요.

모바일 개발에서의 냄새

God Activity / God Fragment — 라이프사이클, 데이터, 탐색, 권한, DI 등 모든 것을 알고 있는 Activity 또는 Fragment. 애플리케이션에서 유지보수 비용이 가장 높은 클래스입니다. 해결책: MVVM, MVI 또는 Clean Architecture와 같은 아키텍처 패턴이 책임을 분리합니다. Giant ViewController — iOS의 해당 항목으로, UIViewController가 화면의 모든 로직을 포함하고 종종 500줄을 초과합니다.

Hardcoded Resources — 문자열, 색상, 크기, API URL을 코드에 직접 하드코딩. Android에서는 R 리소스 시스템을 위반하고 iOS에서는 NSLocalizedString 및 Asset Catalog를 위반합니다. 수정: 모든 문자열을 strings.xml 또는 Localizable.strings로, URL을 설정 파일로, 크기를 dimens로 이동. Leaking Context — Activity 또는 ViewController에 대한 참조를 컴포넌트 자체의 수명보다 오래 유지. 메모리 누수 및 충돌을 유발합니다. 해결책: 약한 참조, Jetpack Lifecycle, RxSwift DisposeBag.

냄새발생 위치해결책
Long MethodAndroid/iOSExtract Method, 분할
Large ClassActivity, ViewControllerMVVM, VIPER, Clean Arch
Duplicate Code모든 화면공유 컴포넌트, DRY
Feature EnvyViewModel, PresenterMove Method
Leaking ContextAndroidLifecycle-aware 컴포넌트

Code Smell 찾는 방법

코드 리뷰는 냄새를 감지하는 가장 신뢰할 수 있는 방법입니다. 사람의 눈은 자동 분석 도구가 놓치는 부자연스러운 구성을 알아챕니다. 팀이 일반적인 냄새의 체크리스트를 사용하면 코드 리뷰 효율성이 향상됩니다. 세션당 200~400줄을 초과하는 코드는 검토하지 않는 것이 좋습니다 — 이 임계값 이후에는 주의력이 떨어지고 냄새가 놓치기 시작합니다.

정적 분석은 구조적 냄새 검색을 자동화합니다. Android의 표준 도구는 Detekt(Kotlin)와 Android Lint이고, iOS의 경우 SwiftLint와 SonarQube입니다. 이러한 도구는 긴 메서드, 큰 클래스, 중복 코드 및 기타 많은 문제를 찾습니다. 프로젝트에 맞게 규칙을 조정하는 것이 중요합니다 — 기본 구성은 종종 너무 엄격하거나 반대로 중요한 냄새를 놓칩니다.

코드 메트릭은 객관적인 기준을 제공합니다: 순환 복잡도(임계값 >10은 주의 필요), 메서드당 코드 줄 수(임계값 >30), 상속 깊이(>3 — 고려할 이유). CodeMetrics(Xcode) 및 Gradle Metrics Plugin과 같은 도구는 시간에 따른 메트릭 변화 그래프를 만듭니다. 마지막 커밋 후 메서드의 복잡도가 5에서 15로 증가했다면 — 리팩토링 신호입니다.

kotlin
// 예: 순환 복잡도 = 7인 메서드(임계값 5 초과)
fun processOrder(order: Order) {
    if (order.status == Status.NEW) { /* 10줄 */ }
    else if (order.status == Status.PAID) { /* 15줄 */ }
    else if (order.status == Status.SHIPPED) { /* 20줄 */ }
    else if (order.status == Status.DELIVERED) { /* 8줄 */ }
    else if (order.status == Status.CANCELLED) { /* 5줄 */ }
    else { throw IllegalStateException() }
}

// 수정: switch 대신 다형성
interface OrderHandler {
    fun handle(order: Order)
}

자동화된 냄새 감지는 코드 리뷰를 대체하지 않습니다: 정적 분석 도구는 구조적 문제만 찾고 의미적 냄새(Feature Envy, Inappropriate Intimacy)는 포착하지 못합니다. 자동 도구와 사람의 검토 조합이 최상의 결과를 제공합니다. 복잡도나 메서드 길이 임계값을 초과하면 빌드가 실패하도록 CI/CD 파이프라인을 구성하세요.

Code Smell 수정 방법

리팩토링은 코드 냄새를 제거하는 주요 방법입니다. Fowler는 각각 특정 냄새에 적용되는 수십 가지 리팩토링 기법을 설명합니다. Extract Method — 긴 메서드용, Extract Class — 큰 클래스용, Move Method — Feature Envy용. 각 변경 후에도 코드가 작동하도록 작은 단계로 리팩토링을 수행하는 것이 중요합니다.

리팩토링 전 테스트는 필수입니다. 코드가 단위 테스트로 커버되지 않은 경우 리팩토링은 결과를 알 수 없는 재작성이 됩니다. 테스트가 없는 레거시 코드의 경우 Characterization Tests를 사용하세요 — 현재 동작을 캡처하는 테스트를 작성한 다음 리팩토링하세요. 테스트는 리팩토링 후 비즈니스 로직이 손상되지 않았다는 확신을 줍니다.

점진성은 모바일 개발에서 냄새를 성공적으로 수정하는 핵심입니다. God Activity를 한 번에 완전히 다시 작성하려고 하지 마세요. 먼저 탐색 계층, 그다음 데이터 계층, 그다음 표시 로직을 추출하세요. 각 단계는 커밋과 테스트 실행을 수반해야 합니다. 기능 토글을 사용하여 일부 사용자에게 리팩토링을 활성화하고 문제가 발생하면 롤백하세요.

  • Extract Method — 긴 메서드를 명확한 이름의 여러 짧은 메서드로 분할
  • Extract Class — 관련 필드 및 메서드 그룹을 별도 클래스로 추출
  • Replace Conditional with Polymorphism — switch를 클래스 계층 구조로 대체
  • Introduce Parameter Object — 매개변수 그룹을 객체로 통합
  • Replace Inheritance with Delegation — extends를 컴포지션으로 대체

IDE 도구는 많은 리팩토링 기법을 자동화합니다. Android Studio와 IntelliJ IDEA는 내장 리팩토링(Extract Method, Extract Interface, Pull Members Up, Encapsulate Fields)을 제공합니다. Xcode(버전 14부터)는 Swift에 대한 리팩토링 지원을 개선했습니다. 자동 리팩토링을 사용하면 수동 코드 복사에 비해 오류 위험이 줄어듭니다.

모바일 개발에서의 Code Smell

모바일 개발은 플랫폼 제약과 관련된 고유한 냄새를 추가합니다. Android에서는 Context 누수, 닫히지 않은 Cursor, Lifecycle의 부적절한 사용이 포함됩니다. iOS에서는 클로저를 통한 순환 참조, Auto Layout의 잘못된 처리, 거대한 ViewController가 포함됩니다. 이러한 냄새는 유지보수성을 저하시킬 뿐만 아니라 애플리케이션의 성능과 안정성에 직접적인 영향을 미칩니다.

Callback Hell은 비동기 작업을 처리하는 코드의 특징적인 냄새입니다. 중첩된 콜백은 코드를 읽기 어렵게 만들고 디버깅을 어렵게 만듭니다. 해결책: 코루틴(Kotlin), async/await(Swift 5.5+), RxJava/RxSwift 또는 Combine. Google I/O 2023에 따르면 콜백 스타일에서 코루틴으로 전환한 프로젝트는 버그 수가 30% 감소하고 새 기능 추가가 가속화되었습니다.

Platform Coupling — 비즈니스 로직의 플랫폼 컴포넌트에 대한 강한 결합. 이러한 로직을 테스트하려면 에뮬레이터를 실행해야 하므로 피드백 주기가 느려집니다. 수정: Clean Architecture는 코드를 Domain(플랫폼 종속성이 없는 순수 Kotlin/Swift) 계층과 Data/UI(플랫폼 종속성 있음) 계층으로 분리합니다. 비즈니스 로직은 에뮬레이터 없이 JVM에서 테스트됩니다.

자주 묻는 질문

Code Smell과 버그는 같은 것인가요?

아니요 — Code Smell은 오류가 아닙니다. 냄새가 있는 코드는 올바르게 작동하지만 유지보수, 변경 및 테스트가 어렵습니다. 버그는 잘못된 동작이고 냄새는 잠재적인 미래 문제에 대한 경고입니다.

Martin Fowler는 몇 가지 냄새를 식별했나요?

22가지 냄새를 “Refactoring”(2019) 제2판에서 식별했습니다. 여기에는 Long Method, Large Class, Primitive Obsession, Data Clumps, Switch Statements, Speculative Generality 등이 포함됩니다. 커뮤니티는 현대 패러다임과 플랫폼을 위한 수십 가지 새로운 냄새를 추가했습니다.

Code Smell을 찾는 가장 좋은 도구는 무엇인가요?

조합이 가장 좋은 결과를 제공합니다: 자동 분석용 Detekt(Android/Kotlin), SwiftLint(iOS), SonarQube(둘 다)와 의미적 냄새용 코드 리뷰. 단일 도구로 100% 문제를 찾을 수 없습니다 — 인간의 경험이 결정적입니다.

Code Smell을 무시해도 되나요?

, 코드가 거의 변경되지 않거나 곧 완전히 다시 작성될 경우 가능합니다. 그러나 냄새의 축적은 기술 부채로 변합니다: 각각의 새로운 변경이 점점 더 어려워지고 수정 비용이 기하급수적으로 증가합니다.

SwiftUI와 Jetpack Compose에 특화된 냄새가 있나요?

— 선언적 프레임워크는 새로운 냄새를 만들어냈습니다: 거대한 @State 블록, 반복 렌더링의 부적절한 처리, 과도한 재구성, 개별 View로 추출 부족. SwiftUI의 일반적인 냄새는 수십 개의 @State 변수가 있는 Massive View입니다.

요약

  • Code Smell — 코드의 깊은 문제에 대한 표면적 징후, 버그는 아니지만 유지보수성을 저하시킴
  • Long Method 및 Large Class — 모바일 개발에서 가장 흔한 냄새로, Extract Method와 Extract Class 필요
  • Duplicate Code — 변경할 때마다 작업이 두 배가 되는 중복 로직
  • Feature Envy 및 Switch Statements — 클래스 간 잘못된 책임 분배의 징후
  • 특정 냄새 — God Activity, Giant ViewController, Leaking Context — 모바일 플랫폼에 고유
  • 리팩토링은 테스트 없이 위험: 먼저 Characterization Tests, 그다음 커밋과 함께 작은 단계
  • 정적 분석(Detekt, SwiftLint)은 감지를 자동화하지만 코드 리뷰를 대체하지 않음

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기