Skip to content

Conversation

@tae-wooo
Copy link

@tae-wooo tae-wooo commented Oct 30, 2025

안녕하세요, 루카님!
이번 미션도 잘 부탁드립니다 😊

궁금한 점

참여자와 실행 결과를 입력받을 때, 문자열에서 공백과 쉼표를 제거한 뒤 저장해야 하는 상황이 있었습니다.
그래서 처음에는 이 공백/쉼표 제거 기능을 재사용하기 위해 상속 으로 구현하려고 했지만,
두 클래스(Players, Results)는 의미적으로 너무 다른 역할을 가지기 때문에 상속이 적절하지 않다고 판단했습니다.

이후 조합 개념을 사용해 Names 클래스로 공통 기능을 분리하여 재사용할 수 있었지만,
다른 클래스에서 값을 가져올 때 players.getPlayers().getValues()처럼 접근 경로가 길어지는 구조가 생겼습니다.

이럴 경우 다음 중 어떤 방향이 더 바람직한 설계인지 고민되었습니다.

  1. 조합 을 유지하여 코드 재사용성과 중복 제거를 우선시하는 방식
  2. 각 클래스별로 기능을 직접 구현하여 이해하기 쉬운 구조를 유지하는 방식

루카님께서는 이런 상황에서 어떤 접근이 더 적절하다고 생각하시는지 궁금합니다.

또한, 원시값 포장을 적극적으로 적용하다 보니 .연산자가 여러번 연결되는 구조가 많아졌습니다.
이런 형택가 제 3자가 코드를 읽을 때 오히려 이해를 어렵게 만들 가능성이 있는지도 여쭤보고 싶습니다.

@tae-wooo tae-wooo changed the base branch from main to tae-wooo October 30, 2025 08:57
taewoo added 26 commits October 31, 2025 12:54
Copy link

@dooboocookie dooboocookie left a comment

Choose a reason for hiding this comment

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

안녕하세요, 태우.
리뷰어 루카입니다.

처음 제출 후 수정 요청을 드렸는데, 빨리 반영해주셔서 감사합니다.
덕분에 리뷰를 남기기 수월했습니다.

리뷰 스타일 같은 것은 이전에 리뷰를 나눈 경험이 있으니 생략하겠습니다.

🎯 리뷰 포인트

  1. Name은 중복인가?
  2. 어떤 개념을 객체로 정의할 것인가?
  3. enum 활용

이번에도, 리뷰 주제는 많이 드리지 않았습니다.
다만 저희가 지금 연습하는 객체지향 프로그래밍에서 본질에 가까운 개념들이라고 생각하는데요.
깊게 고민해보시고 태우만의 답을 들려주세요.

이번에 리뷰 전 다시 제출을 요청한 이유는 여러가지가 있는데,
저는 미션 제출 > 리뷰 과정이 제출에 의미가 있다고 생각하지 않아요.
애초에 점수를 부여하기엔 모호한 개념을 실습해보고 있는 것이기도 하구요.
리뷰를 주고 받는 과정에서 태우가 성장 목표를 지정하는 것이 필요합니다.
코드가 멋있지 않더라도, 제출이 늦더라도 괜찮습니다.
좋은 코드리뷰 나눠보아요.

바쁜 와중에 미션하느라 고생하십니다.
조금 더 파이팅해보시죠.

Comment on lines 3 to 34
public class Name {
private final String value;

public Name(String value) {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException("비어 있을 수 없습니다.");
}
this.value = value.trim();
}

public String value() {
return value;
}

@Override
public String toString() {
return value;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Name)) return false;
Name name = (Name) obj;
return value.equals(name.value);
}

@Override
public int hashCode() {
return value.hashCode();
}
}

Choose a reason for hiding this comment

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

🔥 Request Change

'Name'은 구체적인 개념일까요? 추상적인 개념일까요?
관점에 차이가 있을 수 있을 것 같아요.

이 사다리 게임에서 2가지 의미로 사용이 되는데요.
'참여자의 이름' 과 '결과의 이름'을 표현하는데 사용되고 있습니다.
두가지 사이에는 비슷한 개념도 있고 다른 개념도 존재합니다.

