
들어가며
로보 77 구현 도전을 마친 뒤, 밀려 있던 일들을 정리하며 잠시 숨을 고르는 시간을 가졌다.
일주일 간 충분히 쉬고 나니 다시 달릴 준비가 된 듯하다!
2주 전 나는 작년 편의점 미션에서 경험했던 한계를 다시 마주하고 극복해보고 싶어서 복잡한 요구사항을 가진 로보 77을 선택했고,
나만의 객체지향 기준이 복잡한 요구사항 속에서도 얼마나 통하는지 검증하며 기준을 발전시키고 싶었다.
(보다 자세한 도전 계기는 아래 링크 클릭! 바람니다)
https://jiihyunn.tistory.com/33
로보 77 구현기 0일 차 - 왜 이 도전을 하는가?
들어가며화, 수, 목 3일 간의 고민을 마치고, 마침내 도전 주제를 선정하였다👏🏻👏🏻👏🏻👏🏻로보 77이라는 최종 주제를 선택하기까지 여러 주제를 떠올리며 우여곡절이 많았는데,내게
jiihyunn.tistory.com
그래서 이번 글에서는 처음 세운 목표 대비 어떤 결과를 얻었는지, 그리고 앞으로의 나에게 어떤 기반이 되었는지 등
2주 간의 도전이 어떤 의미였는지 차근차근 되짚어보며 기록하고자 한다.
목표 대비 결과
▪️ 외적 목표
도전에 앞서 세운 외적인 목표는 콘솔 기반 로보77을 만들고, 나아가 이를 디스코드 봇으로 확장하는 것이었다.
결과적으로 도전에 앞서 세운 외적인 목표는 모두 달성했다.
콘솔 기반 로보 77 게임을 구현했고,
디스코드 봇으로도 확장하여 봇과 플레이어가 대결할 수 있는 형태까지 만들었다.
구현하면서 겪은 고민 및 트러블 슈팅 등 보다 자세한 내용은
구현기n일차 글마다 확인할 수 있기에 따로 적지 않겠다!
아래는 실제 작동 모습이다.


