본문 바로가기
📝 회고/🤝 코드스쿼드 백엔드 프로젝트

[프로젝트 회고] : 사다리 게임 (OOP, 리팩토링, 단위 테스트, Git)

by kukim 2022. 3. 2.
  사다리 게임 프로젝트 (전체 소스코드)
배운 것 Java, OOP, 리팩토링, DCI 패턴의 테스트코드, Git
기간 22.02.14 ~ 22.02.18 (5일) 
팀원 @쿠킴 
Step 1 기본 기능 구현 / 1단계 PR
Step 2 리팩토링 맛보기 / 2단계 PR
Step 3 사다리 모양 개선 / 3단계 PR
Step 4 리팩토링 2 / 4단계 PR
Step 5 실행결과 출력 / 5단계 코드
Reviewer @Honux, @Roach

사다리 게임은 콘솔 프로그램 프로젝트로 Java, OOP, 리팩토링, 테스트 코드를 연습할 수 있었고 '코드 스쿼드'에서 코드 리뷰를 받을 수 있는 첫 프로젝트였다. 개인적인 부족함을 많이 느끼면서도 재미있어서 시간이 빠르게 갔다. 


프로젝트하며 배운 점

Java : ✅  박싱 타입과 기본 타입을 적재적소에 사용하자

자바 박싱 타입(Integer, Character, Float...)은 컬렉션의 원소, 키 값이나 제네릭, 리플렉션을 통한 메서드 호출할 때 사용한다.

그 외의 변수나 연산은 기본 타입 int, char, float... 을 사용하는 것이 좋다.

🧐 생각

이 내용은 이펙티브 자바 item 61에도 있는 내용이다. 박싱 타입은 null이 가능하지만 문제가 있다.

박싱 타입의 문제는 메모리 비효율 적이고 기본 타입 + 박싱 타입 혼용 연산 시 박싱 타입의 박싱이 풀린다. 만약 이때 박싱 타입이 null이라면 NullPointException이 발생한다. 정말 자주 일어나는 일 같다.

 

Java : ✅  표준 입력을 사용했고 더 이상 사용하지 않는다면 닫아주자

사용자 입력을 위해 표준 입력, Scanner(system.in) 해주었다. 입력이 더 이상 없을 때는 scanner.close()로 닫아주자. 


리팩토링 : ✅  메서드, 변수 이름 짓기와 매직넘버 변경

가장 기본적인 이름짓기는 중요하다. 항상 주의해야겠다. 또한 매직넘버를 사용하기보다 상수로 추출해야겠다.

리팩토링 : ✅  복잡한 조건문이나 계산식은 메서드나 변수는 추출하자

너무 복잡한 조건이나 계산식은 추출하여 사용하자

// 메서드 추출 예
// 추출 전
if (lines[col_i] == 1 && lines[col_i + 1] == 1)

// 추출 후
if (isRadder(col_i, lines[row_i]))
private static boolean isRadder(int col_i, int[] lines) {
	return lines[col_i] == 1 && lines[col_i + 1] == 1;
}



// 변수 추출 예

// 추출 전
height * 2 - 1

// 추출 후
line = height * 2 - 1

OOP : ✅  생성자 vs 정적 팩토리 메서드

이펙티브 자바 item 1을 공부한 후 연습을 위해 의도적으로 생성자 대신 정적 팩토리 메서드를 사용하고 있다. 

🧐 생각

현재 프로젝트에서 Ladder 클래스의 발전 가능성은 적다. 다시 말해 동일한 매개변수로 들어오는 생성자가 더 필요하지 않을 수 있다.

단순히 생성자만으로도 좋을 수 있다. 하지만 정적 팩토리 메서드를 작성했다. 그 이유는 혹시 모를 프로젝트 발전 가능성을 두었다. 아쉬운 점은 팩토리 메서드라고 설명하는 주석(자바독)이 없었다. 메서드 이름도 create() 대신 createWithPlayerAndLadderCount()는 어떨까 생각해본다. 너무 길려나?

 

OOP : ✅  Utils 관련 class는 static으로 만들어야 할까 의존성 주입을 해야 할까?

