Skip to content

[사다리 1-4단계] 천희정 미션 제출합니다. #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: heejung72
Choose a base branch
from

Conversation

heejung72
Copy link

@heejung72 heejung72 commented May 24, 2025

안녕하세요 ! 오찌 :-)
이번 리뷰도 잘 부탁드립니다

1. 기존 프로그래밍 요구사항

이번 미션에서는 기존 프로그래밍 요구사항인 indent, else등을 잘 지키기 위해 노력해서 코드를 작성했습니다 ! 값 검증에서 else가 들어가야하는 경우나, if가 계속해서 들어가야하는 경우 메소드로 따로 작성했습니다. 또한, 지난 리뷰에 작성해주신 와일드 카드 임포트 없이 필요한 클래스만 임포트 해서 사용했습니다.
-> 사실 와일드 카드가 굉장히 편리하다고 느꼈는데, 왜 사용하지 말라고 하시는지 궁금하여 찾아보았습니다. 명확하지 않은 의존성, 클래스 이름 충돌 위험. 불필요한 클래스 임포트, 가독성 저하 정도가 있는 것 같습니다. 지금 처럼 규모가 작은 프로젝트에서는 문제를 일으키지 않겠으나, 개발 규모가 커지면 안전하지 않을 수 있기에 와일드 카드를 지양하는 습관을 가지려고 하겠습니다 !

2. MVC

지난 주 미션 리뷰에서 말씀해주신 모델-뷰-컨트롤러의 개념과, 도메인, 추가로 의존성에 대한 공부를 한 경험을 바탕으로 이번 미션에서는 MVC 방식을 지키기위해 노력했습니다. 그런데 이 구조를 제대로 사용하고 있는지, 역할을 제대로 분리하고 있는지 궁금합니다.

3. Java Enum

이번 미션에서 Java Enum을 어디에 적용하면 좋은지 궁금합니다 !
-> Enum에 관해 공부해보았을때 타입안정성 존재, 코드 자동완성 제공(IDE에서), 가독성 좋음, 메모리 효율성 좋음, 추가 기능 구현 가능정도의 장점을 갖고 있는 것으로 알고 있습니다. 현재 제가 작성한 코드에서는 사용자가 결과값을 입력할 수 있기도하고, 이름의 수와 결과의 개수가 매번 달라질 수 있기 때문에, enum을 사용하지 않았습니다. 미션의 요구사항에 Enum을 적용하라는 내용이 있는 만큼 어디에 적용하면 좋을지 궁금해서 여쭤봅니다 !

4. 테스트 코드

이번 미션 같은 경우 테스트 코드를 Domain의 값 검증, 그리고 ladder game 두가지로 나누어 코드를 작성했습니다. 본래 domain, view, controller 내의 모든 클래스마다 각각 테스트 파일을 만들어 코드를 작성해야하나 고민했으나, 파일이 너무 많아질 것 같기도하고, 테스트의 내용이 Height나 Result, Name의 경우 너무 짧을 것 같아 두개의 파일로 작성했습니다. 모든 클래스별로 테스트 파일을 만드는것이 좋은지, 혹은 지금처럼 domain별로, view 별로 작성하는 것이 좋은지 궁금합니다 !

감사합니다 :-)

@heejung72 heejung72 closed this May 24, 2025
@heejung72 heejung72 reopened this May 25, 2025
@heejung72
Copy link
Author

heejung72 commented May 27, 2025

<리팩토링 1>

  • 시험기간이라 과제가 점점 많아져 시간이 부족하기도 하고, 장순님과 예진님 PR 내용이 많아서, 끊어서 리뷰를 올리면 좋을 것 같아 여러차례에 나누어 올리려고 합니다 ! 우선, 값 검증과 ident, 도메인-뷰 의존성관련에 대해 수정한 부분 업로드 합니다.

<장순님 PR>

  • 사람 이름이 all일 수 없다는 제약이 없는데, all로 이름을 입력하면 어떻게 되는지?
    • 저도 이 부분에 대해서 생각하지 못한 부분이라 고민을 오랫동안 하였는데요, if (value.equals("all")){ throw new IllegalArgumentException("\"all\"은 참가자 이름으로 사용할 수 없습니다.");}로 코드를 리팩토링 하였습니다. 만약에, 제약을 가하여 사람 이름이 all일 경우고, all을 입력하게 되면 all의 result 값을 볼 수 있겠으나, 전체 name에 대한 result 값은 볼 수 없을 것 이라 이렇게 수정하였습니다. 그리고 InputView의 readNames에 참여할 사람의 이름은 all은 불가하다고 작성하였습니다. 이렇게 하는 방법이 적절한지 궁금합니다.
    • 확인해보니 장순님도 리팩토링을 동일한 방법으로 작성하셨네요 !