https://github.com/Jiihyun/robo77
GitHub - Jiihyun/robo77: 카드 없이 즐기는 로보77! 디스코드만 있으면 친구들과 바로 플레이할 수 있어
카드 없이 즐기는 로보77! 디스코드만 있으면 친구들과 바로 플레이할 수 있어요. Play the board game Robo77 directly on Discord — no cards, no setup. Just join and play anytime, anywhere. - Jiihyun/robo77
github.com
▪️ 내적 목표
내적인 목표는 나의 객체지향 기준이 요구사항 변화 속에서도 유효한지 검증하고,
한계를 확인하며 더 나은 기준으로 발전시키는 것이었다.
⬇️ 1-3주차동안 미션을 수행하면서 쌓아 온 나의 기준은 다음과 같다. (더보기 클릭)
### 1. 책임 분리
- 비즈니스 로직과 UI 로직을 분리한다.
- UI는 사용자와의 상호작용만 담당한다.
- 비지니스 규칙 및 검증은 도메인 객체 혹은 서비스 레이어에서 처리한다.
### 2. 단일 책임 원칙
- 하나의 메서드는 오직 하나의 이유로 변경되어야 한다.
- 메서드 길이는 가능하면 10줄 내로 유지하고, 역할 단위로 잘게 분리한다.
- "이 객체가 반드시 해야만 하는 일인가?"를 기준으로 책임을 판단한다.
### 3. 도메인 중심 설계
- 도메인 판단 기준은 다음과 같다.
- 이 로직이 시스템의 핵심 규칙(비즈니스 규칙) 을 표현하고 있는가?
- 해당 로직이 바뀌기 위해서는 기획자 등 다른 분야와 조율이 필요한가?
- 변수명, 메서드명, 객체명은 도메인 용어를 기준으로 작성한다.
- 도메인 규칙은 도메인 객체에 최대한 응집시키고 서비스 객체로 과도하게 밀어내지 않는다.
- 데이터 구조 대신 행동을 가진 객체를 우선적으로 설계한다.
- 매직넘버 대신 상수(static final)를 정의하고 의미 있는 이름을 부여한다.
### 4. 객체 간 협력
- 객체는 다른 객체의 내부를 알지 않고, 메시지를 통해 협력한다.
- getter 지양 하여 객체가 스스로 할 수 있는 일을 다른 객체에게 맡기지 않는다.
### 5. 불변성 선호
- 객체는 가급적 불변으로 설계한다.
- 상태 변경이 필요한 경우, 변경의 책임을 명확히 하고 부작용을 최소화한다.
- 생성자를 통해 완전한 상태를 보장하고, setter 사용을 지양한다.
### 6. 테스트 가능한 구조
- 테스트하기 어려운 의존성(랜덤, 시간, 외부 API, I/O 등)은 외부에서 주입(DI) 하거나 별도 클래스로 분리하여 테스트 환경에서 교체 가능하게 만든다.
- private 메서드는 public 메서드를 통해 간접 테스트한다.
- private 메서드가 단순 분리가 아니라 독립적인 책임을 가진 중요한 로직이라면, 단위 테스트가 가능한 형태로 클래스(객체) 분리를 고려한다.
### 7. enum/static 사용 기준
- enum은 단순한 상수 묶음이 아니라 서로 연관된 상태를 하나의 타입으로 정의할 때 사용한다.
- 상황에 따라 달라질 수 있는 행위나 표현 로직이 필요하다면 클래스가 더 적합하다.
- static 메서드는 도메인 규칙을 담지 않고, 여러 영역에서 재사용되는 순수 기능(utility)일 때만 사용한다.
- 인스턴스 변수가 없어 상태를 지니지 않아야 한다.
- 두 개 이상의 도메인에서 공통적으로 사용되어야 한다.
### 8. 코드 가독성 및 명명 규칙
- 이름은 역할을 명확히 드러내도록 구체적이고 일관적으로 작성한다.
- 약어(abbreviation) 사용을 지양한다.
- boolean 변수/메서드는 가급적 긍정문으로 작성한다. (isValid, hasPermission 등)
- 메서드 이름은 동사+목적어 형태를 우선적으로 사용한다.
### 9. 근거 기반의 설계 원칙
- 모든 설계 선택에는 근거가 있어야 한다.
- “왜 이렇게 설계했는가?”에 답할 수 있도록 한다.
- 불필요한 추상화, 과도한 패턴 적용, 성급한 최적화는 피한다.
### 10. 코드 스타일
- 클래스는 상수, 멤버 변수, 생성자, 메서드 순으로 작성한다.
- `if-else`, `switch-case`, 삼항 연산문을 사용을 지양한다.
이 기준을 가지고 콘솔 기반 프로그램을 구현하였고,
이후 콘솔 기반 프로그램에 디스코드 봇을 추가하는 과정에서 크게 2가지의 요구사항이 추가되었다.
1. 입출력 방식 변경 (JDA 이벤트 리스너 기반으로 메세지 처리)
2. 플레이어 닉네임 규칙 변경 (디스코드 닉네임 규칙 반영)
지금까지 개발하면서 작성되어 있던 코드를 기반으로 새로운 기능을 추가하거나 리팩토링 등을 해본 경험이 거의 없었다.
그런데 이번 과정을 통해 1주 차에 작성한 코드가 얼마나 책임이 명확히 분리되어 있고, 확장하기 쉬운 형태이며, 재사용성이 높은 지에 대해 돌아볼 수 있었다.
1주 차에 작성했던 코드는 1번 요구사항을 반영하기 위해 수정해야 할 부분이 많았지만..(디스코드 봇 추가 파일 제외 변경 파일 15개),
2번 요구사항에 대해서는 코드 단 2줄만 수정하면 되었다.
(아래 pr을 통해 구체적인 코드 변경 범위를 파악할 수 있다.)
https://github.com/Jiihyun/robo77/pull/1
feat: 디스코드 봇 도입 by Jiihyun · Pull Request #1 · Jiihyun/robo77
💡 디스코드 봇 도입을 통해 확인한 구조적 보완점과 기존 설계의 장점 디스코드 봇 기능을 추가하면서, 기존 콘솔 기반 구조가 새로운 요구사항을 완전히 수용하기엔 일부 객체지향적 분리가
github.com
요구사항 반영을 위해 코드를 수정하면서 깨달은 점이 3가지 존재한다.
1. mvc 패턴은 만능이 아니다
2. 변경에 대비하기 위해 추상화를 미리 도입하지 말자
3. 컨트롤러에서는 비지니스 로직 없이 흐름만 담당하게 하자
1. mvc 패턴은 만능이 아니다
개발을 시작할 때, 나는 자연스럽게 지금까지 프리코스에서 익숙하게 써왔던 MVC 패턴을 그대로 적용했다.
그동안 진행해온 프로젝트들의 기본 구조도 Controller–Service–Repository–Domain이었고,
프리코스 미션에 적용한 구조는 그중 Service와 Repository가 빠진 조금 단순한 형태일 뿐이라고 생각했다.
콘솔이든 디스코드든 입력과 출력이 존재한다는 점은 동일하니,
입력을 받아 도메인에 전달하고 결과를 출력하는 흐름 역시 같을 것이라 판단했다.
그래서 두 환경을 하나의 구조로 묶기 위해 InputView와 OutputView에 Reader/Writer 인터페이스를 두어 추상화하는 방식으로 통합을 시도했다.
“구현체만 바꾸면 되니 훨씬 유연하고 중복도 줄어들겠지”라는 생각은 당시의 나에게 매우 자연스러운 결론이었다.
하지만 디스코드 로직을 구현하는 순간 문제를 마주했다.
콘솔은 프로그램이 입력을 요청하는 Pull 방식이지만,
디스코드는 사용자가 행동할 때 이벤트가 발생하는 Push 방식이었다.
JDA 이벤트 리스너 구조는 내가 기존에 가정했던 MVC 흐름과 맞지 않았다.
그제야 나는 두 환경을 억지로 동일한 구조에 끼워 맞추려 했다는 사실을 깨달았다.
이 경험을 통해 MVC 패턴은 요청–응답 기반으로 흐름이 명확하게 순차적으로 진행되는 환경에서 더욱 효과적인 패턴이며,
모든 상황에 적용되는 만능 구조가 아니라는 사실을 몸으로 배웠다.
2. 변경에 대비하기 위해 추상화를 미리 도입하지 말자
지금까지 나는 “변경될 수 있는 부분은 미리 추상화해두는 것이 좋은 설계”라고 믿고 있었다.
하지만 실제로는 디스코드가 어떻게 동작하는지 이해하지 않은 상태에서 만든 추상화가
오히려 구조를 복잡하게 하고 개발 속도를 저해하고 있었다.
확장성이라는 명목으로 만든 Reader/Writer 인터페이스로 인해,
콘솔&디스코드 두 구현체가 동시에 필요하지도 않은 시그니처를 억지로 맞추느라 객체만 많아지고 각 역할은 흐려져 있었기 때문이다.
그래서 추상화를 걷어냈다.
콘솔은 ConsoleInput / ConsoleOutput으로 단순화했고,
디스코드 봇은 이벤트 리스너 자체가 입력 역할을 하니 출력만 DiscordOutput으로 분리했다.
이 과정에서 나는 추상화는 목표가 아니라 결과라는 사실을 배웠다.
충분한 이해와 반복되는 변화의 패턴이 확인될 때 비로소 추상화는 의미를 가진다는 점을 깨달았다.
변화에 대한 가장 좋은 예측은 변화를 경험하는 것이며,
실제로 변화가 일어나고 그 패턴이 반복된다는 확신이 생기는 순간에 추상화를 도입하는 것이 훨씬 견고하다는 것을 경험했다.
앞으로는 불필요한 인터페이스나 계층을 늘리는 실수를 줄이기 위해,
“충분한 이해 → 실제 변화 경험 → 반복 패턴 확인 → 추상화 도입”이라는 흐름을 지키며 더 단단한 설계를 해나가고 싶다.
3. 컨트롤러에서는 비지니스 로직 없이 흐름만 담당하게 하자
디스코드 봇은 이벤트 기반 명령 처리 흐름을 요구하지만,
기존 콘솔 구조에서는 Controller가 게임 흐름 제어와 일부 비지니스 로직 수행 책임을 동시에 담당하고 있었다.
이 상태에서 디스코드 봇 이벤트 리스너를 작성하자,
콘솔 Controller에 존재하던 로직이 리스너 클래스에도 그대로 중복되는 문제가 발생했다.
이 문제를 해결하기 위해서는 Controller와 Listener가 각각의 흐름 제어 역할만 담당하고,
모든 비즈니스 로직을 도메인 계층으로 분리해야 한다는 필요성이 명확하게 드러났다.
그래서 카드 효과 처리, 턴 전환, 우승 판단과 같은 핵심 게임 로직을 RoboGame 도메인 중심으로 재배치했고,
Controller와 Listener는 오직 흐름만 관리하며 같은 도메인을 호출하는 방식으로 리팩토링 하였다.
그 결과 코드 중복이 사라졌고, 콘솔과 디스코드 환경 모두에서 비즈니스 로직을 공유하며 확장할 수 있는 구조를 만들 수 있었다.
도전을 통해 느낀 점
아직 개선해야 할 부분도 있지만,,목표를 향해 꾸준히 노력하는 과정에서 중요한 사실을 깨달았다.
바로 '완벽함을 추구하기보다 일단 시작하는 것이 경험이 되고, 그 경험이 다음 행동을 구체화한다는 것'이다.
이로 인해 스스로 만들어둔 한계를 깨고 성장할 수 있었다.
그동안은 나 혼자 프로젝트를 시도할 생각조차 하지 못했고, 시작하면서도 “내가 할 수 있을까?” 라는 불안이 컸다.
복잡한 요구사항 앞에서 한 번에 완벽히 완성하려다 보니 계속 머릿속에서만 고민이 반복되고,
앞으로 나아가지 못하기도 했다.
하지만 목표 달성을 위해 해야할 일들을 작은 단계로 나누고 부족한 지점을 인식하며 보완해 나가는 방식을 거치며,
결국 끝까지 해낼 수 있었고 내가 생각보다 많은 것을 해낼 수 있는 사람이란 것을 알게 됐다.
이 경험을 바탕으로 더 큰 도전도 두려워하지 않고, 목표를 작게 쪼개며 계속 앞으로 나아가고 싶다.
또한 이번 미션에서는 1차 구현 성공까지 AI를 사용하지 않겠다는 도전에도 실패했지만, 그 과정에서 더 큰 배움을 얻었다.
AI를 쓰지 않는 것이 중요한 것이 아니라, 스스로 고민하고 문제를 정의한 뒤 AI를 활용하는 게 더 중요하다는 것을 깨달았다.
AI에게 막연히 해결책을 요구하는 대신, 내가 느끼는 문제와 원하는 방향을 구체적으로 설명하자
AI는 내 동료가 되어 내가 놓친 부분을 보완하고 나의 사고를 확장시켜주는 역할을 해주었다.
(예를 들어 코드를 주면서 “리팩토링 해줘”가 아니라,
“컨트롤러에 비즈니스 로직이 너무 많이 섞여 있어서 도메인 재사용성이 떨어진다고 느낀다.
RoboGame 같은 객체를 만들고 싶은데, 컨트롤러에서 어디까지 도메인으로 넘겨야 할지 감이 안 오니 힌트를 달라”처럼
구체적으로 적어 내려갔다.)
이 경험을 통해 앞으로도 AI에 의존하는 사람이 아니라,
먼저 스스로 사고한 뒤 명확한 질문을 던지고 답을 선별할 줄 아는 개발자가 되고자 한다.
다음 목표는?
이번 도전은 결과물보다도 나에게 더 넓은 가능성과 새로운 목표를 남겼다.
도전을 통해서 배울 점을 얻고 나니 자연스럽게 ‘다음에는 더 잘할 수 있을 것 같은데?’하는 의욕이 생겼고,
성장한 만큼 개선점도 더 또렷하게 보여 더 잘할 수 있었을 것 같은 아쉬움이 남는다.
QA 과정에서 /play 명령어의 응답 지연으로 인터랙션이 폐기되는 문제를 겪으며,
정상 동작 여부뿐 아니라 ‘얼마나 빠르고 안정적으로 응답하는가’도 서비스 품질을 결정짓는 요소라는 사실을 알게되었다.
이로 인해 기능 구현을 넘어, 시스템 레벨의 안정성까지 고려해야 한다는 점을 몸으로 배웠다.
또한 순서 변경 알고리즘 버그가 발견되었는데, 로그가 없어 버그 발생 상황을 일일이 물어봐야 했다.
이 경험은 꽤 민망했지만 동시에, 로깅을 공부하고 도입해야겠다는 확실한 동기 부여가 되었다.
앞으로는 이번 도전에서 발견한 부족함을 채우며 프로젝트를 확장하고 싶다.
친구들로부터 받은 피드백을 반영하고, 로깅을 도입해 안정성을 높인 뒤
배포까지 해내 프리코스 커뮤니티에 서비스를 소개하는 것이 다음 목표다.
또한 시스템 레벨의 예외 처리, 로깅, 이벤트 기반 구조 등 이번 도전에서 드러난 기술적 빈틈을 채워갈 것이다.
특히 비동기 흐름과 이벤트 처리는 이번에 개발하면서 처음 마주한 영역이라 더 깊게 공부해보고 싶다.
마치며
이번 도전은 외적으로도 내적으로도 결과물을 남겼다는 점에서 도전에 성공했다고 느낀다.
도전을 하면서 처음엔 생각하지 못했던 새로운 목표가 생기고,
“조금 더 잘할 수 있을 것 같은데?” 하는 아쉬움도 남았다.
그래서 이런 감정을 느꼈다는 게, 이번 경험 속에서 분명히 성장했다는 의미 아닐까 하는 생각이 들었다.
아쉬움에서 비롯된 새로운 목표들을 하나씩 실천하며, 무럭무럭 성장해 나가야쥐
화이팅!!!!🍀
'Project > Robo77' 카테고리의 다른 글
| 로보 77 구현기 9일 차 - 불필요한 추상화를 걷어내고 Discord 리스너 테스트하기 (0) | 2025.11.15 |
|---|---|
| 로보 77 구현기 8일 차 - 동작 우선 코드, 객체지향으로 CPR 하기 (0) | 2025.11.14 |
| 로보 77 구현기 7일 차 - 빈 깡통이었던 디스코드 봇에 생명 불어넣기 (0) | 2025.11.13 |
| 로보 77 구현기 6일 차 - Phase 2를 향한 재정비와 첫 디스코드 봇 생성! (0) | 2025.11.12 |
| 로보 77 구현기 2일 차 - 전략 패턴과 씨름한 하루 (0) | 2025.11.08 |