작은 프로젝트 확장하기

in #krsuccess22 hours ago

완성된 프로그램에 기능을 ‘슬쩍’ 더해보기: 작은 프로젝트 확장하기

MemoryCatcher

음… 사실 나도 예전에 “완성했다!” 하고 끝냈다가, 며칠 뒤에 “어? 이거 하나만 더 넣으면 더 재밌을 텐데?” 이런 생각이 계속 들더라.
그래서 이번 7-3에서는 이미 만들었던 프로그램을 유지한 채로 기능을 확장하는 연습을 해볼 거야. 나름 핵심은 이거 하나:

  • 기존 구조는 안 건드리고(가능하면)
  • “새 기능”을 추가하되
  • 코드가 점점 안 망가지는 방식으로 업데이트하기

솔직히 말하면… 기능 추가하다가 가끔은 내가 만든 내가 봐도 “왜 이렇게 됐지?” 싶은 순간이 오거든. 그런 실패담도 같이 처리해보자 😅


오늘 목표: “확장 가능한 구조” 감 잡기

지난 6장에서 만든 작은 프로그램은 아마 이런 흐름이었을 거야.

  • 메뉴를 보여준다
  • 사용자가 선택한다
  • 조건에 따라 기능이 실행된다

여기서 이번엔 딱 하나를 더해볼 거야.

  • 예: 기록 기능 추가, 정렬 기능 추가, 저장 기능 추가, 옵션 메뉴 추가 같은 것들

mwitt1337

“기능 추가”는 쉬워 보여도, 코드 입장에선 꽤 큰 이벤트거든.
그래서 기존 코드에 무작정 붙이기보단, 어디에 무엇을 끼워 넣을지 감을 잡는 게 포인트야.


준비물: 우리가 지키고 싶은 약속 3가지

확장을 시작하기 전에 나름 규칙을 정해두면 편해.

  1. 입력(사용자 선택)과 로직(실제 처리)을 분리
  2. 기능 단위로 생각하기
    • “1번은 계산”, “2번은 저장” 이런 식으로
  3. 자료 구조(리스트/딕셔너리)는 재사용하기
    • 새 기능 만든다고 자료형을 매번 갈아엎으면 코드가 쉽게 터져…

여기서 “솔직히 아직 확신은 없는데”라고 말하고 싶다.
나도 확장하다가 한 번씩 규칙을 어긴 적 있어. 그때의 결말은… 갑자기 오류가 폭발하고, 디버깅을 밤새 하게 되더라. (아… 그때 나 뭐 했니)


확장 아이디어 1: “이전 결과 저장” 기능 붙이기

지난 글들에서 파일 입출력까지 했잖아? (4-4~4-5 라인)
이번에는 그걸 살짝 가져와서, 프로그램이 실행할 때마다 결과를 저장해보자.

예를 들면 이런 느낌:

  • 사용자가 계산하거나 입력한 결과를
  • 리스트에 담아두고
  • 파일에 저장한다

확장 전(상상)

  • 콘솔에서 결과 출력만 한다

확장 후(추가)

  • 출력 + 기록(저장)

이때 중요한 건 기존 “계산/처리 함수”를 건드리지 않는 것이야.

def calculate(a, b):
    return a + b

기존 함수는 냅두고, “기록”용 함수를 추가하는 거지.

def save_result(result, filename="history.txt"):
    with open(filename, "a", encoding="utf-8") as f:
        f.write(str(result) + "\n")

그리고 메뉴 로직에서 “기존 결과 얻기” 다음에 저장만 붙여.

result = calculate(a, b)
print("결과:", result)
save_result(result)

어? 이렇게 하면 기존 구조는 거의 그대로인데 기능이 늘어나지?
이게 바로 “확장”의 맛이야. 나름 현실적으로도 관리가 쉬워.


확장 아이디어 2: “메뉴에 옵션” 추가해서 기능 선택권 주기

프로그램이 커지면 보통 이런 상황이 와.

  • 기본 메뉴(1~5)는 그대로 두고
  • “저장할지 말지” 같은 옵션이 필요해져

여기서 팁 하나.
나는 보통 메뉴를 두 레벨로 나누는 방식을 좋아해.

  1. 메인 메뉴: 어떤 작업을 할지
  2. 옵션: 저장/정렬/필터 같은 부가 기능

Pexels

예시로 이런 식:

    1. 계산하기
    1. 기록 보기
    1. 기록 지우기
    1. 종료

그리고 계산하기에서 “저장할까요?”를 물어보는 식으로 확장할 수 있어.

