Skip to content
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

[사다리 타기] 김우진 미션 제출합니다. #8

Open
wants to merge 24 commits into
base: woogym
Choose a base branch
from

Conversation

woogym
Copy link

@woogym woogym commented May 12, 2024

어려웠던 점

  • TDD 구현이 막막해서 필수적인 모델만 진행하고 구현을 시작했습니다.
  • 테스트 코드와 구현에 신경을 많이 써서 그런지 클린코드를 많이 놓쳤습니다
  • TDD가 너무 어려웠습니다..
  • MVC패턴에 있어서 최대한 view, controller, model의 흐름을 맞추는데 신경을 많이 썼던거 같습니다 아직 부족한 점이 분명히 보여서
    이 부분에 있어서 피드백이 궁금합니다!

고민했던 부분 / 질문할 부분

  • 사다리를 그리는데 StringBuilder를 채용했습니다
    하지만 StringBuilder가 멀티스레드 환경에서 사용하기에 그리 적합한 객체가 아니라고 하더라구요
    그럼에도 StringBuilder말고는 사용할 방법을 찾지 못해서 고민하다가 결국은 StringBuilder를 채용했습니다
  • 사다리 라인을 그리는 프로세스를 model에게 위임했습니다 하지만 계속 고민이 들었던 부분은
    view에서 처리를 해야할거 같은 생각이 계속 들었음에도 기능을 옮기는데 어려움이 있어서 이전은 하지 않았습니다.

inhooo00 and others added 3 commits May 7, 2024 23:12
- 입력, 출력, 기능으로 분류하여 과제를 진행합니다.
- 테스트 코드를 작성합니다.
- 각종 기능 구현 -> 이사 필요
- StringBuiler 부분 view로 이동 필요
- Line 랜덤 값 Test 코드 작성
- Test 코드를 위한 FootholdGenerator 생성
Copy link

@hangillee hangillee left a comment

Choose a reason for hiding this comment

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

안녕하세요, 우진! 👋
중간 고사 끝나고 바로 미션하느라 고생 많았습니다!

1. TDD 왜 이렇게 어렵나요?
이번 미션의 핵심 주제가 TDD였고, 생소한 개념이었기 때문에 많이 어려웠으리라 생각합니다. 저도 가끔은 TDD를 통해 개발하지 못하기도 합니다. 어려웠다고 해서 위축될 필요는 없습니다. 😁 처음에는 무엇부터 개발해야할지 몰라서 TDD를 실천하기 힘든데요, 그래서 Top-Down 방식과 Bottom-Up 방식을 적절하게 혼용하곤 합니다.

Top-Down 방식은 도메인 지식이 거의 없을 때, 사용자와 가장 가까운 부분 부터 차근차근 구현해나가는 방식이며, Bottom-Up 방식은 도메인 지식이 풍부하거나 잘 설계 되었을 때, 도메인부터 구현하는 방식입니다. 정답은 없으며, 우진이 편한 방식으로 개발하면 됩니다!

2. MVC 패턴의 흐름
크게 문제 될 만한 부분은 보이지 않으나, 한가지 걸리는게 있다면, model 패키지의 LadderSymbol 클래스는 View에서 관리하는게 더 나은 것 같아 보입니다. MVC 패턴을 활용한 이유는 결국 패키지 의존성을 포함한 객체 의존관계의 방향에 통일성을 주기 위함이기도 합니다.

각 계층이 적절하고 완벽하게 분리 되었는지 한번 고민 해봅시다!

3. 사다리 모양 그리기
2번과 마찬가지일 수 있는데, 우진의 생각대로 Model에서부터 View에 보여주기 위한 모양을 그리는 것은 적절하지 않은 것 같습니다. StringBuilder를 활용한 것 자체는 문제가 되지 않지만, 해당 객체를 필드로 두거나, 모델에서부터 생성해야 했는지는 조금 의문이 드네요..

충분히 앞 계층으로 분리할 수 있을 것 같습니다. Boolean 리스트를 활용한다거나, 발판 정보를 담은 컬렉션을 컨트롤러에서 변환한다던지 하는 방식으로요.

리팩토링 파이팅입니다!

docs/README.md Outdated
Comment on lines 1 to 50
# 구현할 기능 목록

## 입력
- [x] 사용자 이름 입력
- [ ] 쉼표로 구분
- [ ] 최대 5글자까지 가능
- [x] 사다리 높이 입력
- [ ] 자연수


