
인프런 워밍업 클럽이 시작한지 2주가 지났다.. 앞으로 남은 발자국이 2개 뿐이다.. 👣
이번주에는 중간점검으로 온라인 라이브가 진행 되었다.
온라인 라이브에 대한 내용은 다음과 같다.
각 세션은 놀라울 정도의 세심한 우빈님의 피드백 덕분에 많은 인사이트를 얻게 되어 좋은 시간이었다.
나도 첫번째로 코드리뷰를 신청하고 라이브 마지막에 코드리뷰를 받았는데 너무 좋은 경험이었다. 💪
(커피를 좋아하시기로 유명한 우빈님께 Q&A 세션 도중 저가 커피 브랜드 중 어디 브랜드가 제일 맛있냐는 질문이 나왔는데.. 드셔 보신 적이 없다고 답변해 주신 부분이 인상적이었다.. ㅋㅋ 😁)

위에서 이야기했듯이 코드리뷰를 첫번째로 신청해서 우빈님께 온라인 라이브 시간에 코드리뷰를 받았다.
해당 미션은 "스터디 카페" 프로그램을 리팩토링하는 미션이였는데..
작년 4분기에 강의를 수강했을 당시에 3번이나 진행하였다.
이번이 네 번째 리팩토링이였는데 할 때마다 왜 새로운 것인지.. 🥲
그래도 손이 기억이라도 한 듯 나름 순조롭게(?) 미션을 진행하게 되었고, 추가적으로 리팩토링을 진행하였고 해당 부분을 리뷰를 받고 싶어 신청하게 되었다.
(우빈님께서 4번이라는 부분에 놀라셨는지(?).. 디스코드 스레드에 댓글을 남겨주셨다! 🤣)

