바이브코딩할 때 자주 보이는 하드코딩, 어디까지 걷어낼 것인가
하드코딩은 ‘당연히 하면 안된다’고만 생각했다
바이브코딩이 유행하면서 서비스를 만드는 것은 매우 쉬워졌지만 여전히 하드코딩된 코드를 작성하는 경우가 생긴다. 하드코딩은 보통 걷어내야 할 것으로 여겨진다. 그렇다면 어떤 형태의 코드가 하드코딩에 해당하는지 그리고 '왜 지양해야 하는지'를 이번 글에 작성했다.
하드코딩이란?
개발을 하다 보면 ‘하드코딩’이라는 말을 접하기 쉽다. 보통은 ‘하드코딩되어 있는 부분을 걷어내자’라는 상황에서 쓰일 정도로, 하드코딩은 하나의 안티패턴이다.
하드코딩(hard coding)이란, 코드와 따로 떼어 놓고 작성되어야 할 값인데 코드 내에 박혀 고정되어 있는 어떠한 값을 의미한다. 하드코딩되어 있는 값은 보통 세 가지 경우로 나뉜다.
첫째, 남에게 보이면 안 된다. DB 접속 정보나 패스워드, API Key와 같은 값이다.
둘째, 내 컴퓨터에서만 유효하여 실제 서비스 운영에선 교체되어야 한다. 파일 저장 경로가 대표적인 예시다.
셋째, 운영하면서 조정하게 되는 값이다. 타임아웃이나 재시도 횟수처럼 서비스를 돌리다 보면 값을 바꾸게 되는 경우다.
위 조건과 함께 하드코딩이 문제가 되는 이유와 예시를 들 예정이다.
하드코딩이 문제가 되는 이유
하드코딩이 되어 있는 일반적인 문제는 3가지 관점을 보고 판단해야 한다. 이는 ‘하드코딩이 안 되는 이유의 중요도’ 순서로 작성하였다.
1순위 - 보안 문제
가장 중요하게 다뤄지는 문제다. 앞서 ‘남에게 보이면 안 된다’는 이유와 함께 제시한 예시들은 타인에게 공유가 되면 안 되는 것이 분명하다. 개인정보 탈취를 당하거나 과금의 문제가 생기기 때문이다.
그냥 개인적인 프로젝트에서 작업하는 걸로는 보안 문제가 약해질 수 있다. 다만 GitHub와 같은 저장소에 코드를 올릴 때 문제가 된다. 하드코딩한 코드가 그대로 올라가면 그대로 공개되는 것이기 때문에 큰 보안사고로 이어질 수 있다.(public repository에 한한 이야기이긴 하다.)
노출되면 절대 안 되는 값
python# 파이썬 코드 파일에선 다음과 같이 코드가 쓰이면 안 된다. # DB 접속 정보 conn = connect("postgres://admin:password@prod-db:5432/app") # 비밀번호 예시 SITE_PASSWORD = "qw1234" # API Key 예시 openai.api_key= "sk-proj-abcdefghijk123456789" response = openai.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "안녕하세요"}] )
값이 아니라 데이터가 들어간 경우
이외에도 데이터베이스에서만 조작이 되는 데이터 값(유저의 이름, 전화 번호, 사내 규약 등)이 코드에 그대로 들어간 경우가 있다.
python# 유저의 정보가 그대로 저장이 된 경우 # 단순 예시 정보라면 상관 없지만 # 운영에서 쓰이는 데이터가 들어가면 안 된다는 의미 user = { "name": "lizzie", "phone": "010-1234-5678", "location": "seoul" }
- 해당 값은 DB에서 직접 받아와서 사용하는 방향으로 구현해야 하며 raw query나 ORM을 사용하여 해결한다.
- 이미 commit했다면, 이미 git history로 남아서 파일만 고쳐서는 안 되기 때문에 애초에 개인정보는 하드코딩하지 않도록 유의한다.
2순위 - 환경 분리 문제
필자가 일전에 작성했던 ‘작은 프로젝트라도 로컬-운영 경계 정도는 필요하다(feat. ngrok)’ 글처럼, 개발 작업을 하는 개인 컴퓨터와 실제 코드 배포가 되어 운영하는 서버 간의 분리를 해야 한다. 이에 따라 컴퓨터 경로처럼 환경마다 달라질 것이 명확한 값들은 하드코딩하면 안 된다.
python# 개인 컴퓨터(이하 로컬)의 경로 df = pd.read_csv("/usr/lizzie/project/data/sales.csv")
만약 위의 코드를 그대로 배포 환경에 올리게 되면 어떻게 될까? 배포 환경에서 서버 계정 이름이 우연하게 lizzie가 아닌 이상, 해당 csv 파일을 읽어들이지 못하는 문제가 생긴다.
환경이 달라지면 바뀔 가능성이 있는 값들은 다음과 같다.
- 파일 경로
- DB 접속 정보
- API Key
- 개인용과 운영용(보통 회사 제공)이 다른 경우
- 컴퓨팅 자원 정보(CPU, 메모리 등)
- 컴퓨팅 자원 상한(CPU 워커 상한, 메모리 상한 등)
3순위 - 변경 비용 문제
해당 사례는 보안 문제라기보단 번거로움의 영역이다. 단 하나의 값을 변경하기 위해 새로 배포해야 할 때의 문제다.
pythonimport requests def fetch_payment_status(order_id: str): response = requests.get( f"https://api.payment.com/orders/{order_id}", timeout=5, # 코드에 박혀 있는 타임아웃 ) return response.json()
만약 코드에 적어뒀던 timeout 값을 변경하고 싶다면? 코드를 수정하여 commit하고 올려야 한다. 이렇게 되면 사소한 변경임에도 commit 이력이 불필요하게 쌓인다. 그렇기에 이 또한 하드코딩을 걷어내기 좋은 사례다.
하드코딩이 아닌 경우
하드코딩을 피해 값을 코드 밖으로 빼는 행위는 '이 값은 바뀔 수 있다'는 신호를 준다. 그래서 바뀌지 않을 값까지 밖으로 빼면, 코드를 읽는 사람에게 혼란을 준다. 밖으로 분리한 값을 확인하려고 설정 파일을 열어봐야 하는 건 덤이다.
그렇다면 빼지 않아도 되는 건 어떤 값일까? 즉, 하드코딩인지 아닌지를 가르는 기준은 무엇일까?
하드코딩인지 아닌지를 가르는 기준은 앞서 말한 세 가지다.
- 남에게 보이면 안 되는 값인가
- 상황에 따라 달라져야 하는 값인가
- 운영하면서 조정하게 될 값인가
셋 중 어디에도 해당하지 않는다면 고정 값을 코드 안에 그대로 둬도 된다. 여기에 더해, 세 가지에 걸리더라도 밖으로 빼는 것이 오히려 손해인 경우가 있다.
값 자체가 절대 바뀌지 않을 때
수학이나 물리 개념에서 이미 정립된 상수 값, 개발에서 이미 컨벤션으로 정리되어 있는 http status 코드처럼 더 이상 바뀌지 않는 경우, 이는 이미 표준으로 정해졌기 때문에 값을 그대로 써도 하드코딩이 아니다.
pythondef convert_hour_to_minute(hour: int) -> int: # 1시간이 60분이라는 것은 수학 체계에서 정의한 불변의 개념이다. # 따라서 60이라는 상수는 하드코딩이 아니다. minute = hour * 60 return minute
python# Flask API 예시 코드 @app.route("/api/check") def check(): # 200이라는 http status code는 # 이미 정의되어 널리 쓰이는 값이기 때문에 하드코딩이 아니다. return "success", 200
값 자체가 로직의 일부일 때
값 자체를 코드 내에 녹여내지 않고 환경변수로 따로 빼두는 것이 오히려 가독성을 해치는 경우다.
pythonimport os # '패스워드는 8자리 이상으로 구현'하라는 정책이 있을 때 if len(password) < int(os.environ["PASSWORD_LENGTH"]): raise Exception("Password is too short")
위의 경우는 패스워드의 길이 또한 코드를 해석하는 정보가 되는데, 이를 환경변수로 빼서 오히려 ‘어떤 조건에 Exception이 나온다는 것’인지 확인이 번거로워진다. 해당 조건이 작성된 파일을 다시 찾아야 한다는 의미다.
이땐 그냥 코드 내에 상수를 그대로 작성한다. 저서 ‘Refactoring’을 펴낸 마틴 파울러 또한 해당 저서의 ‘9.6 Replace Magic Literal’에서 언급한 사항이다. 이름을 붙였을 때 원래 값보다 정보를 더 주지 못하면 값을 그냥 두는 것을 원칙으로 정하였다.
pythonif len(password) < 8: # 정책 값이 코드에 그대로 보인다 raise Exception("Password is too short")
하드코딩 값은 어떻게 관리하는가?
그럼 코드를 매번 고칠 수도 없고 GitHub 저장소에도 올릴 수 없는 값이라면 어떻게 관리해야 할까?
환경 변수 파일로 관리하기
이때 등장하는 것이 환경 변수 파일이다. 보통은 .env 라는 이름으로 관리가 되고 있다. 이 .env 파일에 하드코딩이 될 만한 값들을 이름을 붙여 저장하고, 코드에서는 해당 이름을 불러오는 형식으로 한다.
그리고 가장 중요한 것은 이 .env 파일이 저장소에 올라가지 않게 하는 것이다. 이는 .gitignore 에 파일 이름을 추가하여 방지한다.
로컬에서의 값은 이렇게 GitHub에 올라가지 않도록 방지하며, 운영에서의 값은 환경변수 관리 시스템을 이용하면 된다. GitHub의 Actions secrets and variables나 클라우드의 설정값 관리 서비스(AWS Secrets Manager, AWS Parameter Store, GCP Secret Manager 등)를 활용하여 CD 레벨에서의 배포 자동화 단계에서 주입시키면 된다.
운영 사이트에 적용할 값들은 환경변수 관리 시스템에서도 암호화되기 때문에 더욱 안전하다.
아래의 예시들은 위에서 문제가 됐던 하드코딩의 경우를 .env 에 넣고 관리하는 방안을 제시한다.
.env 파일 작성
bash# DB 접속 정보 DB_CONNECTION_URI=postgres://admin:password@prod-db:5432/app # 비밀번호 예시 SITE_PASSWORD=qw1234 # API Key 예시 OPENAI_API_KEY=sk-proj-abcdefghijk123456789 # 타임아웃 예시 PAYMENT_API_TIMEOUT=10
python 파일 내에서 불러오기
pythonimport os import openai from dotenv import load_dotenv load_dotenv() # .env 파일의 값을 환경변수로 읽어들인다 # DB 접속 정보 conn = connect(os.environ["DB_CONNECTION_URI"]) # 비밀번호 예시 SITE_PASSWORD = os.environ["SITE_PASSWORD"] # API Key 예시 openai.api_key = os.environ["OPENAI_API_KEY"] response = openai.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "안녕하세요"}] )
pythonimport os import requests # .env에서 받는 환경변수 값은 항상 str 타입이므로 int로 type casting PAYMENT_API_TIMEOUT = int(os.environ["PAYMENT_API_TIMEOUT"]) def fetch_payment_status(order_id: str): response = requests.get( f"https://api.payment.com/orders/{order_id}", timeout=PAYMENT_API_TIMEOUT, ) return response.json()
코드로 구현하기
코드 구현으로 해결할 수 있는 경우도 있다. 파일 경로 지정하는 경우가 그렇다.
다음의 파이썬 코드는 현재 위치의 폴더를 가져와서 파일을 읽는 코드 예시다.
pythonimport os import pandas as pd df = pd.read_csv(os.path.join("data", "sales.csv"))
최종 정리
| 기준 | 예시 값 | 관리 방법 |
|---|---|---|
| 남에게 보이면 안 되는 값 | API Key, 비밀번호, DB 접속 정보 | .env, Secrets Manager |
| 상황에 따라 달라지는 값 | 파일 경로, 서버 주소, 포트 | .env, 코드로 처리 |
| 운영하면서 조정하는 값 | 타임아웃, 재시도 횟수, 로그 레벨 | .env, 설정값 관리 서비스 |
| (해당 없음) | 60, HTTP 200, 정책 상수 | 코드에 그대로 |
바이브코딩 할 때 하드코딩을 걷어내고 싶다면?
하드코딩을 걷어내는 프롬프트를 활용한다. 해당 프롬프트는 Claude Code에서 대화할 때 활용하거나, CLAUDE.md 등의 에이전트 가이드라인에 추가하는 것을 권장한다.
plain이 코드에서 하드코딩된 값을 찾아 환경변수로 분리해줘. 기준은 세 가지야. 1. 남에게 보이면 안 되는 값 (API 키, 비밀번호, 토큰, 접속 정보) 2. 실행 환경에 따라 달라져야 하는 값 (파일 경로, 서버 주소, 포트) 3. 운영하면서 조정하게 되는 값 (타임아웃, 재시도 횟수, 로그 레벨) 위 세 가지에 해당하지 않는 값은 그대로 둬. 모델명, 상태 코드, 파일 이름처럼 바뀌지 않는 값까지 빼지 마. 작업 후에 아래를 같이 알려줘. - 어떤 값을 왜 뺐는지 - .env 파일에 들어갈 내용 (실제 키 값은 넣지 말고 자리만 만들어) - .gitignore에 .env가 들어 있는지
References
- Refactoring, 마틴 파울러 ㅡ 9.6 Replace Magic Literal