## 출력
- [x] 실행 결과
- [ ] 첫번째 줄 : 사람 이름
- [ ] 그 아래 줄 : 사다리 그리기

## 기능
- [ ] 사람의 이름을 받고 , 기준으로 나누기.
- [ ] 한 줄에 | 개수를 사람 이름 개수만큼 반환.
- [ ] 랜덤 값 기능 구현
- [ ] 랜덤에 충족하면 ----- 그리기.
- [ ] 한줄의 |에 ----- 연결선이 동시에 나오지 않게
- [ ] 테스트 코드 작성


## TDD 객체 명세서
- [x] Height
- [x] 객체 생성 가능
- [ ] Ladder
- [x] 객체 생성 가능
- [ ] 전체적인 값이 맞는지 확인
- [ ] Line
- [x] 객체 생성 가능
- [ ] 예상되는 사다리 라인 문자열
- [x] Player
- [x] 객체 생성 가능
- [x] Players
- [x] 객체 생성 가능
- [x] util
- [x] LadderHeightValidator
- [x] 정상_입력
- [x] 정수가_아닌_입력
- [x] 음수_입력
- [x] 공백_입력
- [x] PlayerNamesValidator
- [x] 정상_입력
- [x] 공백_포함
- [x] 공백
- [x] 이름_길이_초과
- [x] 중복_입력
# 1차 리뷰 후 개선 방향

Choose a reason for hiding this comment

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

구현 기능 목록 👍

그런데, 몇몇 기능은 완성 못하신 건가요?

}

