🏁 서론
사다리 게임... 발판 만들기... 빈 칸 없이 연결하기... 생각보다 너무 어렵다 😵💫 친구들과 내기할 때 종종 사용했던 익숙한 사다리 게임을, 직접 코드로 구현해보니 그 속에 생각보다 많은 고민이 숨어 있다는 걸 느꼈다.
단순히 "랜덤하게 발판을 만들면 되겠지?" 하고 시작했지만, 연속으로 연결되면 안 되고, 모든 열은 최소한 한 번은 연결되어야 하고,
게임 규칙도 자연스럽게 보장해야 한다는 점에서 꽤 까다로운 미션이었다.
이번 회고에서는 사다리 게임을 구현하면서 마주한 고민들, 그에 대한 해결 방식, 그리고 리뷰어 피드백을 통해 배운 점들을 정리해보려 한다.
📚 이번 주 학습 내용
https://github.com/izzy80/java-ladder-func-playground
GitHub - izzy80/java-ladder-func-playground: 초록 스터디 자바 기초 과정 사다리 미션을 위한 저장소
초록 스터디 자바 기초 과정 사다리 미션을 위한 저장소. Contribute to izzy80/java-ladder-func-playground development by creating an account on GitHub.
github.com
🛠️ 단계별 PR 회고
📌 1단계
1. 메서드 네이밍은 읽는 사람이 이해하기 쉽게
📎 참고 링크: 좋은 코드를 위한 자바 메서드 네이밍
- 초기에는 cannotLink()처럼 부정형 메서드를 사용했는데, 리뷰어께서 가독성을 이유로 다른 메소드명을 추천했다.
- 그래서 canLink()로 메소드명을 바꾸고, 조건문에서 if (!canLink())로 사용하는 것으로 수정했다.
boolean 메서드는 is, has, can 등 의도를 명확하게 드러내는 네이밍이 좋다!
📌 2단계
1. Interface의 default 메서드를 쓸까? 말까?
public interface LinkStrategy {
boolean canLink();
default int pickRandomRow(int bound) {
throw new UnsupportedOperationException("랜덤 선택은 지원되지 않는 전략입니다.");
}
}
이 코드를 작성한 이유는 pickRandomRow() 메서드가 오직 RandomLinkStrategy에서만 사용되기 때문이었다. 그래서 나머지 전략에서는 구현 부담을 줄이기 위해 default 메서드로 정의하고, 기본적으로는 예외를 던지도록 했다.
그런데 리뷰어에게 아래와 같은 질문을 받았다
"default 메서드에서 결국 UnsupportedOperationException을 던진다면, 굳이 default로 둘 필요가 있을까요?"
이 질문을 통해 default 메서드의 역할과 사용 목적에 대해 다시 생각해보게 되었다.
내가 default 메소드를 쓴 이유는 다음과 같은 특징 때문이었다.
선택적 구현 가능
- 모든 구현체가 반드시 구현할 필요는 없고, 필요한 경우만 override 가능
그런데 사실 이게 주되기 보다는 구현해놓고 override를 잘 하지 않는 정말 기본 메소드? 개념으로사용하나보다. 그래서 약간 특징을 잘못 이해했다고 생각했다.
기본 구현이 실제로 사용 가능한 경우라면 default는 좋은 선택이지만, 이렇게 예외만 던지는 형태라면 오히려 혼란을 줄 수 있다는 피드백이었다. 결국 이 인터페이스는 설계를 바꾸는 과정에서 더 이상 사용되지 않게 되었고, 자연스럽게 삭제하게 되었다. 그리고 별개로 사실 interface에 default 메서드가 있긴 하지만 잘 쓰이지 않는다는 의견도 듣게 되었다. 개념이랑 실무랑은 약간 다른가보다. 그래서 interface에서는 추상 메소드만 써야겠다고 생각했다.
2. 캡슐화와 디미터 법칙(Demeter's Law)
📎 참고 링크: 디미터의 법칙
lines.get(row).getLinks().get(col).link();
위 코드는 객체 내부 구조에 너무 깊이 관여하고 있어서 캡슐화가 깨진다는 피드백을 받았다. 그래서 아래와 같이 객체가 해야 할 일을 객체에게 메시지로 전달하도록 리팩터링했다.
public boolean isLinkedAt(int col) {
return points.get(col).isLinked();
}
public void linkAt(int col) {
points.get(col).link();
}
public void unlinkAt(int col) {
points.get(col).unlink();
}
디미터의 법칙
-객체에게 자료를 숨기는 대신 함수를 공개
-여러 개의 .(도트)을 사용하지 말라는 법칙
객체지향의 캡슐화라고 하면 그냥 private 필드를 떠올리기 쉬운데, 이번 경험을 통해 행위 단위로 캡슐화하는 것이 얼마나 중요한지 배울 수 있었다.
📌 3단계
1. `.formatted()` 활용하기
public Column {
if (value < MIN_COLUMN) {
throw new IllegalArgumentException("사다리의 넓이는 " + MIN_COLUMN + " 이상이어야 합니다.");
이 코드에서
자바 15부터 지원되는 .formatted() 메서드도 고려해보시면 좋을 것 같아요 👍🏻
다음과 같은 리뷰가 달렸다. 그래서 아래와 같이 사용해보았다.
public Column {
if (value < MIN_COLUMN) {
throw new IllegalArgumentException(
"사다리의 넓이는 %d 이상이어야 합니다.".formatted(MIN_COLUMN)
)}}
사소해 보일 수 있는 문법 하나지만, 코드의 가독성과 유지보수성을 확실히 높여준다고 느꼈다.
2. IntelliJ 포맷팅 단축키
- Windows: Ctrl + Alt + L
- Mac: Cmd + Option + L
3. `\n` vs `System.lineSeparator()`
문자열 줄바꿈 시 단순히 \n을 사용하기보다, 운영체제별 줄바꿈 차이를 고려해 System.lineSeparator()를 사용하는 것이 더 안전하다
- \n → 유닉스(Linux/macOS) 기준 줄바꿈 문자
- System.lineSeparator() → 시스템에 따라 \n, \r\n 등을 자동 적용
💡 플랫폼 독립적인 코드를 위해선 System.lineSeparator()를 사용하는 습관을 들이자!
4. 일급 컬렉션 내부에서 생성 책임을 가지는 방식
초기에는 다음과 같이 외부에서 Map을 생성하고, 생성자에 직접 넘겨주는 방식이었다.
Map<Integer, Integer> resultMap = new HashMap<>();
for (int start = 0; start < columns.value(); start++) {
resultMap.put(start, game.play(start));
}
LadderResult result = new LadderResult(resultMap);
그런데 리뷰어님이 이 구조에 대해 의견을 주셨고, 이를 계기로 생성 책임을 객체 내부로 넘기는 방향을 고민하게 되었다.
public static LadderResult of(LadderGame game, Column columns) {
Map<Integer, Integer> resultMap = new HashMap<>();
for (int start = 0; start < columns.value(); start++) {
resultMap.put(start, game.play(start));
}
return new LadderResult(resultMap);
}
이 접근이 좋은 이유
- LadderResult라는 도메인 객체가 자신이 필요한 데이터가 어떤 과정으로 생성되는지 알고 있음
- 외부에서는 더 이상 Map의 구조나 생성 방식에 대해 알 필요 없이, of() 메서드만 호출하면 됨 → 캡슐화 향상
- 다른 일급 컬렉션처럼 의미를 가진 생성 로직이 내부로 이동하면서, 도메인 로직에 집중할 수 있음
📝 마무리 회고
이번 사다리 게임은 자바 스터디의 마지막 미션이었지만, 솔직히 말하면 코드가 썩 마음에 들진 않는다.
지금 당장 수정한다고 크게 나아질 것 같지도 않고, 오히려 조금 더 공부한 뒤에 다시 리팩터링해보고 싶은 마음이 크다.
코드를 작성할 당시엔 "이 정도면 괜찮지!" 싶다가도, 나중에 다시 보면 "이게 뭐야..." 싶은 게 개발자 성장의 증거라는 말이 떠오른다. 지금도 딱 그 느낌이다. 아직 부족하지만, 그래도 분명히 예전보단 나아졌다. 자바의 기초를 다진 만큼, 이제 스프링으로 넘어갈 준비가 되었다!
앞으로 더 성장한 모습으로 돌아올 수 있도록, 꾸준히 배워가자 💪
🔗 Pull Request 기록
'🌤️일상 > 초록 스터디' 카테고리의 다른 글
Spring 1주차) Spring MVC 회고 (0) | 2025.06.19 |
---|---|
Java 3주차) 로또 회고 (0) | 2025.05.19 |
Java 2주차) 자동차 경주 회고 (2) | 2025.05.13 |
Java 1주차) 계산기 회고 (0) | 2025.05.03 |