이름이기 때문에 한글자 이상 문자열 값을 갖는다는 것이 대표적인 비슷한 점이겠네요.
반면,
'참여자의 이름'은 5글자 이상일 수 없고, Unique한 값이기 때문에 같은 이름은 같은 존재로 판단할 수 있다.
'결과의 이름'은 글자 제한이 없고, Unique한 값이 아니기 때문에 같은 이름이라도 같은 존재라고 판단할 수 없다.
이런 분명한 개념적 차이가 존재합니다.
따라서 저는 이 Name이라는 객체가 꽤나 추상적인 개념으로 사용되고 있다고 생각됩니다.

객체지향적인 설계를 하다보면, 중복을 줄이고 생각이 들 수 밖에 없는데요.
그래야 책임 분리도 확실해지고 유지보수도 용이한 코드가될테니까요.

이를 진짜 중복으로 볼것인지 아닌지는 조금 더 고민해볼 필요가 있을 것 같아요.

저는 Name 래핑 클래스가 java에서 제공하는 String을 대체할만큼 가치가 있게 느껴지진 않습니다.
그리고 이정도 추상화된 개념이라면 앞으로 역할 및 책임이 부여되기도 힘들어보이구요.

그래서 저는 개인적으로 두 개념을 따로 일급컬렉션으로 만들어서 조금 더 응집도를 높여보셨으면 좋겠습니다.
혹시나 다른 의견이 있으시면 편하게 말씀해주세요.

[참고자료: 우발적 중복(greengööse의 블로그)]

Copy link
Author

Choose a reason for hiding this comment

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

단순히 String 을 감싸는 Name / 그리고 그걸 리스트로 감싸는 Names 구조로 사용했습니다.
그리고 Players, Results 에서 이를 재사용하면 중복을 줄일 수 있을 거라고 생각했습니다.

하지만 링크에서 말하는 우발적 중복 개념을 보고 다시 생각해보니,
저는 서로 다른 개념을 같은 것으로 간주해버린 상태였던 것 같습니다.
PlayerName 과 ResultName 은 “둘 다 이름이다” 라는 표면적 공통점만 있을 뿐
제약 조건과 책임은 전혀 다르다는 것을 뒤늦게 인지했습니다.

그래서 Name/Names 로 묶어둔 것은 같은 개념이 아니라
“다른 개념을 하나처럼 써버린 중복”이었고, 우발적 중복으로 봐야 했다는 것을 깨달았습니다.

그래서 PlayerName / ResultName 으로 각각 구체적인 VO로 분리했고,
각 도메인이 자신의 제약을 스스로 갖도록 하는 방향으로 수정했습니다.

좋은 관점 제시 감사합니다 🙇‍♂️

Comment on lines 9 to 16
public Names(String input) {
List<String> tokens = List.of(input.split(","));
List<Name> temp = new ArrayList<>();
for (String token : tokens) {
addIfValid(temp, token);
}
this.values = List.copyOf(temp);
}

Choose a reason for hiding this comment

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

🔥 Request Change

Name에 이어서 Names라는 추상적인 개념이 등장했네요.
Name에 대해 고민하다보면 이 일급컬렉션도 변경될 가능성이 크다고 생각이 듭니다.
근데 그와 별개로 이 부분에서 꼭 리뷰를 드리고 싶은 부분이 있습니다.

이 생성자에는 아래와 같이 쉼표(,)로 구분되어있는 String을 List<Name으로 변환합니다.

"태우, 루카, 승용, 수민" -> ["태우", "루카", "승용", "수민"]

이 사다리게임이 매우 흥행해서, 여러 View로 서비스를 제공해야하는 상황을 상상해보죠.

Image

웹형태의 View에서는 각 input 태그에 존재하는 곳에 사용자 이름을 입력합니다.
그때도 이 생성자가 유효한 역할을 할까요?

저는 이 생성 로직은 console view 어플리케이션을 만들기 때문에 존재하는 것이라고 생각합니다.

모델과 뷰의 의존성을 끊기 위해 (뷰의 변화가 모델에 영향을 끼치지 않기 위해) MVC 패턴을 사용하고 있는데, domain 영역의 객체가 뷰가 바뀔 때 마다 코드 수정을 해야한다면, MVC패턴을 사용하는 의미가 많이 없어지겠네요.

Name을 리팩터링하면서 바뀔 가능성이 매우 크지만, 위 개념도 꼭 유의하며 코드수정해보시면 좋겠습니다.

Copy link
Author

Choose a reason for hiding this comment

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

말씀해주신 부분을 보고 크게 공감했습니다.