private Players createPlayersFromInput() throws IOException {
String[] playerNamesArray = inputView.readPlayerNames().split(",");

Choose a reason for hiding this comment

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

split까지 InputView에서 해주면 어떨까요?

View의 요구 사항이라고 볼 수 있을 것 같습니다.

Comment on lines +5 to +9
public static class DuplicatedPlayerNameException extends IllegalArgumentException {
public DuplicatedPlayerNameException() {
super(ErrorMessage.DUPLICATED_CAR_NAME.getMessage());
}
}

Choose a reason for hiding this comment

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

Custom Exception을 구현하셨네요! Nested Class로 구현하신 특별한 이유가 있으실까요?

IllegalArgumentException으로 충분히 처리 가능한 것 같은데 Custom Exception을 구현한 이유는 무엇인가요?

private Line line;

public Ladder() {
this.lines = new StringBuilder();

Choose a reason for hiding this comment

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

StringBuilder를 생성자를 통해 필드에 주입할 이유가 있었나요?
makeLinesgenerateLadder 같은 이름으로 바꾸고 바로 return 해주는 편이 낫지 않았을까요?

클래스의 필드가 늘어났다는 것은 그만큼 의존하는 객체가 늘었다는 의미입니다!

public StringBuilder getLines() {
return lines;
}
}

Choose a reason for hiding this comment

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

EOL!


@Test
void makeLineWithNotFoothold() {
TestFootholdGenerator generator = new TestFootholdGenerator(false);

Choose a reason for hiding this comment

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

테스트용 Fake 객체를 활용한 테스트 더블!

테스트 더블에 대해 학습하시고 사용하신 건지 직접 떠올린 아이디어로 작성하신 건지는 모르겠으나,
매우 훌륭한 랜덤한 조건에 대한 테스트 방법입니다!

Comment on lines 25 to 34
@Test
@DisplayName("makeLines 메소드가 정상적으로 작동하는지 테스트 합니다.")
void Test_MakeLines_Operation() {

ladder.makeLines(height, players);

String[] lines = ladder.getLines().toString().split("\n");

assertEquals(5, lines.length);
}

Choose a reason for hiding this comment

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

스크린샷 2024-05-18 오후 6 42 39

미션 제출 전에 모든 테스트가 통과하는지 꼭 확인해주세요!

Comment on lines 12 to 33
@Test
@DisplayName("리스트를 사용해 정상적으로 Players 객체를 생성하고, 값이 같은지 확인")
void Players_Object_Create_Success_Test() {
// given
List<String> playerNames = new ArrayList<>();

playerNames.add("Player1");
playerNames.add("Player2");
playerNames.add("Player3");

// when
Players players = new Players(playerNames);

// then
assertThat(players.getPlayers()).hasSize(playerNames.size());

List<String> extractedNames = players.getPlayers().stream()
.map(Player::getName)
.toList();

assertThat(extractedNames).containsExactlyElementsOf(playerNames);
}

Choose a reason for hiding this comment

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

Given-When-Then 활용 👍

@Test
void ladderHeightValidator_정수가_아닌_입력() {
String ladderHeight = "abc";
assertThrows(LadderGameValidationException.NotNumericException.class, () -> LadderHeightValidator.validateLadderHeightNumberIsCorrect(ladderHeight));

Choose a reason for hiding this comment

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

Java 컨벤션에는 한 줄에 최대 글자 제한도 있습니다.

적절하게 개행해볼까요?

Comment on lines +11 to +15
@Test
void playerNameValidator_정상_입력() {
String[] playerNames = {"pobi", "honux", "crong", "jk"};
assertDoesNotThrow(() -> PlayerValidator.validatePlayerNameIsCorrect(playerNames));
}

Choose a reason for hiding this comment

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

다른 테스트들 처럼 @DisplayName을 활용해보는 건 어떨까요?

코드의 통일성이 다소 떨어져 보입니다. 서로 다른 드라이버가 작성한 코드라고 할지라도
통일된 컨벤션을 통해 유지보수성을 높이는 것이 훨씬 좋습니다.

@woogym
Copy link
Author

woogym commented May 20, 2024


  1. README.md 수정했습니다!
  2. 저 역시도 페어와 view와 controller중 어느 계층에서 split하는게 맞을지에 대해서 고민했습니다.
    페어와 고민하면서 저는 입력을 받는 행위 자체에 책임만 가지고 있으면 된다고 생각했기에
    split하는 역할을 controller에게 주고 model에게 넘겨주는게 올바른 flow라고 생각했었습니다
    코드를 작성하면서도 헷갈렸기에 리팩토링하면서 다시 생각해보는 계기가 되었습니다
    해당 부분 수정했습니다!
  3. CustomException을 Nested Class로 구현한 이유
    클린코드, 코드의 유지보수성에 관심을 가지다가 Nested Class라는것을 알게 되었고
    캡슐화도 직관적으로 지켜낼수 있고 무엇보다 가독성을 높일 수 있음에
    저번 과제부터 쭉 반영해오고 있었습니다 하지만 남용은 항상 지양해야하듯이
    nested class를 남용하면 역으로 코드의 가독성을 해칠수 있다는 점을 간과하지 않는 선에서
    활용해보고 싶어서 사용했습니다.
  4. StringBuilder를 생성자를 통해 필드에 주입할 이유
    구현이 어려워서 구현에 초점을 맞추다보니 이런 점을 놓친것 같습니다
    생각해보니 StringBuilder를 메소드 내부에서 선언해서 불필요한 필드 주입을 줄이고 해당 메소드만
    실행될때 생성되는것이 자원관리 측면에서 더욱더 괜찮은 선택지임을 알았습니다
    해당 부분 수정했습니다!
  5. EOL
    코드를 이것저것 수정하면서 작성하다보니 까먹은거 같습니다
    수정했습니다!
  6. 출력과 관련된 내용은 View 패키지에 있는 객체로 처리했습니다
    또한 사다리를 그리는 부분을 OutputView의 책임으로 전환하려고 대대적인 코드 수정이 있었습니다
    라인의 연결 유무를 Boolean타입으로 관리해서 사다리를 그리는 부분을 outputView의 책임으로 이동시켰습니다 (정말 골통분쇄 당하는줄 알았습니다 ㅠㅠ)
  7. 예외처리 부분 indent 수정했습니다
    stream()을 사용량을 늘리고 싶은데 아직은 stream보다는 기존에 사용하던 방법들이 더 익숙해서
    바로바로 떠오르지 않았던 거 같습니다
  8. 불필요한 공백 수정했습니다!
  9. 테스트 더블을 따로 학습하지는 않았고 그 전 미션을 진행하면서 페어와 테스트 코드를 작성할때
    고안해낸 방법을 한 번 더 적용해봤습니다
  10. 개행해서 코드 수정했습니다!
  11. 스네이크 케이스.. 그전에 한글로 테스트 메소드명을 작성할때 스네이크 케이스로 작성했었는데
    영문으로 테스트 코드의 메소드 이름을 네이밍할때 그 습관이 그대로 나온 것 같습니다
    카멜케이스로 수정했습니다

스크린샷 2024-05-21 오전 1 16 54

@woogym woogym requested a review from hangillee May 20, 2024 16:17
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.

3 participants