다시 돌아와서.. 코드리뷰 받은 내용은 아래와 같다.
🔗 Github PR 링크
StudyCafePassType 구조화 리팩토링
♻️ 리팩토링 코드
public enum StudyCafePassType implements PassTypeSelectable, PassTypeFormatter { // 📝 인터페이스 구조화
HOURLY("시간 단위 이용권") {
// 📝 사용자 입력에 대한 구조화
@Override
public boolean selected(String userInput) {
return "1".equals(userInput);
}
// 📝 사용자 출력 포맷에 대한 구조화
@Override
public String format(StudyCafePass pass) {
return String.format("%s시간권 - %d원", pass.getDuration(), pass.getPrice());
}
}
}
✏ 우빈님 리뷰
Q. 클래스 내부에서 사용자 입력값 및 출력값에 사용하는 인터페이스를 구현함으로써 오버 엔지니어링이 된 것 같은 느낌이 드네요.. 🤦♂️
A. 저도 그렇게 생각해요.. ㅋㅋㅋㅋ
오버 엔지니어링이기보다 PassType은 중요한 도메인 모델인데, Input에서만 의미를 가지는 사용자 선택지가 침투하고 있다.
사용자 선택 방법이 "a", "b", "c"로 바뀐다면? 단순히 입력 방식을 바꿨을 뿐인데 무료 도메인 모델이 수정되어야 하는 엄청난 사태가 발생한다.
항상 구조화를 하는 것이 정답은 아니다.
Output format도 마찬가지이다.
책임이 우선이다. 적절한 책임의 분배가 객체의 결합도를 낮추고 응집도를 높이는 것이다.
🤔 돌아보기
단순히, OCP를 적용하기 위해 접근해서 리팩토링 했었는데..
적절한 객체 책임 분리를 하지 못했으며,
중요한 도메인 모델을 수정하는 엄청 큰 사이드 이펙트가 일어날 수 있다는 점을 간과 했다는 것이다.
다음부터는 구조화를 남발하지 않고 책임에 집중해서 리팩토링 해야겠다..
🔗 Github PR 링크
ReadLockerPasses 객체 분리
♻️ 리팩토링 코드
public class ReadLockerPasses { // 📝 LockerPasses를 해석하는 객체 분리
private final List<StudyCafeLockerPass> passes;
private ReadLockerPasses(List<StudyCafeLockerPass> passes) {
this.passes = passes;
}
// 📝 lines을 해석하여 List<StudyCafeLockerPass> 객체를 만들어준다.
public static ReadLockerPasses ofLines(List<String> lines) {
List<StudyCafeLockerPass> passes = lines.stream()
.map(ReadLockerPasses::ofLine)
.toList();
return new ReadLockerPasses(passes);
}
private static StudyCafeLockerPass ofLine(String line) {
String[] values = line.split(CSV_SPLITTER); // ⭐️ CSV라는 방식에 종속적
StudyCafePassType studyCafePassType = StudyCafePassType.valueOf(values[0]);
int duration = Integer.parseInt(values[1]);
int price = Integer.parseInt(values[2]);
return StudyCafeLockerPass.of(studyCafePassType, duration, price);
}
// 📝 StudyCafeLockerPasses를 생성해준다.
public StudyCafeLockerPasses toPasses() {
return StudyCafeLockerPasses.of(passes);
}
}
✏ 우빈님 리뷰
Q. 일급 컬렉션을 적용하기 위해 toPasses() 메서드를 생성했는데
Read~라는 네이밍을 가진 클래스에 많은 책임이 부여된 것 같아 네이밍이 모호한 것 같습니다.
좋은 방법이 있을까요..? 🧐
A. 많은 책임이라고 생각하신 이유가 있을까요?
"ReadLockerPasses는 어디선가 읽은 lines를 가지고 StudyCafeLockerPasses를 만들어준다"의 책임으로 보여서, 어색하지 않으며 테스트 코드 작성도 가능하다.
그와 별개로 CSV라는 방식에 종속되어있다. CSV형식이 다른 방식으로 바뀌었을 때 같이 바뀌어야 하는 부분이 CSV_SPLITTER 부분이다. 의도한 것 이라면 상관없다.
🤔 돌아보기
Read라는 클래스명을 가지고 있어 StudyCafeLockerPasses를 생성해주는 메서드가 존재해 많은 책임이 있다고 생각했는데..
우빈님 리뷰 이후에 다시 보니.. 그렇게 어색한가 싶기도 하다.. ㅎㅎ
해당 클래스의 작성 당시 CSV 방식을 의존하려는 의도는 없었다.
단순히 읽은 부분의 개념을 추출한 것인데.. 위의 클래스는 CSV_SPLITTER상수가 사용되어 의도하지 않게 CSV라는 방식에 종속적이게 된 것이다.
CSV라는 방식이 변경되면 객체 로직이 바뀌어야 한다.
객체 구현 시, 종속성에 대해서 방어적으로 접근할 필요가 있어보인다.
🔗 Github PR 링크
ProvideException 커스텀 예외
♻️ 리팩토링 코드
// 📝 이용권을 가져오는 과정에서 생긴 에러의 커스텀 예외 클래스 생성
public class ProvideException extends RuntimeException {
public ProvideException(String message) {
super(message);
}
}
public class StudyCafePassMachine {
public void run() {
try {
outputHandler.showPassOrderSummary(order);
} catch (AppException e) {
outputHandler.showSimpleMessage(e.getMessage());
} catch (ProvideException e) {
// 📝 커스텀 예외 catch
outputHandler.showSimpleMessage("이용권을 제공받을 수 없습니다.");
} catch (Exception e) {
outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다.");
}
}
}
✏ 우빈님 리뷰
Q. AppException 성격이랑 다른 것 같다고 생각되어 Provider 인터페이스에서 발생하는 예외 클래스 ProvideException를 별도로 생성하였습니다.
A. 혹시 어떻게 다르다고 생각셨나요??
AppException의 의도는, 프로그램에서 발생할 수 있는 대부분의 애플리케이션 상황을 정의하는 최상위 예외 클래스이다.
만약 ProvideException을 별도로 표기하여 더 구체적인 상황을 나타내고 싶으면, AppException을 상속받아서 구성해야 한다.
그렇지 않으면 커스텀 예외 클래스가 늘어남에 따라 catch절도 같이 늘어날 것이다.
추가적으로, "이용권을 제공받을 수 없습니다."라는 메시지가 사용자 친화적이지 않다.
🤔 돌아보기
리팩토링 당시, 초기 이용권을 가져와야만 프로그램이 실행된다는 관점에서 ProvideException의 커스텀 예외 클래스를 작성하였다.
하지만 이용권을 가져오는 부분은 프로그램 내부에서 필요한 시점마다 호출하고 있어
우빈님 리뷰대로 AppException 클래스를 상속받아서 작성하는 것이 더 나은 설계 같다.
예외 메세지도 사용자 관점에서는 친화적이지 않은 것이 분명하다.
내가 키오스크 시스템을 사용하다가 저런 메세지를 마주한다면... 화가 날 것 이다... 😡
프로그램의 의도를 정확히 파악할 필요가 있어보인다. 또한 예외 메세지도 누가 보는지에 따라 고민해보는 습관을 길러야겠다.
이렇게, 요청한 3개의 리뷰와 2개의 추가 리뷰를 받아 보았다..
고작 3일 만에 7명이나 리뷰를 해주셨는데 세심하고 또 세심했다... 퀄리티가 상당했다.. ✨
이번 온라인 라이브를 통해 우빈님에 대한 팬심과 존경심이 더욱 커졌다....! 📈
리뷰해주신 내용으로 다시 리팩토링을 함으로써 한층 더 Readable Code에 대한 성장을 경험할 수 있었다. 🚀

좋은 주석 - 주석의 양면성
변수와 메서드 나열 순서
패키지 나누기
기능 유지보수하기
정렬 단축키, linting, style - sonarlint, editorconfig