choice = input("저장할까요? (y/n): ").strip().lower()
if choice == "y":
    save_result(result)

음… 사실 이 방법은 간단하지만, 확장이 계속되면 “y/n 질문이 여기저기 박히는” 문제가 생겨.
그래서 다음 단계로 갈수록(7-4에서) 구조를 더 깔끔하게 잡는 방법도 나올 거야.

일단 이번 글에서는 “확장으로 인해 코드가 망가지지 않게” 감을 잡는 게 목적!


확장 아이디어 3: “정렬/필터” 기능 추가 (리스트/딕셔너리 재활용)

프로그램에 “기록” 같은 데이터가 생기면 자연스럽게 이런 게 하고 싶어져.

  • 가장 큰 값만 보고 싶다
  • 특정 조건에 해당하는 것만 보고 싶다
  • 정렬해서 보기

여기서 핵심은 데이터를 어떤 형태로 저장했는지야.

예를 들어 기록이 리스트로 쌓였으면:

history = [10, 3, 25, 7]

정렬은:

sorted_history = sorted(history)

또는 내림차순:

sorted_history = sorted(history, reverse=True)

딕셔너리로 저장했으면(예: { "name": "kim", "score": 80 } 형태) 필터가 더 쉬워져.

students = [
    {"name": "kim", "score": 80},
    {"name": "lee", "score": 92},
]

high = [s for s in students if s["score"] >= 90]

음… 이런 확장은 “알고리즘 느낌”도 나니까, 4장 때 배운 흐름이 다시 살아나는 기분이 들어.
나도 그런 포인트 느끼면 좀 뿌듯하더라 😄


확장할 때 제일 흔한 실수 2가지 (나도 함)

실수 1) 기존 코드에 무작정 if를 계속 추가하기

처음엔 “간단히 한 줄만” 넣었는데
어느새 if가 20개가 되고, 조건이 꼬여서 유지보수 지옥이 열려…!

해결은 간단해:

  • 기능별로 함수로 빼기
  • 메뉴 선택은 “호출만” 하게 만들기

실수 2) 파일 저장/불러오기 로직이 여기저기 흩어지기

예를 들어 save/load 코드가 모든 기능 함수에 들어가면,
나중에 파일 포맷 바꿀 때 진짜 곤란해져.

그래서 나는 보통 이런 식으로 정리해:

  • load_history() : 읽기만 담당
  • add_history(item) : 데이터에 추가만 담당
  • save_history(history) : 저장만 담당

이렇게 역할을 나누면, 변경할 때 충격이 확 줄어.


미니 실습: “메뉴 + 저장 옵션”으로 확장해보기

자, 이제 진짜로 해보자.
너가 6장에서 만든 프로그램이 “계산기/메뉴 기반/입력 처리” 중 하나였을 가능성이 높으니까, 그 흐름에 맞춰 확장하면 돼.

실습 체크리스트

  • [ ] 기존 기능은 그대로 두기
  • [ ] 기능 수행 결과를 기록(리스트에 추가)
  • [ ] “저장할지 여부” 옵션을 하나 추가
  • [ ] 저장 버튼/메뉴(선택 사항) 또는 자동 저장 중 하나 구현
  • [ ] 기록 보기 메뉴(선택 기능) 추가

refactor

그리고 나서… 실행해봐.
실행 중에 오류 뜨면? 그건 완전 정상 루트야.
오류 메시지는 읽는 법(1-5) 배웠잖아.
나도 한 번 저장 파일 경로 틀려서 “왜 저장이 안 되지?” 하면서 20분 날린 적 있어… 하하


다음 글 예고: 협업을 위한 코드 작성법

자, 7-3까지 하면 “기능을 늘리는 감”은 생길 거야.
근데 여기서 멈추면, 언젠가는 또 이런 문제가 와.

  • 기능이 더 늘어나서 코드가 복잡해짐
  • 다른 사람이 봤을 때 “뭐가 어디서 돌아가지?”가 됨

그래서 다음 7-4에서는 협업을 위한 코드 작성법을 다룰 거야.

  • 함수 이름/역할 정리
  • 파일 구조(폴더/모듈) 잡는 법
  • 읽기 쉬운 흐름 만들기
  • “나 말고 다른 사람이” 이어서 개발해도 덜 망가지는 방식

솔직히 말하면, 협업은 거창해 보이지만…
사실은 “미래의 나를 살리는 것”이더라. 나도 자주 느꼈어 😅

다음 글에서 같이 더 깔끔하게 업그레이드 가보자!