<공통>

  • LadderController의 processQuery부분 ident가 2여서 수정하였습니다. 꼼꼼하게 본다고했는데, 실수가 반복되네요 …
    • handleQuery를 따로 만들어서 ident를 1로 만들었습니다.
  • 공통적으로 PR 남겨주신 부분이 도메인 - 뷰 의존성 분리에 관한 이야기라서 예진님께 남겨주신 기준을 기억하고 저도 제가 작성한 코드를 다시 한 번 살펴보았습니다.
    • 이 클래스를 분리했을때, 분리 대상인 클래스와 새로 분리한 클래스 모두 각자의 책임, 즉 역할을 가지고 있는가?
    • 정말 클래스 분리가 필요할 정도로 기존 클래스가 여러 책임을 가지고 있는가?
    • 이 클래스가 분리되어 나왔을 때, 이 클래스로 위임되어야 하는 역할이 모두 옮겨져있는가?
      • Application : 메인 메서드에서 컨트롤러 생성하고 사다리 게임 시작.
      • Controller
        • LadderController : 사용자 입력 받기, 게임 실행, 결과 출력 등 사다리 게임의 전체 흐름을 제어 (입력과 뷰 사이의 controller 역할)
      • Domain
        • Name : 참가자 이름 하나하나를 표현하는 값 개체, 검증 진행
        • Names : 여러 이름의 집합. 문자열 파싱. 이름 크기 정보 + 출력 포맷팅 담당
        • Result : 게임 결과 하나하나를 표현하는 값 개체, 단순 결과 보관
        • Results : 여러 결과들의 집합 관리, 문자열 파싱. 결과 조회 + 출력 포맷팅 담당
        • Height : 사다리 높이 표현(캡슐화)
        • Line : 사다리 행 표현. 연결상태 관리, 이동로직 처리, 출력
        • Ladder : 전체 사다리 표현. Line 집합 관리. 최종 도착 위치 계산. 출력
        • LadderGenerator : 사다리 생성
        • LadderGame : 게임 결과 실행
      • View
        • InputView : 이름 결과 높이 조회할 이름 입력
        • OutputView : 사다리 전체, 개별 결과, 전체 결과 출력
  • 이렇게 제가 작성한 모든 클래스를 책임 에 초점을 맞추어 정리해보았습니다. 사실 제가 생각하기에는 잘 작성했다는 생각이 들어요… ! 그나마 조금 고민이 드는게 Height가 갖고 있는 책임이 너무 없다는 것과, Result를 Results로 합치는 것이 좋을 것 같다는 생각이 들어 수정하였습니다.
  • Height는 어디에 넣는 것이 좋을지 잘 모르겠어서, 어떻게 생각하시는 지 궁금합니다. 사다리를 생성하는 LadderGenerator가 더 적절한지, 아니면 위치를 계산하는 Ladder에 넣는 것이 더 좋은지 … 잘 모르겠습니다.

Copy link

@Ohzzi Ohzzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선 1차적으로 리뷰 드려봅니다 희정님!

MVC

MVC 구조는 일단 전체적인 구조 면에서는 컨트롤러의 조율을 통한 구조로 잘 만들어주신 것 같아요. 다만, MVC 구조가 근본적으로 하고자 하는 것이 무엇인지, 얻을 수 있는 장점이 무엇인지 좀 더 고민을 해보면 좋을 것 같아요. 지금은 약간 MVC 구조를 흉내낸 것에 불과하다는 느낌도 있어서, 깊은 고민을 해보면 아키텍처 설계에 많은 도움이 될거에요!

enum

제 생각에 이번 미션에는 enum을 적용할만한 곳은 크게 없어보여요!

테스트

안그래도 이 부분은 코드에 File Changes에 리뷰를 남기려다가 본문으로 가져오려고 한 부분인데요. 전체적으로 테스트 하나하나의 크기가 너무 크고 검증이 장황합니다. 우리가 프로덕션 코드도 클래스 하나, 메서드 하나가 하는 일이 너무 많으면 분리하잖아요? 테스트 코드도 코드인 만큼 같은 논리로 볼 수 있을 것 같아요. 테스트 코드 역시 유지보수를 해야 하고, 그렇기 때문에 가독성을 유지해야 합니다. 또한 테스트 코드는 문서화의 역할을 겸할 수도 있기 때문에 가독성이 더더욱 중요하죠.

