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

[자동차 경주 게임] 코즈 미션 제출합니다. #64

Merged
merged 72 commits into from
Feb 15, 2020

Conversation

kouz95
Copy link

@kouz95 kouz95 commented Feb 12, 2020

No description provided.

comma로 나뉘어진 숫자의 합을 반환 하는 메소드 작성.
calculate 메소드에서 입력값의 구분자가 colon인 경우에 대한 테스트 추가 작성.
Positive 일급 객체 사용으로 유효성 검사를 진행.
null 또는 공백이 입력된 경우 0을 반환한다.
입력값이 숫자인지 판별한다.
입력값이 1이상인지 확인한다.
domain 및 util 패키지 생성.
domain 및 util 패키지 생성.
Copy link

@Hue9010 Hue9010 left a comment

Choose a reason for hiding this comment

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

이번 리뷰를 맡게된 휴라고 합니다 :)
각 객체에 맞게 책임 분리를 꼼꼼히 잘해 주셨네요!

전체적으로 final을 많이 사용하셨는데 불변성을 고려하여 작성 한거라면 불변 객체가 아닌 것들이 일부 존재하는데
한번 고민 해보시고 불볍 객체로 마저 완성해주시거나, 불변 객체로 처리 할 객체가 아니라고 생각되시면 final 키워드를 제거해주세요. (혹은 처리가 힘드시다면 제거만 해주시고 고민 된 부분 문의 주세요!)
final 키워드로 인해 다른 사람들이 불변 객체로 오해 할 수 있답니다.

그리고 Cars에 대한 테스트도 한번 마저 작성해보세요 😋

public static void main(final String[] args) {
final Cars cars = CarsFactory.create(InputView.inputNames());
final TryCount tryCount = new TryCount(InputView.inputTryCount());
final Record record = new Record();
Copy link

Choose a reason for hiding this comment

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

final 키워드를 달아 불변할거라고 기대되지만 사실 불변한 객체가 아니랍니다.
아래 글을 보면 딱 Record와 같은 예제를 보여주네요 :)
http://wonwoo.ml/index.php/post/1173

package tdd.racingcar.domain;

public class Car {
private final Position position = new Position(0);
Copy link

Choose a reason for hiding this comment

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

생성자에 같이 들어가면 어떨까요?

import java.util.function.Consumer;
import java.util.stream.Stream;

public class Cars {
Copy link

Choose a reason for hiding this comment

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

공백이 두칸이네요

}

public void move() {
cars.forEach(tryToMove());
Copy link

Choose a reason for hiding this comment

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

        for (Car car : cars) {
            final Power randomPower = PowerFactory.createRandomPower();
            car.move(randomPower);
        }

전체적으로 람다를 활용하는 방향으로 Iterable의 forEach를 많이 사용하셨는데 향상된 for문도 한번 사용해보시는건 어떨까요?
학습을 하는 차원에서 람다를 많이 사용하는 것도 도움이 되고 좋게 봅니다.

다만 지금의 forEach와 Consumer를 조합하여 사용하는 방식이 보편적인 않은 환경이라면 서로간의 코드를 이해하는데 약간의 시간을 사용하게 되기도 하고, 충분히 지금 방식의 반복은 익숙하신 걸로 보여서 향상된 for문에 적응해 보는 것도 추천드립니다.
이미 잘 사용 하실 수도 있을 것 같네요 :)

return position.match(maxPosition);
}

public int getPosition() {
Copy link

Choose a reason for hiding this comment

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

일급 객체를 좀 더 철저하게 사용해서 int가 아니라 position을 반환하게 바꿔보세요.
한가지 힌트를 드리자면 출력부분에는 Position의 toString을 재정의해서 숫자가 그대로 출력되게 할 수 있답니다.

return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElseThrow(() -> new IllegalArgumentException("차가 존재하지 않습니다."));
Copy link

Choose a reason for hiding this comment

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

IllegalArgumentException 는

부정한 인수, 또는 부적절한 인수를 메서드에 건네준 것을 나타내기 위해서 발생 됩니다.

현재 상황에서는 다른 Exception이 좋을 것 같습니다!

return position.getPosition();
}