사용자 입력을 담당할 InputView, 사용자 출력을 담당할 OutputView가 있었다. Step2 구현 사항은 static으로 만들었다. 그 이유는 코드 작성 시 가독성을 위해서였다. 매번 InputView나 OutputView 객체를 생성하고 사용하기 힘들었다.

🧐 생각

Utils 관련 클래스는 static으로 좋아 보인다. 왜냐하면 자주 사용되기 때문에 메모리 상에서도 이점을 가질 수 있다. 또한 Util 클래스는 상태(정보)가 없기 때문에 static해도 문제가 없다는 것이다.(동료 Ader의 말)

 

OOP : ✅  디자인 패턴을 이론보다 몸으로 경험하자

디자인 패턴 틀에 박혀 설계 고민에 빠지는 것보다 어떻게 쪼개고 좋을지 고민하고 코드를 작성해보자. 그렇다 보면 자연스럽게 패턴이 나오지 않을까?

 

OOP : ✅ 역할과 책임을 분명히 하기

MVC 패턴으로 프로젝트 구조를 잡았다. 문제는 InputView에서 메시지 출력을 위해 OutputView의 메서드를 InputView에서 사용했다. 두 사이의 결합도가 높아졌다. InputView에서 OutputView를 사용해도 문제없지만, 사용자 입력을 받는 것은 InputView의 책임이니 InputView에서만 모든 일이 끝나도 문제없어 보인다. 정답은 없겠지만 설계 시 역할과 책임을 분명히 해야겠다는 경험을 쌓았다.


테스트 코드 : ✅ DCI 패턴의 테스트 코드

Step 3에서 TDD으로 Line, StringUtils를 만들었다. 호눅스의 말을 통해 BDD를 알게 되었고 Step 4에서 처음으로 BDD와 유사한 방식을 적용해보았다. 코드의 행동을 설명하는 테스트 코드는 처음엔 낯설었지만 결과를 봤을 때 해당 객체의 행동들이 한눈에 들어왔다. 이종립님의 Junit5로 계층 구조의 테스트 코드 작성하기 의 레퍼런스로 구현하니 어렵지 않았다. 행위 중심으로 테스트를 하니 전에는 보이지 않았던 누락된 예외나 추가 테스트 항목이 보였다.(TDD를 잘 못했을 수도 있겠지만) BDD 연습은 좋은 경험이었다. 앞으로도 조금씩 실천해봐야겠다. (사진의 소스코드)

step5의 DCI 패턴의 테스트 코드

 

테스트 코드 : ✅  private 메서드를 테스트해야 할까?

테스트 코드를 작성하며 종종 private 메서드를 테스트하고 싶을 때가 있다. 해야 할지 말아야 할지 고민이 되었다. 보통 public 메서드가 private를 커버하고 있기 때문에 안 해도 된다는 의견이었지만 페이스북 그룹 javawocky 박성철 님의 글을 통해 생각을 정리할 수 있었다.

private 메서드의 테스트 여부는 정답이 없고 현재 프로그래밍하는 시점의 context을 고려하자였다. 자세한 요약 내용은 private 메서드도 테스트를 해야 할까? (private 메서드 테스트하고 싶을 때...) ✅이다.


Git : rebase, merge, branch 관리

upstream (원본) 저장소의 ku-kim 브랜치

upstream을 fork 뜬 origin 저장소의 ku-kim 브랜치

 

상황 : origin 저장소를 clone 받았다. origin의 ku-kim 브랜치에서 step1 브랜치를 생성했다. 많은 것을 작업하고 git push origin ku-kim 했고 upstream으로 PR을 보냈다. PR 리뷰받기 전 step 2 진행을 하고 싶어 step1위에서 step 2 브랜치 생성하여 작업했다. 그러던 중 PR 리뷰가 끝나고 머지가 되었다. 로컬 저장소에서 upstream의 머지 커밋을 업데이트하고 머지 커밋 위에서 이전의 작업 중이던 step 2 내용을 시작하고 싶다. 이때 step 2 브랜치를 upstream/ku-kim으로 rebase 했다. 충돌을 해결하여 작업을 진행할 수 있었다.