특히나 이번 PR에서는 도메인 객체들이 print 메서드들을 모두 가지고 있는데, 이 부분과도 연결해서 생각해보면 좋을 것 같습니다. 힌트는, 저는 도메인 객체에 print가 있는 것은 무슨 일이 있어도 반대입니다.

저는 개인적으로 각 클래스 단위, 메서드, 분기 단위로 테스트를 나누는 것을 추천드립니다. (따로따로가 아니라 셋 모두를 적용해야 해요)

  • 클래스 단위: Ladder, Line이 있으면 LadderTest, LineTest
  • 메서드 단위: 존재하는 메서드들에 대한 테스트. 단, getter 테스트 등 무의미한 테스트는 불필요.
  • 분기 단위: 메서드 내부에서 분기가 존재한다면 해당 분기에 대한 경곗값을 모두 테스트

또한 한 테스트에서 여러 검증문이 있을 수는 있으나, 그것은 테스트하려는 기능을 호출했을 때 여러 상태가 변경되기 때문에 그것을 검증해야 하는 경우에 사용하는 것이지 한 테스트에 여러 기능을 테스트하기 위해 사용되는 것은 아닙니다. 테스트 하나는 하나의 기능만을 테스트해야 합니다. 만약 도메인에 대한 단위 테스트라면 도메인의 메서드 하나를 테스트 해야 하는 것이고, 전체 프로그램에 대한 통합 테스트를 한다면 전체 프로그램이 가진 기능 중 하나를 테스트 해야 하는 것이죠.

전체적으로 코드는 가독성 있게 잘 짜주셨기 때문에, 프로그램의 구조, 그리고 탄탄하고 가독성 좋은 테스트 라는 두 가지 주제를 바탕으로 리팩토링을 진행해보시면 좋을 것 같습니다!


public static Line generate(int width) {
List<Boolean> steps = new ArrayList<>();
Random random = new Random();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

랜덤을 굳이 매 번 생성할 필요는 없을 것 같아요!

Comment on lines +53 to +59
public void print() {
System.out.print(" ");
for (Boolean step : steps) {
System.out.print(step ? "|-----" : "| ");
}
System.out.println("|");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

출력의 책임은 Line일까요? 만약 게임의 규칙은 유지한 채, 출력 수단이 콘솔에서 웹으로 바뀐다면 도메인도 변경되어야 할까요?

Comment on lines +3 to +13
public class Height {
private final int value;

public Height(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

height는 그냥 int 값으로 존재해도 괜찮았을 것 같군요(어차피 width가 있는 것도 아니어서)
의미 있는 값이 되려면 value > 0 검증 정도라도 했어야 할 것 같은데, 이 상황에서는 클래스 자체가 없어도 됐을 것 같긴 해요.

애초에 List의 사이즈가 곧 height일테니까요!

public class LadderGenerator {
public static Ladder generate(int countOfPlayers, Height height) {
List<Line> lines = new ArrayList<>();
for (int i = 0; i < height.getValue(); i++) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기를 보니까 결국 height가 굳이 클래스일 필요가 없는 것이 증명되긴 했군요

throw new IllegalArgumentException("이름은 5글자 이하여야 합니다.");
}
if (value.equals("all")) {
throw new IllegalArgumentException("\"all\"은 참가자 이름으로 사용할 수 없습니다.");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escape 👍

Comment on lines +31 to +41
public static class Result {
private final String value;

public Result(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웬만하면 별도의 파일을 통해 클래스를 만드는 것을 추천드려요! 내부 클래스보다는 그게 더 가시성이 좋은 편입니다!

import domain.Names;
import java.util.Map;
public class OutputView {
public void printLadder(Names names, Ladder ladder, Results results) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

도메인이 뷰로 넘어가게 되면 어떤 문제가 있을까요?

Comment on lines +21 to +24
assertDoesNotThrow(() -> new Name("A"));
assertDoesNotThrow(() -> new Name("12345"));
assertDoesNotThrow(() -> new Name("가나다라마"));
assertDoesNotThrow(() -> new Name(" A "));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

과거 미션 요구사항에 테스트는 assertj의 메서드를 사용하는 것이 있었던 것 같은데요! 혹시 assertj보다 jupiter의 assertion이 더 편하다고 느끼시나요?

}

public Results.Result play(Name name) {
int index = names.getValues().indexOf(name);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디미터의 법칙을 위배하지 않는 방법을 생각해볼까요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants