코드 구조 설계하기
작은 프로그램도 “구조”가 반이다: 6-2 코드 구조 설계하기
6-1에서 “어떤 프로그램을 만들지”를 정리했지?
이제부터는 내가 제일 중요하게 보는 단계가 나와. 바로 코드 구조 설계!
솔직히 말하면, 처음부터 완벽하게 짜는 건 거의 불가능이야. 나도 예전에 “일단 되게 해보자!” 모드로 막 작성했다가, 나중에 기능이 늘어나면서 파일이 하나도 안 읽히는 괴물이 되더라. 그때 깨달았지.
기능이 늘어날수록 구조는 선택이 아니라 생존이더라고.
1) 목표: “유지보수하기 쉬운” 구조 만들기
여기서 말하는 유지보수는 거창한 게 아니야.
- 기능 추가할 때 코드가 덜 깨지게
- 수정할 때 어디를 고쳐야 할지 빨리 찾게
- 나(미래의 나 포함)가 다시 봐도 이해되게
음… 사실은 미래의 나를 위해 지금 내가 도와주는 거지.
미래의 나는 아마 바쁘고, 나만큼 친절하지 않을 거니까.
2) 전체를 “덩어리”로 쪼개기 (기능 단위 분리)
내가 보통 이렇게 쪼개.
덩어리 1: 사용자 인터랙션(입력/출력)
- 콘솔에서 입력 받기
- 메시지 출력하기
- 잘못 입력했을 때 안내하기
덩어리 2: 핵심 로직(계산/판단/처리)
- 입력값을 가지고 실제로 뭔가 처리하기
- 조건에 따라 결과 만들기
덩어리 3: 데이터(저장/관리)
- 리스트/딕셔너리로 상태 담기
- 파일이 들어가면 경로/포맷 정리하기
3) “흐름도” 먼저 그리면 덜 헤맨다
코드로 바로 들어가면 막히는 순간이 꼭 와.
그때 필요한 게 “아, 지금 흐름이 이렇게였지!” 같은 확신이야.
그래서 나는 설계할 때 보통 흐름을 먼저 잡아.
예시(텍스트 기반 메뉴 프로그램이라고 가정)
- 메뉴 출력
- 선택 입력
- 선택에 따라 처리 로직 호출
- 결과 출력
- 계속 반복/종료
4) 함수 설계: “한 함수 = 한 가지 역할” 규칙
여기서 함수 설계는 그냥 감으로 하면 망해… 라기보단,
경험상 역할이 섞이기 시작하면 나중에 진짜 힘들어져.
그래서 내가 자주 쓰는 규칙은 이거야.
print_menu(): 메뉴를 보여주기만get_choice(): 입력을 받아서 숫자/문자만 처리하기run_action(choice, data): 선택에 따라 실제 작업 수행handle_result(result): 결과 메시지를 예쁘게(혹은 명확하게) 출력load_data()/save_data(): 파일 있으면 입출력만 담당
아! 그리고 한 가지 더.
함수 이름은 “동사”로 짓는 게 편해. 예를 들면:
calculate_total()find_item()add_record()
솔직히 말하면, 내가 예전에 함수 이름을 막 지었다가(예: doStuff1, doStuff2)
나중에 “이게 뭐였지?” 싶어서 스스로를 욕했어… 좀 웃기지?
5) 데이터 구조 설계: “뭘 저장할지”부터 정하자
기능 설계를 하다 보면 자연스럽게 데이터가 필요해져.
예를 들어 어떤 프로그램이 “기록”을 다룬다면 보통 이런 질문이 생겨:
- 항목은 어떤 형태로 저장할까?
- 리스트? 딕셔너리?
- 각 항목에 필요한 값은 뭐지?
- 이름, 점수, 날짜 같은 것들?
- 빠르게 찾아야 하나?
- 이름으로 바로 찾으면 딕셔너리가 편할 수 있음
나름 팁을 주자면:
- 순서가 중요하면 리스트
- 키로 바로 찾으면 딕셔너리
이건 이전 시리즈에서 리스트/딕셔너리 다룬 내용(3-1, 3-2) 그대로 가져오면 돼.
6) 예외(오류) 처리 위치도 미리 정하기
“오류 수정과 개선하기”(6-4)에서 제대로 다룰 거지만,
여긴 어디서 오류를 잡을지만 간단히 설계하자.
예를 들면:
- 입력 단계에서 숫자가 아니면 다시 입력하게 하기
- 메뉴 선택 범위를 벗어나면 안내하기
- 파일 로드 실패하면 기본값으로 시작하기
이걸 어디에 넣냐에 따라 구조가 완전히 달라져.
내가 자주 하는 패턴
- 입력 함수가 “입력 오류”를 책임지고
- 로직 함수는 “데이터가 정상일 때의 동작”에 집중하게 하기
이러면 나중에 테스트/수정이 훨씬 쉬워져.
7) 프로젝트 뼈대(템플릿)부터 만들기
자, 이제 진짜 “시작하는 느낌”을 주는 단계야.
나는 구현 전에 대충 이런 형태로 뼈대를 만들어.
- 전체 실행 흐름 함수(예:
main()) - 메뉴/입력 관련 함수들
- 로직 함수들
- 데이터 로드/세이브 함수들
예시 뼈대(개념용):
def main():
data = load_data()
while True:
choice = show_menu_and_get_choice()
if choice == "0":
save_data(data)
break
result = run_action(choice, data)
print_result(result)
이 정도만 있어도, 다음 단계(6-3)에서 “기능별 구현”이 훨씬 쉬워져.
왜냐면 이미 “어떤 함수에 뭘 넣어야 하는지”가 정해졌거든.
8) 여기까지 하면 뭐가 좋아지냐?
솔직히 말하면, 지금 설계한다고 코드가 갑자기 완벽해지진 않아.
근데 실수할 확률이 줄어들어.
- 나중에 기능이 늘어도 구조가 크게 안 무너지고
- 어디를 수정해야 할지 감이 생기고
- 디버깅(오류 찾기)이 덜 고통스럽고
“아… 왜 이렇게 엉켜있지?” 같은 상황을 미리 막는 느낌이라고 보면 돼.
정리: 6-2에서 한 것
- 프로그램을 덩어리(입력/로직/데이터)로 나눴고
- 함수는 “역할 하나”로 설계하고
- 데이터는 저장/조회 방식 기준으로 결정하고
- 오류 처리는 책임 위치를 정했어
- 마지막으로 구현 전에 뼈대를 만들었지
다음 글(6-3)에서는 드디어 본격적으로 기능별 구현 단계로 들어갈 거야.
아마 “이 함수에 뭘 넣는지”가 코드로 확실히 보이면서, 프로그램이 실제로 살아나는 느낌이 올 거야.
그럼 6-3에서 만나자! 😄





