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

1단계 - 단위 테스트 작성 #290

Merged
merged 25 commits into from
Jul 17, 2022
Merged

1단계 - 단위 테스트 작성 #290

merged 25 commits into from
Jul 17, 2022

Conversation

zbqmgldjfh
Copy link

안녕하세요 리뷰어님! 김지우 라고 합니다!

우선 저의 리뷰를 담당해주셔서 감사하다는 말 먼저 전합니다!!

질문 1가지

1. Section의 생성을 어디서?

예를 들어 다음과 같이 section을 추가하는 메서드가 있다고 해봅시다!

LineService 내부

    @Transactional
    public void addSection(Long lineId, SectionRequest sectionRequest) {
        Station upStation = stationService.findById(sectionRequest.getUpStationId());
        Station downStation = stationService.findById(sectionRequest.getDownStationId());
        Line line = lineRepository.findById(lineId).orElseThrow(IllegalArgumentException::new);
        line.addSection(new Section(line, upStation, downStation, sectionRequest.getDistance()));
    }

이때 addSection의 인자부분에서 Section을 생성하여 전달하는 방식을 사용하고 있습니다.
즉, LineService에서 Section을 생성하고 있습니다.

이와는 달리

line.addSection(line, upStation, downStation, sectionRequest.getDistance());

위와 같이 값만 전달 한 후, line 내부에서 section을 생성하는 방법도 가능할 것 입니다.

저는 개인적으로 전자의 방식이 적합하다 생각하는데... Section의 생성은 어디가 적합할까요?
Service일까요? 아니면 Line과 같은 class 내부 일까요?

@pch8388
Copy link

pch8388 commented Jul 16, 2022

Section의 생성을 어디서?

두 방법 모두 많이 사용하기도 하고, 의견이 분분할 수 있다는 점 미리 말씀드리면서, 제 의견을 말씀드리겠습니다.
개인적으로는 Line 내에서 생성하는 것을 추천드립니다.
Section 이 과연 외부로 노출되어야 할 객체인가를 고민해봐야 한다고 생각합니다.
노선 을 사용할 때, 클라이언트가 노선은 A 구간(X역과 Y역으로 이루어져 있고 거리는 3), B 구간(�Y역과 Z역으로 이루어져 있고 거리는 5) 로 이루어져 있다 라는 세부적인 정보를 알아야 할까요? 아니면 정제된 노선은 X ~(거리3) Y ~(거리5) Z 역으로 이루어져있다 라는 정보만 알아도 될까요? 당연히 정답은 없습니다 😅
다만, 대부분의 경우에는 현재와 같이 Line<Section> 이 외부로 드러나 있으면 부작용이 있을 수 있습니다. (컬렉션을 마음대로 조작)
하지만 Section 이라는 객체를 아예 숨기는 것은 선택적인 문제라고 생각합니다.

다만, 객체 생성의 책임을 지는 곳이 명확하면, 그만큼 변경에 대한 전파가 줄어들고, 리팩토링도 더 쉬울 수 있습니다.
예를 들어 Section 의 생성 로직이 복잡해져서, Factory 클래스를 만들고 싶다거나 하는 경우에도, 해당 Factory 에 의존성을 갖는 클래스가 적은게 더 좋겠죠

개인적인 결론을 말씀드리면 라이프 사이클을 같이 하는 객체는 대표하는 엔티티의 속성 이라고 볼 수도 있기 때문에 내부로 감추는 것이 더 좋다고 생각합니다. 😄

Copy link

@pch8388 pch8388 left a comment

Choose a reason for hiding this comment

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

지우님 안녕하세요 🙇
이번 미션을 같이 진행하게 된 권승철입니다. 😄
코멘트 참고하셔서 수정 후 리뷰 요청 부탁드립니다 🔥

public List<Section> getSections() {
return sections;
}