기존에는 String 입력을 split 하여 바로 Domain 객체(Name/Names)를 생성하는 방식이었는데,
이 방식은 Console View의 입력 형태(String 쉼표 기반)에 domain이 직접 의존하고 있다는 문제를 가지고 있었습니다.
즉, View가 바뀌면 Domain 생성 로직도 함께 변경될 수 있는 구조였습니다.

그래서 이번 리팩터링에서는 Console에 종속된 parsing 로직을 제거하고,
domain 생성자는 List<PlayerName> / List<ResultName> 처럼
이미 가공되어 유효한 상태의 데이터를 직접 받도록 변경했습니다.

이렇게 변경함으로써 Domain은 "사용자 입력 처리" 책임을 가지지 않고
오직 "도메인 규칙"만 표현하도록 응집도를 높일 수 있었고
MVC 패턴 관점에서 View 변화와 Domain 변화가 분리되는 구조가 되었습니다.

Comment on lines 49 to 51
public Names getPlayers() {
return players;
}

Choose a reason for hiding this comment

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

🔥 Request Change

Names 생성자에서 리스트를 List.copyOf(...)를 사용해서 반환한 경우가 있었는데요.
여기서는 그냥 객체 그자체를 반환하고 있네요.

혹시 두 반환에 대해서 차이를 둔 이유가 있으실까요?

[참고자료: Collection 복사 (성장하는 성하 Blog)]

Copy link
Author

Choose a reason for hiding this comment

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

Names 내부에서 copyOf가 적용되어 있기 때문에 Players도 방어된 상태라고 생각했습니다.
하지만 공유해주신 자료(방어적 복사, 얕은 복사, 깊은 복사)를 참고하면서 copyOf는 얕은 복사에 해당하고, 이는 Names 내부의 리스트만 불변으로 만들 뿐 Players의 최종 상태까지 보장하지 않는다는 점을 이해했습니다.

리뷰에서 말씀주신 것처럼 Names는 도메인에서 의미가 다소 추상적인 개념이라고 판단하여 제거하였고,
Players / Results에서도 생성자에서 직접 List.copyOf(...)를 적용하여
도메인 객체 생성 시점에서 불변을 보장하도록 리팩터링하였습니다.

Comment on lines 7 to 13
public Height(int height) {
if (height < HEIGHT_MIN) {
throw new IllegalArgumentException("사다리 높이는 양수여야 합니다.");
}

this.height = height;
}

Choose a reason for hiding this comment

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

👀 Comment

위에 남긴 리뷰들과 조금은 비슷한 맥라의 리뷰일 수 있겠는데요.
높이라는 입력값을 표현할 객체가 필요할까?라는 생각이 좀 들었어요.

객체나 메서드의 책임범위를 점점 좁혀가는 것은 클린한 객체지향 코드를 작성하는데 매우 중요한 태도중 하나입니다.
하지만 객체가 많아진다는 것은 그만큼 관리포인트가 늘어나는 것이기도합니다.

이렇게 미션에서 거의 원칙과 같이 제시하는 방향성에도 장단점이 존재하는 것이죠.

생성 로직에서 텍스트로 보이는 것과 같이 사다리이기 때문에 0보다는 커야합니다.
이 책임은 Ladder가 갖기에도 충분한 가치 아닐까합니다.

아예 입력값을 검증하고 싶은 것이라면, inputView가 가져야할 책임일 수도 있겠구요.

Copy link
Author

Choose a reason for hiding this comment

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

처음에는 문제 조건에서 원시값 포장 / 일급컬렉션을 적극적으로 활용하라는 가이드가 있어 Height도 VO로 분리했었습니다.
하지만 리뷰 내용처럼 ‘높이’라는 값은 별도 도메인 규칙이나 확장 여지가 거의 없어서, 이 경우에는 오히려 관리 포인트만 늘리고 있다고 판단했습니다.

따라서 Height 객체는 제거하고 입력값 검증은 컨트롤러에서 처리하는 방향으로 진행했습니다.
컨트롤러는 사용자 입력 → 도메인으로 넘겨주기 전의 최종 validation/파싱 책임을 가져가는 레이어라고 생각하고 있으며, 현재는 컨트롤러에서 height 검증과 예외 처리 흐름(while/retry)까지 담당하도록 구성했습니다.

혹시 이 부분을 InputView로 위임하는 방식이 더 적절한 케이스라면 조언 부탁드립니다. 저는 지금 단계에서는 Controller가 해당 책임을 가져가는 방향이 더 자연스럽다고 판단했습니다.