메서드 추출로 추상화 레벨 맞추기
Optional
객체에 메시지 보내기
객체의 책임과 응집도
⭐️ 추상화 관점의 차이 - FileHandler

능동적 읽기
오버 엔지니어링
은탄환은 없다

단위 테스트
수동 테스트, 자동화 테스트 -> 인지 필요
sout으로 출력하고 눈으로 직접 확인Junit5, AssertJ
해피 케이스, 예외 케이스 -> 테스트 케이스 세분화
경계값 테스트
테스트하기 쉬운/어려운 영역 (순수함수)
lombok

TDD
테스트 주도 개발 (Test Driven Development)로, 프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론
선 기능 구현, 테스트 작성의 문제점 (일반적인 개발) - 구현순서 : 기능 -> 테스트
선 테스트 작성, 기능 구현 (TDD) - 구현순서 : 테스트 -> 기능
TDD의 관점
레드 - 그린 - 리팩토링
1. Red : 실패하는 테스트 작성
2. Green : 테스트 통과 하는 최소한의 코딩
3. Refactor : 구현 코드 개선 테스트 통과 유지
애자일 방법론 vs 폭포수 방법론
애자일 방법론 https://agilemanifesto.org/iso/ko/manifesto.html
폭포수 방법론
익스트림 프로그래밍
스크럼, 칸반
스크럼
애자일 프레임워크로, 일정한 스프린트 동안 작업을 계획하고 진행하는 반복적이고 점진적인 개발 방식이다. 짧은 개발 스프린트를 통해 빠르게 결과물을 만들고 지속적으로 개선하는 것이 핵심이다.
1️⃣ 백로그 작성 – 제품 백로그에 모든 요구사항을 정리
2️⃣ 스프린트 계획 – 스프린트 기간 동안 수행할 작업 선정
3️⃣ 스프린트 진행 – 개발 진행 및 매일 스탠드업 미팅
4️⃣ 스프린트 리뷰 – 개발 완료된 기능을 검토
5️⃣ 회고(Retrospective) – 개선점을 찾고 다음 스프린트에 반영
칸반
Workflow와 가시성을 중심으로 한 애자일 프레임워크로, 지속적인 개선과 작업량 관리를 중점적으로 다룬다. 작업을 시각적으로 표현하여 현재 진행 상황을 쉽게 파악할 수 있도록 합니다.
1️⃣ Backlog: 해야 할 작업을 모아둠
2️⃣ To Do: 현재 진행할 작업
3️⃣ In Progress: 진행 중인 작업
4️⃣ Review/Test: 리뷰나 테스트가 필요한 작업
5️⃣ Done: 완료된 작업

테스트 코드는 문서다.
@DisplayName - 도메인 정책, 용어를 사용한 명확한 문장
Given / When / Then - 주어진 환경, 행동, 상태 변화
TDD vs BDD
Junit vs Spock
언어가 사고를 제한한다.

미션과 발자국을 정신없이 진행하다 보니 벌써 남은 인프런 워밍업 클럽도 2주밖에 남지 않았다.
처음에 OT 라이브 당시 러너가 120명 정도였는데, 이번 중간 점검 라이브 때는 60명 정도로 줄어들었다.
강의 내용 자체는 어렵지 않지만.. 2개의 강의(14시간 + 12시간)를 한 달만에 들으면서 미션과 발자국을 진행하는 건 쉽지 않아 보인다..
나는 미리 강의를 수강해서 다행이다라는 생각이 든다.. 😅
하지만, 쉽지 않은 만큼 성실히 참여한다면 단기간 내 성장하는 데 큰 도움이 될 것이다.

그리고 이번 주에 코드리뷰를 신청하기 잘했다는 생각이 들었다. 🍀
위에서도 여러 번 언급했지만 우빈님의 세심한 리뷰 탓(?)에
내가 미션을 수행하는 데 있어 우빈님보다 세심하게 집착 했었나..? 반성하게 된다... 😭
마지막 주차 온라인 라이브에서도 테스트 코드에 대한 코드리뷰가 진행된다고 한다.
기회가 된다면 또 한 번 코드리뷰를 받아 단골 손님이 되고 싶다. 😂

2주를 걸쳐, 읽기 좋은 코드의 스터디 과정은 이번주로 막을 내렸다.
다음 주차부터는 테스트 코드에 대해 본격적으로 스터디하는 과정이 진행된다.
남은 2주도 화이팅하며 좋은 성장을 이루길 기대해 본다. 🔥
끝으로,
3월 중순이 되니 이제 슬슬 봄 내음이 나는 것 같다.. 🌸
얼어붙은 개발 시장에도 봄이 찾아왔으면 좋겠다.. 🧊
발자국 2주차 끄읕 !
[출처]