“못 박기”와 “하드코드”는 설정이나 구성으로 값을 분리하지 않고 프로그램 코드에 직접 값을 고정하는 것을 의미하는 은어입니다. 하드코드는 개발에서 가장 잘 알려진 안티패턴 중 하나로, 코드의 유연성과 재사용성을 떨어뜨립니다. Refactoring Guru에 따르면, 하드코드는 테스트, 유지보수 및 다양한 환경에 대한 애플리케이션 적응을 어렵게 만듭니다. 하드코드 대신 의식적으로 상수를 사용하는 것은 성숙한 아키텍처의 신호입니다.
핵심 사항
하드코드하기 (못 박기) — 변경하려면 소스 코드를 편집하고 애플리케이션을 다시 컴파일해야 하도록 프로그램 코드에 특정 값을 내장하는 것입니다. “못 박기”라는 비유는 본질을 정확히 반영합니다. 값이 영구적으로 고정되어 코드에서 떼어내는 데 노력이 필요합니다.
하드코드의 예 — 함수 본문에 문자열로 직접 작성된 서버 URL입니다. 서버가 다른 주소로 이전하는 경우 개발자는 코드에서 문자열을 찾아 변경하고 애플리케이션을 다시 빌드하여 릴리스해야 합니다. 올바른 아키텍처를 가진 애플리케이션에서는 이러한 URL이 구성 파일, 환경 변수 또는 구성 서비스에 분리되어 있을 것입니다.
“못 박기”라는 용어는 더 감정적인 뉘앙스를 띠고 있습니다. 값이 영구적으로 삽입되어 빠른 교체가 불가능함을 강조합니다. 러시아어권 환경에서는 두 표현 모두 부정적인 의미를 가진 완전한 동의어로 사용됩니다. 때로는 하드코드를 아이러니하게 “상수에서 별도의 상수로 추출한 상수”라고 부르기도 합니다.
하드코드는 코드의 유지보수성, 테스트 용이성 및 확장성 원칙을 위반하기 때문에 안티패턴입니다. 값이 “못 박힌” 코드에서는 환경, 디자인 또는 로직의 변경이 있을 때마다 소스에서 수동 검색 및 교체가 필요합니다. 이는 오류 위험을 증가시키고 개발을 지연시킵니다.
일반적인 모바일 애플리케이션을 예로 들어 하드코드의 구체적인 결과를 살펴보겠습니다. 모든 버튼의 여백이 리소스가 아닌 코드의 숫자로 지정된 경우 — 디자인 변경 시 모든 항목을 찾아 교체해야 합니다. 엔드포인트 URL이 고정된 경우 — 환경(dev, stage, prod) 간 전환이 재빌드 없이는 불가능합니다.
| 결과 | 설명 | 심각도 수준 |
|---|---|---|
| 유지보수 복잡성 | 변경 시 전체 코드 검색 필요 | 높음 |
| 복사 시 오류 | 모든 항목을 찾아 교체하지 못할 수 있음 | 높음 |
| 테스트 불가능 | 테스트 데이터를 대체할 수 없음 | 중간 |
| 현지화 문제 | 코드 내 텍스트가 번역되지 않음 | 중간 |
| 코드 리뷰 복잡성 | 검토자가 모든 컨텍스트를 기억해야 함 | 낮음 |
매직 넘버와 고정 문자열을 사용하는 함수는 하드코드의 전형적인 예입니다. 한 달 후에는 작성자조차 18, 0.07, 2.5가 무엇을 의미하는지 기억하지 못합니다. 1년 후에는 — 팀의 누구도 로직이 깨질까 두려워 이 숫자들을 변경하지 못합니다. 값을 명명된 상수로 추출하면 코드가 자기 문서화됩니다.
// 나쁜 예: 매직 넘버와 문자열
fun calculatePrice(base: Double): Double {
val tax = base * 0.07
val tip = base * 0.15
val discount = if (base > 100) 10 else 0
return base + tax + tip - discount
}
하드코드된 데이터베이스 URL은 로컬 인메모리 데이터베이스에서 테스트를 실행할 수 없게 만듭니다. 개발자는 테스트 전에 전체 서버를 시작하거나 코드를 편집해야 합니다. 코드에서 구성을 분리하면 문제가 해결됩니다. 테스트는 테스트 매개변수를, 프로덕션은 실제 매개변수를 사용하며 코드는 변경되지 않습니다.
하드코드는 안티패턴이지만, 고정 값이 허용될 뿐만 아니라 선호되는 정당한 예외가 존재합니다. 경계는 변경 가능성의 축을 따라 결정됩니다. 값이 애플리케이션 수명 주기 내에서 전혀 또는 거의 변경되지 않는 경우 하드코드해도 괜찮습니다. 변경될 가능성이 있는 경우 — 구성으로 분리하세요.
수학 및 물리 상수 — 원주율, 중력 가속도, 1초당 밀리초 수 — 는 하드코드에 안전합니다. 이들은 자연이나 표준에 의해 정의되며 변경되지 않습니다. 명세에 의해 정의된 상수 배열 크기도 고정할 수 있지만, 숫자의 출처에 대한 주석을 함께 작성해야 합니다.
1초당 밀리초 수는 시간 표준에 의해 정의된 안정적인 상수입니다. 절대 변경되지 않으므로 구성에 분리할 의미가 없습니다. 그러나 이러한 상수라도 코드에 “매직 넘버”가 없도록 의미 있는 이름으로 선언하는 것이 좋습니다. 1000 대신 MILLISECONDS_IN_SECOND라고 쓰세요.
// 정당화되는 하드코드: 안정적인 상수
private const val MILLIS_IN_SECOND = 1000
private const val LOGIN_TIMEOUT_SECONDS = 30
fun formatDuration(ms: Long): String {
val seconds = ms / MILLIS_IN_SECOND
return "${seconds} sec."
}
하드코드를 피하는 여러 검증된 방법이 있으며, 각각 값 유형에 적합합니다. 대안 선택은 값이 변경되는 빈도와 변경 주체(개발자, 데브옵스, 최종 사용자)에 따라 달라집니다.
서버 URL, API 키, 기능 플래그에는 JSON, YAML 또는 TOML 형식의 구성 파일을 사용하세요. Android에서는 build.gradle의 buildConfigField나 res/values/config.xml을 사용합니다. iOS에서는 — Info.plist나 xcconfig를 사용합니다. 구성 파일은 애플리케이션과 함께 빌드되지만 빌드 스킴에 따라 다를 수 있습니다.
비밀 정보(토큰, 비밀번호) 및 환경 매개변수에는 환경 변수를 사용하세요. 이들은 저장소에 들어가지 않으며 dev, stage, prod 서버에서 다를 수 있습니다. 모바일 개발에서는 환경 변수가 종종 Xcode 빌드 스킴이나 Gradle의 빌드 플레이버를 통해 에뮬레이션됩니다.
문자열, 색상, 크기, 이미지는 리소스 파일에 분리해야 합니다. Android의 strings.xml, iOS의 Localizable.strings, Flutter의 ARB 파일. 이렇게 하면 현지화, 다양한 화면 적응 및 다크 테마가 쉬워집니다. 리소스의 문자열을 변경해도 코드를 다시 작성할 필요가 없습니다.
<!-- Android: res/values/strings.xml -->
<resources>
<string name="app_name">MyApp</string>
<string name="api_base_url">https://api.example.com</string>
</resources>
서비스 및 제공자에는 Android에서 Dagger, Hilt 또는 Koin, iOS에서 Swinject를 통한 의존성 주입을 사용하세요. DI 프레임워크는 — 테스트, 다양한 환경, 다양한 사용자를 위해 — 구현을 실시간으로 교체할 수 있게 해줍니다. 이는 값의 “고정”이 외부 주입으로 대체되는 가장 높은 수준의 추상화입니다.
하드코드 리팩토링은 고정된 값을 구성이나 리소스로 추출하는 과정입니다. 체계적으로 수행한다면 가장 안전한 리팩토링 작업 중 하나입니다. 아래 설명된 순서는 모든 언어와 플랫폼에 적합합니다.
IDE(Search in Project)나 스크립트를 통해 검색할 수 있습니다. 문자열, URL, 숫자 리터럴, 크기, 타임아웃을 찾으세요. 특히 — 반복되는 값에 주의하세요. 같은 숫자가 다섯 곳에 나타나면 상수로 추출할 후보입니다. grep이나 IDEA / Xcode의 내장 검색을 사용하세요.
찾은 각 값에 대해 의미 있는 이름의 상수를 만드세요. 모듈이나 클래스별로 상수를 그룹화하세요. 이름은 값의 의미를 설명해야 하며 사용 방법이 아닙니다. API_TIMEOUT이지 TIMEOUT_30이 아닙니다. 교체 후 코드에 설명 없는 숫자가 남아 있어서는 안 됩니다.
// 이전: 매직 넘버 0.4
let cardHeight = screenHeight * 0.4
// 이후: 명명된 상수
private let cardHeightRatio: CGFloat = 0.4
let cardHeight = screenHeight * cardHeightRatio
값이 빌드나 환경 간에 달라질 수 있는 경우 — 구성 파일이나 애플리케이션 리소스로 추출하세요. 문자열에는 현지화 파일을 사용하세요. URL에는 — build config나 xcconfig를 사용하세요. 크기에는 — 리소스 파일(Android의 dimens.xml)을 사용하세요. 추출 후 애플리케이션이 올바르게 빌드되고 작동하는지 확인하세요.
리팩토링 후 구성이 올바르게 로드되는지와 값이 예상과 일치하는지 확인하는 테스트를 작성하세요. 향후 누군가 구성을 변경하면 테스트가 불일치를 지적할 것입니다. 구성 테스트는 — 회귀를 방지하는 빠르고 신뢰할 수 있는 방법입니다.
구성으로 추출한 후 이전 값을 사용하던 모든 위치가 단일 소스를 참조하는지 확인하세요. 주석 처리된 코드와 더 이상 사용되지 않는 이전 상수를 제거하세요. 어떤 값이 어디로 추출되었는지 설명하는 커밋 메시지로 리팩토링을 완료하세요.
자주 묻는 질문
하드코드하기 — 구성이나 리소스로 분리하지 않고 소스 코드에 직접 값을 작성하는 것입니다. 이는 코드의 유연성을 떨어뜨리고 유지보수를 어렵게 만듭니다.
하드코드는 애플리케이션 동작 변경을 어렵게 하고, 테스트를 방해하며, 중복을 만들고, 복사 시 오류 위험을 높입니다. 하드코드된 값을 변경하려면 애플리케이션을 다시 빌드하고 재배포해야 합니다.
허용되는 경우는 수학 상수, 애플리케이션 수명 주기 내에 변경되지 않는 안정적인 값, 임시 프로토타입입니다. 프로덕션에서는 상수조차 명명된 변수로 추출해야 합니다.
검색을 통해 모든 매직 넘버를 찾아 명명된 상수로 교체하거나 구성 파일로 추출하세요. 구성 로딩을 확인하는 테스트를 작성하고, 중복을 제거한 후 변경 내용을 설명하는 커밋 메시지로 커밋하세요.
상수 — 코드 내 명명된 값으로 한 곳에서 변경 가능합니다. 하드코드 — 코드에 흩어져 있는 이름 없는 값입니다. 좋은 관행: 항상 의미 있는 이름을 가진 명명된 상수를 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.