Comment on lines 3 to 14
public class Point {
private final Connect point;

public Point(Connect connect) {
this.point = connect;
}

public Connect point(){
return point;
}

}

Choose a reason for hiding this comment

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

👀 Comment

이 Point라는 애도 어떤 개념인지 잘 모르겠습니다.

Copy link
Author

@tae-wooo tae-wooo Nov 3, 2025

Choose a reason for hiding this comment

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

Point 객체를 만든 이유는 Line 내부에서 일급컬렉션을 적용하기 위해 enum을 바로 리스트에 담는 것이 불가능하다고 생각했기 때문이었습니다. 하지만 enum도 타입이기 때문에 List 형태로 바로 사용할 수 있다는 점을 다시 확인했고, 결국 Point는 단순 포장 이상의 도메인 의미를 가지고 있지 않다고 생각합니다.
따라서 Point는 제거하고 Line은 List로 단순화하여 도메인이 더 명확해지도록 리팩터링 진행하겠습니다.

Comment on lines 34 to 42
public Connect validateMoveRight(int index) {
if (index >= points.size()) return Connect.DISCONNECTED;
return points.get(index).point();
}

public Connect validateMoveLeft(int index) {
if (index == 0) return Connect.DISCONNECTED;
return points.get(index - 1).point();
}

Choose a reason for hiding this comment

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

🔥 Request Change

태우도 위에서 사용했듯이, validate는 관습적으로 상태 검증해서 예외를 발생시킬 때 많이 사용하는 메서드명입니다.

조금 더, n번째 point에서 왼쪽으로 갈 수 있는지 오른쪽으로 갈 수 있는지 물어보는 듯한 네이밍으로 갈 수 있으면 좋겠어요.


여기서부턴 루카의 참견입니다.

여기서 조금 힌트를 얻으셨으면 하는 것은
Line에게 물어보는 방식도 있을 수 있겠죠?
예를 들면,

"나 지금 N번째야" 라고 Line에게 물어봄 -> 라인 "너는 N-1로 가"

이렇게요.😁

Copy link
Author

Choose a reason for hiding this comment

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

말씀해주신 네이밍 방향 참고하여
validateMoveRight / validateMoveLeft 는 각각 rightOf, leftOf 로 변경해보았습니다.
"어느 방향으로 갈 수 있는지" 판단 역할만 가지는 의미가 훨씬 더 자연스럽게 맞는 것 같습니다.

Comment on lines 3 to 20
public enum Connect {
CONNECTED(true),
DISCONNECTED(false);

private final boolean value;

Connect(boolean value){
this.value = value;
}

public boolean isConnected(){
return value;
}

public static Connect from(boolean value){
return value ? CONNECTED : DISCONNECTED;
}
}

Choose a reason for hiding this comment

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

👀 Comment

사실 이번 PR에서 가장 신경이 쓰이는 부분은 이쪽인데요.
enum은 여러 프로그래밍 언어에서 제공하는 객체 정의 방식인데요.
이를 이용해서 java에서는 정말 기발한 방법으로 좋은 코드를 짤 수 있습니다.
단지 true false 밸류를 저장하기엔 너무 아까워요!
사실 어떤 리뷰를 드려야될지 모르겠어서 이렇게 아쉬움만 표현했는데요.

NEXT STEP 학습 테스트를 진행해보셨다면, 아마 다른 코멘트를 반영하시다보면 아이디어가 떠오를 것이라 생각합니다.
꼭 생각나지 않으셔도 좋습니다. 다음 코멘트 때 제가 제안을 드려볼게요.

Copy link
Author

Choose a reason for hiding this comment

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

말씀 주신 힌트 기반으로 방향성을 잡아봤습니다!

"나 지금 N번째야" 라고 Line에게 물어보면 → Line이 이동해야 할 방향을 판단하고,
실제 이동은 각 상태(enum)가 담당하도록 책임을 분리했습니다.

이렇게 나누니 Line은 판단만 / Connect는 실제 이동만 담당하게 되어
역할이 더 명확해지고 유지보수도 쉬워진 것 같습니다.

제가 선택한 이 방향도 충분히 올바른 해결 방식 중 하나일까요?
다음 리뷰 때 추가로 조언 주시면 감사하겠습니다

Copy link

@dooboocookie dooboocookie left a comment