ETC : ✅  IntelliJ의 메서드 자동 생성 시 접근 제어자 주의

접근 제어자를 항상 조심하자. 나는 보통 메서드나 클래스 생성 시 해당 메서드, 클래스 사용하는 곳에 구현되지 않은 것을 먼저 작성하고 옵션 + 엔터 단축키를 사용해 생성한다. 오늘은 테스트 용도로 main문을 내부에 만들고 메서드를 만들었는데 main문이 클래스 안에 있다 보니 public 되어야 할 메서드가 private로 만들어졌다. 이를 제대로 확인하지 않았다. 항상 주의하자


도전 : 💪 일급 컬렉션 도입 여부

로치의 리뷰로 일급 컬렉션 도입을 고민하게 됐다. 현재 과제에선 적용하지 않기로 했고 다음 과제에 도입해보고자 한다.

 


생각 : 처음부터 완벽한 설계란 불가능하다. 미래 지향적으로 코딩하지 말자(YAGNI)

모든 상황에 맞진 않겠지만 현재 주어진 요구사항에 맞춰서 구현하자. 필요한 작업만 하자. YAGNI(You Ain't Goona Need It)의 원칙과 동일하다. 미래가 안올 수 있다. 미래가 생각과 다를 수 있다. 미래를 고민하다 현재를 구현하지 못한다. 당장 할 수 있는 것부터 작게 구현하고 리팩터링 하자. 설계를 진화시키자. 필요할 때 클래스, 메서드를 분할하고 작업하자. 그래야 일찍 퇴근도 할 수 있으니까

 

생각 : 🤝 프로그래밍, 개발에 정답이 있을까?

프로그래밍은 컴퓨터로 하는 것이니 이분법적인 사고로 정답이 있다고 생각했다. 하지만 요즘 개발을 배우면 배울수록 개발에는 하나의 정답이 없다는 것을 알게 되었다. 다양한 풀이가 있고 그중에 현재 주어진 문맥(context)에 맞는 최적의 솔루션을 찾아야 한다. 그렇다! context가 중요하다. 현대의 개발은 대부분 혼자가 아닌 팀으로 한다. 따라서 context를 적확하고 빠르게 이해하기 위해선 뛰어난 커뮤니케이션 능력이 필요하다. 아, 이래서 개발자가 개발 능력과 함께 커뮤니케이션 능력이 좋아야 하는구나를 느낀다.


프로젝트를 하며 Java, OOP, 리팩토링, DCI 패턴의 테스트코드, Git를 연습할 수 있었다. 

객체에게 책임을 위임하려했고 객체 스스로 상태와 행동 갖도록 고민하고 구현했다. 리팩터링의 의도적 연습을 위해 들여 쓰기 1단계만 허용하여 메서드 추출을 했다. 주석보단 코드 자체로 의미를 표현했다. 계산 로직(사다리 게임 결과)을 TDD로 구현해보았고 테스트 코드에 DCI 패턴을 적용해 구조와 가독성을 높였다. Git, GitHub 사용이 익숙해져 rebase, merge, branch 사용과 문제 발생 시 해결할 수 있게 되었다.

부족한 부분을 많이 느꼈고 사소하게 잦은 실수가 반복되었고 기본기에 집중해야겠다는 생각이 들었다. 시간이 지나 다시 구현해봐야겠다.


👍 Keep

- 새로운 것을 배우고 적용했다. 문서화했다.

- 테스트 코드 작성

- 코드리뷰 적용

🔥Problem

- 이해하기 쉬운 메서드, 변수명이나 들여 쓰기 줄이기 등 기본을 생

🚒 Try

- 피드백을 잊지 말고 다음 프로젝트에도 적용하자


⛓ Reference

[일일 회고] 22.02.14~15 - 좋은 코드와 Git

private 메서드도 테스트를 해야 할까? (private 메서드 테스트 하고 싶을 때...) ✅ 👃

[일일 회고] 22.02.16~17 - DCI 패턴의 테스트 코드 적용하기!, 개발에 정답은 없다. 그래서 ~?

[주간 회고] 22.02. 3주차 ✅ 🥘

 

 

댓글