public void addSection(Section section) {
Copy link

Choose a reason for hiding this comment

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

비즈니스 로직에 관련된 메서드가 getter 보다 상위에 위치하는 것이 클래스를 이해하는 데 도움이 된다고 생각합니다 😄

Exception exception = assertThrows(IndexOutOfBoundsException.class, () -> lineService.deleteSection(line.getId(), downStation.getId()));

// then
then(exception).isInstanceOf(IndexOutOfBoundsException.class);
Copy link

Choose a reason for hiding this comment

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

이 예외가 적절할까요? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

애당초 IndexOutOfBoundsException는 적절하지 않았던 예외라 생각됩니다 ㅎㅎ
검증 부분을 추가하여 IllegalStateException을 던지도록 수정하였습니다.

추가로 커스텀 예외를 만들까 하다가? 일단 IllegalStateException 을 반환하도록 변경하였습니다.
다음 step2에서 커스텀 예외의 적용을 고민해보도록 하겠습니다!

for (Section section : sections) {
stations.add(section.getDownStation());
}
return new ArrayList<>(stations);
Copy link

Choose a reason for hiding this comment

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

이렇게 하신 이유가 있을까요? 🤔

Suggested change
return new ArrayList<>(stations);
return stations;

Copy link
Author

Choose a reason for hiding this comment

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

이게 변명을 하자면?
지난번 과제에서는 일급컬렉션을 구현하여 사용했는데, 이때 일급 컬렉션 내부의 �section들을 가져오기 위해 불변 객체로 만들어 가져왔었습니다.

문제는 이번 과제를 만드는 도중 일급컬렉션이 없음에도 불구하고... 일급컬랙션이 있다고 착각하여 생긴 문제인것 같습니다 ㅎㅎ
바로 stations list를 반환하도록 수정하였습니다!


public List<Station> getStations() {
final List<Station> stations = new ArrayList<>();
stations.add(getFirstSection().getUpStation());
Copy link

Choose a reason for hiding this comment

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

첫번째 상행역을 가져오는 메서드를 만드는 건 어떨까요? 💭
추가로 현재 구현으로는 Section이 하나도 없는 경우도 가능할 것 같은 데, 그런 경우에 이 메서드가 호출되면 어떻게 될까요? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

원래 service 계층에서 Sections이 없는 경우 empty list를 반환하도록 했었는데, 이를 Sections 내부로 로직을 이동시켰습니다!

}

public void deleteLastSection(Station station) {
Section lastSection = sections.get(getLastIndex());
Copy link

Choose a reason for hiding this comment

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

마지막 구간 을 가져오는 메서드를 만들면 어떨까요? 😃

}

private Section getFirstSection() {
return sections.get(0);
Copy link

Choose a reason for hiding this comment

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

클린 코드 G25: 매직 숫자는 명명된 상수로 교체하라

given(stationService.findById(downStationId)).willReturn(new Station("건대입구역"));
given(lineRepository.findById(anyLong())).willReturn(Optional.of(new Line("2호선", "green")));

SectionRequest request = new SectionRequest(upStationId, downStationId, 10);

// when
// lineService.addSection 호출
Copy link

Choose a reason for hiding this comment

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

불필요해진 주석은 삭제하는 것이 좋은 것 같아요 😅
클린코드의 한 구절입니다.

C3 : 중복된 주석
코드만으로 충분한데 구구절절 설명하는 주석이 중복된 주석이다. ...(중략)... 주석은 코드만으로 다하지 못하는 설명을 부언한다.

return new ArrayList<>(stations);
}

public void removeLastSection() {
Copy link

Choose a reason for hiding this comment

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

이 메서드가 있어야 하나요? 🤔

}

private int getLastIndex() {
return sections.size() - 1;
Copy link

Choose a reason for hiding this comment

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

sections 를 일급 컬렉션으로 만들어보면 어떨까요?
일급컬렉션

Copy link
Author

Choose a reason for hiding this comment

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

앗... 이전 미션에서 일급컬렉션을 사용하여 Sections를 만들었는데, 여기에도 적용되있다고 잘못 생각한것 같습니다 ㅎㅎ

public void setColor(String color) {
this.color = color;
}

public List<Section> getSections() {
Copy link

Choose a reason for hiding this comment

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

Line<Section> 이 외부로 드러나는 것이 괜찮을까요? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

일급 컬렉션으로 변경 하였으니, 외부에서는 메시지를 전달하여 필요한 정보를 얻도록 수정하였습니다!

Copy link

@pch8388 pch8388 left a comment

Choose a reason for hiding this comment

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

전체적으로 잘 리팩토링 해주셨습니다 💯
참고하실만한 코멘트를 남겨드렸습니다. 😄
이번 단계는 여기서 마무리하겠습니다. 다음 단계로 가시죠 ~ 🚀


public boolean dontHasDownStation(Station station) {
Copy link

Choose a reason for hiding this comment

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

개인적으로는 hasNot 이라고 그냥 쓰기도 합니다.
일반적으로 ishas prefix 로 시작할 때 boolean 을 반환한다고 예측하기가 쉽기 때문입니다. 😅

return sections.getStations();
}

public int getSectionsSize() {
Copy link

Choose a reason for hiding this comment

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

이 메서드는 테스트를 위해 공개된 것 같은 데, 테스트를 위해서 메서드를 열어 두는 것에 대해 고민해보셨을까요? 🤔

if (sections.isEmpty()) {
return Collections.emptyList();
}
final List<Station> stations = new ArrayList<>();
Copy link

Choose a reason for hiding this comment

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

Sections 가 비어있다면 emptyList를 반환하고, 아니라면 반환할 list를 만든다라는 부분을 빈 행으로 분리해서 조금 더 가독성을 높일 수 있다고 생각합니다.
클린코드를 인용하겠습니다. 😸

개념은 빈 행으로 분리하라
거의 모든 코드는 왼쪽에서 오른쪽으로 그리고 위에서 아래로 읽힌다. 각 행은 수식이나 절을 나타내고, 일련의 행 묶음은 완결된 생각 하나를 표현한다. 생각 사이는 빈 행을 넣어 분리해야 마땅하다.

Copy link
Author

Choose a reason for hiding this comment

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

확실히 빈 행을 통해, 코드와 코드간에 한숨 쉬어간다? 와 같은 생각이 들어서
가독성이 높아진것 같습니다! 감사합니다

for (Section section : sections) {
stations.add(section.getDownStation());
}
return stations;
Copy link

Choose a reason for hiding this comment

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

💯

@@ -29,10 +28,10 @@ public LineService(LineRepository lineRepository, StationService stationService)
@Transactional
public LineResponse saveLine(LineRequest request) {
Line line = lineRepository.save(new Line(request.getName(), request.getColor()));
if (request.getUpStationId() != null && request.getDownStationId() != null && request.getDistance() != 0) {
if (isValidRequest(request)) {
Copy link

Choose a reason for hiding this comment

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

💯

}

public List<Station> getStations() {
if (sections.isEmpty()) {
Copy link

Choose a reason for hiding this comment

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

👀 중복이 발생한 것 같아요. 하지만 해당 메서드 이름을 쓰면 getStations 에서는 조금 어색하다고 생각될 수 있겠네요

Suggested change
if (sections.isEmpty()) {
if (isInValidSize()) {

import java.util.List;

@Embeddable
public class Sections {
Copy link

Choose a reason for hiding this comment

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

SectionsSection 에 대한 단위 테스트를 작성하시지 않으셨네요 😢
다음 단계에서 단위 테스트를 작성해주시기 바랍니다. 🔥

@pch8388 pch8388 merged commit d114eb8 into next-step:zbqmgldjfh Jul 17, 2022
@pch8388
Copy link

pch8388 commented Jul 17, 2022

추가로 깜빡하고 코멘트를 안남긴 부분이 있네요 😢
ControllerExceptionHandler 를 이용하여 예외를 클라이언트에게 전달할 때 어떻게 보여줄 지에 대해서도 고민해주세요 🙇

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.

None yet

2 participants