Choose a reason for hiding this comment

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

안녕하세요, 태우.
리뷰어 루카입니다.

개인적으로 바쁜일이 있어서 리뷰가 너무 늦었네요.

아마 다른 미션이 시작된 것 같아서 고민하신 내용에 코멘트 드리고 리뷰 이만 종료하도록 하겠습니다.
이번에 많은 코멘트를 주고받진 못했지만 정말 중요한 내용을 다뤘다고 생각해요.
추가로 단 코멘트도 꼭 확인해보시면 좋겠습니다.

부디 저와 나눈 리뷰가 성장에 도움이 되는 과정이었길 바랍니다.
추가적으로 이해가 안되거나 더 나누고싶은 대화가 있다면 편하게 말씀해주세요.

Comment on lines 1 to 44
package domain;

public enum Connect {
CONNECTED(true),
DISCONNECTED(false);
CONNECTED(true) {
@Override
public int moveRight(int index) {
return index + 1;
}

@Override
public int moveLeft(int index) {
return index - 1;
}
},
DISCONNECTED(false) {
@Override
public int moveRight(int index) {
return index;
}

@Override
public int moveLeft(int index) {
return index;
}
};

private final boolean value;

Connect(boolean value){
Connect(boolean value) {
this.value = value;
}

public boolean isConnected(){
public abstract int moveRight(int index);

public abstract int moveLeft(int index);

public boolean isConnected() {
return value;
}

public static Connect from(boolean value){
public static Connect from(boolean value) {
return value ? CONNECTED : DISCONNECTED;
}
}

Choose a reason for hiding this comment

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

👍 Approve

좋은 리팩터링 방향입니다!
Connect이 직접 판단을 하는 것이 응집도가 훨씬 높아보입니다.

enums의 init 블럭을 이용해서 추상 메서드를 직접 override 하는 방식으로
상태에 따른 동작을 하도록 구현하였군요.
객체지향적인 방법이라고 생각합니다. 굿굿

Suggested change
package domain;
public enum Connect {
CONNECTED(true),
DISCONNECTED(false);
CONNECTED(true) {
@Override
public int moveRight(int index) {
return index + 1;
}
@Override
public int moveLeft(int index) {
return index - 1;
}
},
DISCONNECTED(false) {
@Override
public int moveRight(int index) {
return index;
}
@Override
public int moveLeft(int index) {
return index;
}
};
private final boolean value;
Connect(boolean value){
Connect(boolean value) {
this.value = value;
}
public boolean isConnected(){
public abstract int moveRight(int index);
public abstract int moveLeft(int index);
public boolean isConnected() {
return value;
}
public static Connect from(boolean value){
public static Connect from(boolean value) {
return value ? CONNECTED : DISCONNECTED;
}
}
package domain;
import java.util.function.IntUnaryOperator;
public enum Connect {
CONNECTED(
index -> index + 1, // moveRight
index -> index - 1 // moveLeft
),
DISCONNECTED(
index -> index, // moveRight
index -> index // moveLeft
);
private final IntUnaryOperator moveRight;
private final IntUnaryOperator moveLeft;
Connect(IntUnaryOperator moveRight, IntUnaryOperator moveLeft) {
this.moveRight = moveRight;
this.moveLeft = moveLeft;
}
public int moveRight(int index) {
return moveRight.applyAsInt(index);
}
public int moveLeft(int index) {
return moveLeft.applyAsInt(index);
}
public boolean isConnected() {
return this == CONNECTED;
}
public static Connect from(boolean value) {
return value ? CONNECTED : DISCONNECTED;
}
}

이건 일종의 예시인데, 이렇게 함수형 인터페이스를 활용하시면, enum의 필드 활용도가 올라갈 수 있습니다!

Comment on lines +7 to +25
public class Ladder {
private final List<Line> lines;

public Ladder(int height, int playerCount, Random random) {
List<Line> temp = new ArrayList<>();

for (int i = 0; i < height; i++) {
temp.add(Line.create(playerCount, random));
}
if (height == 0) {
temp.add(Line.create(playerCount, random));
}
this.lines = List.copyOf(temp);
}

public List<Line> getLines() {
return lines;
}
}

Choose a reason for hiding this comment

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

🔥 Request Change

생성자에서 List 필드를 주입할 때, copyOf를 사용하는 것에 대한 리뷰에 다음과 같이 답해주셨습니다.

Names 내부에서 copyOf가 적용되어 있기 때문에 Players도 방어된 상태라고 생각했습니다.
하지만 공유해주신 자료(방어적 복사, 얕은 복사, 깊은 복사)를 참고하면서 copyOf는 얕은 복사에 해당하고, 이는 Names 내부의 리스트만 불변으로 만들 뿐 Players의 최종 상태까지 보장하지 않는다는 점을 이해했습니다.

리뷰에서 말씀주신 것처럼 Names는 도메인에서 의미가 다소 추상적인 개념이라고 판단하여 제거하였고,
Players / Results에서도 생성자에서 직접 List.copyOf(...)를 적용하여
도메인 객체 생성 시점에서 불변을 보장하도록 리팩터링하였습니다.

[질문]

태우가 방어적 복사를 사용하는 이유는 무엇인가요?
"여기서 copyOf를 쓰면 안되지!!" 라는 뉘앙스보다는 어떤 의도로 사용하였고, 어떤 트레이드 오프를 고려하였는가에 대한 질문입니다.

[추가 설명]

Players나 Results에도 비슷한 방식이 사용되었지만, 일단 Ladder를 대상으로 설명하겠습니다.

    private final List<Line> lines;

이 필드가 실제로 불변일까요?

저는 lines가 현재 실제로 불변하지도 않고, 불변해야할 필요가 있는지에 대한 확신도 없습니다.

        Ladder ladder = new Ladder(height, players.size(), new Random(1));
        ladder.getLines().add(Line.create(players.size(), new Random(1)));

위와 같은 방식으로 ladder가 생성된 이후에 lines를 불러,
java collection에서 제공하는 메서드로 값을 추가할 수 있겠죠.
ladder의 lines의 참조변수는 변하지 않아도 그 참조값이 가르키는 리스트의 모양은 바뀔 수 있습니다.
이는 불변이라고 할 수 없겠죠.

이를 만약 막고 싶다면, 제가 참고 자료로 남겼던 블로그에도 소개되는 unmodifiableList를 활용해도 충분히 막을 수 있습니다.

Comment on lines +66 to +69
int height = inputView.readInt();
if (height < 0) {
throw new IllegalArgumentException("높이는 양수여야 합니다.");
}

Choose a reason for hiding this comment

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

👀 Comment

따라서 Height 객체는 제거하고 입력값 검증은 컨트롤러에서 처리하는 방향으로 진행했습니다.
컨트롤러는 사용자 입력 → 도메인으로 넘겨주기 전의 최종 validation/파싱 책임을 가져가는 레이어라고 생각하고 있으며, 현재는 컨트롤러에서 height 검증과 예외 처리 흐름(while/retry)까지 담당하도록 구성했습니다.

혹시 이 부분을 InputView로 위임하는 방식이 더 적절한 케이스라면 조언 부탁드립니다. 저는 지금 단계에서는 Controller가 해당 책임을 가져가는 방향이 더 자연스럽다고 판단했습니다.

위에서 이런 코멘트를 남겨주셨습니다.
저는 태우가 controller에서 입력값을 검증하고,
재요청을 받는것 같은 처리를 한 것이 적절하다고 판단이 됩니다. 굿👍

"validation을 어디서 할까?" 이건 다들 고민하는 영역인 것 같아요.

  • inputView 에서 입력받을 때
    • 웹 어플리케이션으로 예를 들면, 전화번호 input 태그에 숫자가 아닌 문자를 입력했을 때, 즉시 경고를 띄울 수 있겠죠.
  • Controller
    • View에서 입력 받은 정보를 domain에 넘겨줘도 괜찮은지 판단하거나
    • Domain에서 응답 받는 정보를 view에 출력하도록 넘겨줘도 괜찮은지 판단할 수 있겠네요.
  • Domain
    • 이름은 다섯글자를 넘으면 안된다와 같은 중요한 비즈니스 정책을 담고 있을 수 있겠습니다.

각 영역에서 검증은 모두 있을 수 있고 목적이 약간씩은 다른 것 같아요.
이런 console 어플리케이션을 구현하다보면 그 경계가 모호하고 규모가 너무 작아 헷갈릴 수 있습니다.
하지만, 꼭 필요한 고민이라고 생각하고 추후에 다른 환경에서 개발할 때도
이렇게 각 영역에서 검증해야되는 것이 어떤것일까 고민하시면 좋은 설계를 할 수 있으실거에요.

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