public String getName() {
Copy link

Choose a reason for hiding this comment

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

name도 마찬가지!

src/main/java/tdd/racingcar/domain/Power.java Show resolved Hide resolved
Comment on lines 8 to 11
private final List<Car> cars;

public Cars(final List<Car> cars) {
this.cars = cars;
Copy link

Choose a reason for hiding this comment

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

final 키워드가 있다고 cars가 의도한대로 불변할지 한번 생각해보세요!

@kouz95
Copy link
Author

kouz95 commented Feb 14, 2020

안녕하세요! 코즈입니다. 먼저 좋은 피드백 감사드립니다 😊
재 리뷰 요청드리기 전에 궁금한것이 생겨 질문드리고 싶습니다.

현재, 최대한 final을 붙이는 연습과 람다식을 사용하는 연습을 하고 있습니다. 보내주신 피드백 덕분에 final에 대한 깊은 고민과 방어적 복사, 그리고 람다식만 쓰는것에 대한 부작용에 대해 생각해볼 수 있었습니다. 정말 감사드립니다.
때문에 보내주신 피드백, 학습 이후 대부분 반영할 수 있었는데, 반영 중 final에 대해 궁금한 점이 생겼습니다.

궁금한 것은 재할당만 금지하는 방법이 있는지에 대한 부분입니다.

제가 최대한 final을 붙이게된 배경은 재할당은 버그가 발생하기 쉬운 부분이라고 학습 했던점과, 최대한 객체를 훼손시키지 않고 사용하는걸 연습하기 위함 이었습니다.
하지만 어느 순간 모든 부분에 final을 붙인 후, 재 할당이 필요한 경우에만 final을 제거하는 것이 더 안전하다는 생각이 들게 되었고, 이럴거면 none-final이 존재해야 하는것 아닐까? 라는 생각도 하게 되었습니다.

때문에, 제가 final Record record 또는 final Position과 같이 선언하였던 것은, 객체의 상태는 변경되겠지만 최소한 재할당은 금지한다는 의미였다고 생각합니다.

이번 리뷰를 통해 final을 객체 또는 컬렉션에 선언할 경우, '해당 객체는 불변 객체이다' 라는 것을 암시하게 된다는 것은 새로 알게된 지식입니다. 하지만, 이런 지식과 맞물려 생각이든건 '그렇다면 재 할당만 금지하는 수단이 있는가?' 였습니다.

혹시, 이러한 고민들에 대해서 제가 잘못된 생각을 하고 있는 부분이 있거나, 관련된 지식을 알려주신다면 정말 감사드릴 것 같습니다..!

@kouz95
Copy link
Author

kouz95 commented Feb 14, 2020

추가적으로 혹시, Position의 move, Record의 append 같은 메소드가 내부 변수를 변경하는 것이 아닌,
변경된 상태의 객체를 반환하도록, 최대한 불변 객체로 구현하여 위험성을 낮추는 것이 더 지향해야할 방향인가요?

@Hue9010
Copy link

Hue9010 commented Feb 14, 2020

약간 잘못된 표현을 했었네요. final이 있으면 불변 객체로 생각하기보다는 해당 변수가 변하지 않을 것이라는 걸 기대하게 된다가 맞겠네요. final이 기능상으로는 한번의 초기화 이후 재할당이 불가능한게 끝이지만 그 결과 final 변수는 변하지 않을 것이라는 기대가 따라오게 됩니다.

재할당은 버그가 발생하기 쉬운 부분인 이유가 무엇일까요? 재할당이 된다는 것은 결국 값이 변하는 걸 의미합니다. 그렇다면 다시 한번 생각해보면 리스트의 내부 값이 바뀐 경우엔 리스트가 변한걸까요?
사실 이 문제는 조금 구분해서 생각해야하는게 정말 재할당만을 막기 위해 final을 사용한건지 불변 객체로 사용하기 위함인지 어떤 의도를 담고 작성 했는지가 중요합니다.

아쉽게도 재할당만을 금지하기 위한 방법이 final의 기능이기 때문에 final을 사용하는게 맞습니다. 거꾸로 불변/가변 객체임을 나타낼 방법이 없다가 맞을 것 같네요.

솔직히 말씀드리자면 저는 final을 별로 사용하지 않습니다. 그렇다고 코즈님이 잘못 생각하고 있다고 생각하지도 않습니다. 이 부분은 개인 혹은 팀의 컨벤션 혹은 자신이 담당한 도메인 성격에 따라 다를 수 있는 부분이라고 생각합니다. 아무래도 final을 사용하지 않는 이유가 궁금하실 수도 있을텐데 딱 공감을 하는 부분이라서 책의 내용을 옮기자면

또한 인수와 변수 선언에서 final 키워드를 모두 없앴다. 실질적인 가치는 없으면서 코드만 복잡하게 만든다고 판단했기 때문이다. final을 제거하겠다는 결정은 일부 기존 관례에 어긋난다. 예를 들어, 로버트 시몬스는 "코드 전체에 final을 사용하라....."고 강력히 권장한다. 확실히 나와 다른 생각이다. 내가 보기에 final 키워드는 final 상수 등 몇 군데를 제하면 별다른 가치가 없으며 코드만 복잡하게 만든다. 어쩌면 내가 이렇게 느끼는 이유는 내가 짠 단위 테스트가 final 키워드로 잡아낼 오류를 모두 잡아내기 때문인지도 모르겠다.
<클린코드 p.356 >

저같은 경우는 테스트로 다 잡아 내고 있다고 하진 못하겠고... 😢 대신 필요한 곳에 필요한 변수만 만들어 사용하여 알 수 없는 곳에서 다시 재할당 하는 위험성 자체를 줄입니다.
리뷰어인 저도 잘 사용안한다 하고 하마터면 책도 클린코드에서 사용하지 않는다고 하니까 잘못됐나 생각하실 수도 있지만 사실 final의 사용을 추천 하는 자료들도 더 많은데다가 위 책의 내용도 자신의 주장이 일반적이지 않다는걸 드러냅니다. 중요한건 final을 얼마나 알고 어떤 의도로 사용하는지가 중요하다고 생각합니다.
(사실 코틀린 처럼 가변/불변 객체 타입을 애초에 지원해주는 언어라면 적극적으로 타입을 맞춰 쓰는게 맞다고 생각합니다.)

그리고 람다식 부분에도 말씀드렸다시피 학습 차원의 사용이라는 의도도 충분히 final, 람다 등 무엇이 됐던 그것을 사용하는 충분한 이유가됩니다. 특히나 적절한 상황에 적절한 걸 사용하는게 좋다는데 그것도 많이 사용해봐야지 알수 있는 부분이기 때문에 도리어 적극 사용해 보시는 걸 추천 드립니다. 다만 코즈가 사용하시는 걸 보이 이미 어느정도 익숙하게 사용하시는 걸로 보여 그 다음 단계로 정말 이게 맞는지 생각해보시길 바래서 피드백을 드렸답니다. 😄


Record의 append 같은 메소드가 내부 변수를 변경하는 것이 아닌, 변경된 상태의 객체를 반환하도록 앗! String을 직접 더하는 연산을 안하고 Record 내부에 StringBuilder를 둔 이유가 무엇이였을까요? 😄

값이, 상태가 가변한다는게 무조건 나쁜건 아닙니다. 바뀔건 바뀌어야죠. 다만 바뀌지 말아야 할 것이 바뀌거나 바뀔 수도 있다는 위험성을 가진게 문제일 뿐입니다.

사실 지금 final 키워드에 대해서 한번 생각해보고, 스스로가 final을 어떻게 사용하고 있는지 확인하는 정도도 충분하다고 생각합니다. 특히나 아직 2월이기 때문에 지속적으로 사용해보면서 고민해보시는 걸 추천드리긴 합니다.

@Hue9010
Copy link

Hue9010 commented Feb 14, 2020

일급 컬렉션 (First Class Collection)의 소개와 써야할 이유 조금은 다른 글이지만 이 링크의 2. 불변을 보면 불변이 왜 중요한지에 대해서 깔끔하게 간단히 설명해 주네요.

@kouz95
Copy link
Author

kouz95 commented Feb 15, 2020

정성스러운 피드백 정말로 감사드립니다. 제가 궁금해하였던 부분들 명쾌하게 해결되었습니다.

재할당에 대해선 말씀해주신 테스트로 검증하며 final을 제거하여 가독성을 높인다는 것과, 최대한 필요한 곳에서 필요한 변수만 만들어 위험성을 줄이는 것 모두 좋은 지향점이라고 생각이 됩니다.

피드백 덕분에 제가 어떤 의지를 담아 코드를 작성하고 있는지 좀 더 명확하게 알 수 있는 계기가 된 것 같습니다.
클린 코드 몇몇 부분은 아직 내가 이해 하긴 어렵다는 생각에 뛰어넘은 부분이 있었는데, 정확히 지금 필요한 지식이 적혀있었네요. 😅

현 시점에서 느끼는건 불변 객체를 표현할 수 없는 자바에서 final을 전부 붙혀주기 보단 재할당과 같은 문제를 의식적으로 처리하고, 불변 객체에 final을 붙혀 표현하는 것이 여러모로 경제적인 방법이라고 생각이 됩니다.
좋은 지식 전달해주셔서 감사드립니다. 😆

Records의 StringBuilder를 사용했던 부분은 사실, 맨 처음엔 큐로 작성을 하였습니다.
단순히 게임 결과를 저장한 후, 출력하자라는 부분에서 가장 처음으로 떠오른 구조가 큐였고, 이후에 한 크루에게 왜 큐를 쓰는지, 오버 스펙은 아닌지에 대한 질문을 받고 StringBuilder가 좀 더 가볍겠구나, 생각을 하였습니다.
하지만 말씀해주신 대로 필요한건 String으로 충분했던 부분이었던 것 같습니다. 😅

보내주신 피드백들 반영하여 리뷰 요청드렸습니다!
혹시라도 좀 더 개선해야 할 부분이 있다면 피드백 보내주시기 바랍니다. 😊
정말로 큰 도움 되었습니다. 감사합니다!!

Copy link

@Hue9010 Hue9010 left a comment

Choose a reason for hiding this comment

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

피드백에 맞춰 잘 반영해주셨네요! 💯
이번 요청은 머지하도록 할게요 👍
Records의 String 부분에 대해서만 한가지 의견 남겨드렸습니다! 추가적으로 궁금한 사항이 생긴다면 comment나 DM으로라도 편히 문의 주세요 :)

package tdd.racingcar.domain;

public class Records {
private String records;
Copy link

Choose a reason for hiding this comment

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

https://techcourse.woowahan.com/s/wTqesI5s/ls/GXDJNdI9

Record의 append 같은 메소드가 내부 변수를 변경하는 것이 아닌, 변경된 상태의 객체를 반환 이 부분은 StringBuilder가 매번 문자열을 생성하지 않기 위해서 사용하는데 매번 생성을 하면 결국 String이랑 다를 점이 없다는 것을 말씀 드리고 싶었던 건데 너무 많은 여지를 열어 드렸었네요. 🤣

@Hue9010 Hue9010 merged commit 25e6adc into woowacourse:kouz95 Feb 15, 2020
@Hue9010
Copy link

Hue9010 commented Feb 15, 2020

슬랙 채널로 보내지기엔 내용이 길어질 부분이 있어 추가적으로 한가지 더 말씀드리면 기술적으로는 이미 잘 구현하시는지라 왜 혹은 필요한가를 한번 생각 해보시는 것도 많은 도움이 되실 겁니다.

그렇다고 지금 처럼 작성하시는 것에 대해서는 방어적인 자세가 되진 않았으면 좋겠네요. 가능하면 마음껏 사용해보면서 정말 필요한지 혹은 왜 써야하는지 생각해보는 과정이 추가되면 좋을 것 같아요.

추후에 면접을 보게 되는데 그런 부분들이 있다면(예를 들어 이번의 final) 정말 알고 쓴건지 좋다고 하니까 그냥 쓴건지 질문이 들어 올 수 있답니다.
저같은 경우도 예전에 면접 볼 당시에 final에 대해서 마찬가지 고민을 하다가 인텔리제이로 메소드 생성시 자동적으로 final이 붙게 옵션 설정을 해뒀다가 코딩 테스트에 여러 곳에 final이 들어가 있어서 final을 사용한 이유와 어떻게 생각하는지 질문이 들어왔었네요 🤣
당시 답변으론 아마 final을 사용하는 이유야 많이들 아는 부분에 대해 간략히 말했고 어떻게 생각하는지에 대해서는 아직 잘 모르겠어서 일단 사용해보면서 정말 필요한지 고민을 해보려고 학습적인 차원에서 사용했고 과제에 포함된건 솔직히 인텔리제이 설정 바꿨다가 신경 쓰지 못했었다고 답했었답니다.

이전 피드백에도 말씀드렸다시피 팀이나 개인에 따라서는 final을 정말 신경 써야 할 정도로 중요한 부분이 존재할 수 있다고 생각합니다. 제가 그러한 파트는 아니라서 어림짐작만 하자면 정산 등 돈에 대한 복잡한 계산이 들어가는 부분이 있다면 바뀌지 않아야 값이 실수로 바뀐다면 큰일이 나겠죠? 근데 그러한 값들이 많이 존재한다면? 아마 저라도 final을 적극 사용할 겁니다.(제가 겪어보진 않아서
예로서 상상적인 부분이니까 실제 정산이 이러할 것이라 생각해주진 마세요 😄 )

@kouz95
Copy link
Author

kouz95 commented Feb 15, 2020

마지막까지 감사드립니다..
계속 감사하다는 말을 반복하니, 감사하다는 말이 조금은 가벼워 지지 않을까 걱정도 되네요 😭

말씀해주신 말들 여러번 읽어보며, 정말로 지금 시기에 저에게 중요한 말이었음을 느낍니다.

자바나 객체 지향에 경험이 없어, 학습을 시작하면서 부터 좋은 코드는 무엇인지, 어떠한 부분에서 명쾌한 추상화라고 느끼는지, 그 감각을 익히고 흉내내려고 노력했습니다.

그 결과 어느 정도는 코드 스멜도 느끼고 안티 패턴엔 어떤것들이 있는지 알게 된것 같습니다.
하지만 분명히, 왜 이게 안티고 스멜인지에 대해선 스스로 명확하지 않습니다. 때문에, 기술적으로 잘 구현한다는 말이 상당히 어색하게 느껴집니다.
(근본이 중요한 것인데.. 저에게 근본이 있는 상태인지...)

오히려 제가 가진 문제점은 코드 스타일이 비슷하거나 수준이 높은 사람보단, 그렇지 않은 사람을 페어로 만나게 되었을때 마주 칠지도 모르겠습니다.

앞으로 겉으로 화려하기 보단, 좋다는 것들에 무 비판적인 수용을 지양하고, 여러 가지 시도들 해보며 왜 이게 좋은 코드인지, 나는 어떤 의도로 이 코드를 작성 하고 있는지 설명할 수 있는 사람이 되기위해 노력하겠습니다.
해주신 말들 전부 정말로 큰 도움 되었습니다.
감사합니다!! 🙏

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