각종 후기/우아한테크코스

[우아한 테크코스 3기] LEVEL 1 회고 (8일차)

제이온 (J.ON) 2021. 2. 9.

안녕하세요? 제이온입니다.

 

오늘은 데일리 미팅과 오전 수업, 오후에는 온보딩 팀프로젝트를 진행하였습니다. 배우고 느낀 점을 자세히 기록하려고 합니다.

 

 

데일리 미팅

워니가 어제는 휴가여서 데일리 미팅은 진행하지 않았고, 오늘은 휴가를 마치셔서 정상적으로 데일리 미팅을 진행하였습니다. 간단한 공지 사항 전달 이후에 오늘의 말하기 주제는 '어제는 무엇을 했고, 오늘은 무엇을 할 것이고, 어제 어떤한 이슈가 있었는가?'였습니다.

 

저는 어제 자동차 경주 게임 미션을 완수하였고, 온보딩 팀프로젝트 리허설을 하였고, 블로그 포스팅을 하였다는 이야기와 함께 오늘은 또 xxx을 할 것이다와 같은 이야기를 쭈욱 나열하고 이야기를 마쳤습니다. 그런데, 저도 모르게 제가 할 말만 집중하다보니까 다른 크루들의 질문을 보지 못하였습니다. 말이 발표지만, 사실 양방향 대화를 하는 것이나 다름없기때문에 다른 사람들의 반응을 주의 깊게 보아야 하는데, 제가 실수하지 않고 생각한 부분만 말하려고 실수한 것이죠 ㅠ. 내일부터는 말할 때 여유를 좀 가지고, 다른 사람들의 댓글을 보려고 합니다.

 

 

자동차 경주 게임 리팩토링 수업

오전 10시 30분부터 오후 1시까지 자동차 경주 게임 리팩토링 수업이 진행되었습니다. 제이슨이 사람들이 실수할 만한 부분을 짚고, 더 나아가 좋은 객체지향 개발을 하기 위한 조언을 해 주셨습니다.

 

저는 그 중에서 인상깊게 들었던 부분이 있는데, 바로 '원시 타입이나 문자열을 wrapping', '일급 컬렉션', '불변 객체', '방어적 복사'였습니다. 하나 하나 제가 듣고 느낀 점을 써 보겠습니다.

 

원시 타입은 저번 포스팅에서 입력값인 name과 turn을 wrapping하였는데, 이 부분을 소개해주셨습니다. 수업을 들으면서, 저는 Car 클래스 내의 position은 wrapping하지 않고, 그냥 원시 타입으로 두어서 수정해야겠다고 생각하였습니다. 또한, 일급 컬렉션은 저번 포스팅에서 다뤘던 Participants와 Winner에 해당합니다.

 

그런데, 설명을 듣다 보니 wrapping한 클래스와 일급 컬렉션은 불변 객체이어야한다고 알려주셨습니다. Car 클래스 내의 position은 fuel에 따라 정지할지 앞으로 갈지에 따라 값이 변하는데 어떻게 position을 불변 객체로 설정하는지 의문이 생겼습니다.

 

그리고 그 의문은 소스코드를 보면서 해결이 되었습니다.

 

 

public class Position {
  private final int position;

  public Position(final int position) {
    this.position = position;
  }

  public int get() {
    return position;
  }

  public Position run() {
    return new Position(position + 1);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Position position1 = (Position) o;
    return position == position1.position;
  }

  @Override
  public int hashCode() {
    return Objects.hash(position);
  }
}

 

 

오버라이드한 부분은 무시하시고, run()을 보면 position++가 아닌 새로운 객체를 할당하는 것을 알 수 있습니다. 이것은 position이 final이기때문입니다. 하지만, 처음에 저는 이것이 메모리 상의 낭비라고 생각하였습니다. 그냥 단순히 position을 final로 설정하지 않고 position의 값을 1 올려서 반환하면 안 되나 싶었습니다.

 

다른 크루들과 저와 마찬가지로 불변 객체의 단점을 언급하였지만, 제이슨은 자세히 나중에 다룰 것이니 기다리라고 하면서 "오라클은 불변 객체는 여러분이 메모리 걱정을 하는 것보다 더 많은 장점을 준다고 말하였습니다."라는 말을 해 주었습니다.

 

제가 불변 객체를 검색을 해 보니, 장점으로는 해당 객체의 신뢰성이 상승하고, 방어 복사라는 것을 사용할 필요가 없고, 멀티스레드 환경에서 동기화 작업이 필요없다는 것을 알게 되었습니다. 그리고 제 페어였던 나봄이, 저에게 요새는 컴퓨터의 성능이 좋아져서 불변 객체로 인한 메모리 낭비는 크게 단점이 되지 않는다는 말도 떠올랐습니다.

 

아직까지 명확하게 불변 객체가 좋은지 확 와닿지는 않지만, 일단 코치 분들이 말씀하시는대로 따라가면서 느껴보기로 하였습니다.

 

 

마지막으로, 방어 복사에 대해서 설명해 주셨습니다. 이것은 소스코드로 보시는 것이 편합니다.

 

 

  @Test
  void create() {
    List<Car> cars = new ArrayList<>(Arrays.asList(new Car("pobi"), new Car("sp")));
    // 정상
    Participants participants = new Participants(cars);
    assertThat(participants.getCars()).extracting("name").contains("pobi", "sp");

    // 생성자 단게에서 방어적 복사해야 함.
    cars.remove(1);
    assertThat(participants.getCars()).extracting("name").contains("pobi", "sp");

    // get 단계에서 unmodifiableList 처리
    List<Car> getCars = participants.getCars();
    assertThatThrownBy(() -> getCars.remove(1)).isInstanceOf(UnsupportedOperationException.class);

    // 또는 new ArrayList()를 통한 방어적 복사해야 함.
    assertThat(participants.getCars()).extracting("name").contains("pobi", "sp");
  }

 

 

첫 번째 줄에는 cars라는 리스트에 이름이 pobi, sp인 Car를 넣어 줍니다. 그리고 Participants 클래스에 인자로 리스트를 받는 생성자를 추가함으로써 객체를 생성한 후, pobi와 sp라는 Car가 둘 다 존재하는지 확인합니다.

 

 

package racingcar.domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import racingcar.domain.car.Car;

public class Participants {

  private final List<Car> cars;

  public Participants(final List<Car> cars) {
    this.cars = cars;
  }

  public Participants(final String... names) {
    cars = new ArrayList<>();
    for (String name : names) {
      cars.add(new Car(name));
    }
  }

  public List<Car> getCars() {
    return cars;
  }

  public String decideWinner() {
    Collections.sort(cars);
    Car winnerCar = cars.get(0);

    List<Car> winners = cars.stream()
        .filter(car -> car.isSamePosition(winnerCar))
        .collect(Collectors.toList());
    return new Winner(winners).getWinnerName();
  }
}

 

 

현재 Participants 클래스의 코드이며, 이 상태에서 assertThat()를 사용하면 테스트가 성공합니다. 이것은 아주 명확합니다.

 

하지만, 처음에 정의하였던 리스트에서 sp라는 Car를 지운다면 어떻게 될까요?

 

 

 

 

보시다시피 실패하는 것을 알 수 있습니다. 처음 정의한 리스트의 주소와 Participants 내의 위치한 리스트의 주소가 서로 같기때문에 이러한 문제가 발생하는 것입니다.

 

 

  public Participants(final List<Car> cars) {
    this.cars = new ArrayList<>(cars);
  }

 

 

따라서 이렇게 생성자 단계에서 그냥 cars를 좋다고 초기화해 버리는 것이 아니라, new ArrayList<>() 감싸서 외부와의 관계를 끊어야 합니다. 그리고 이것을 방어적 복사라고 합니다.

 

그렇다면, 이번에는 테스트 코드를 조금 바꿔봅시다.

 

 

class ParticipantsTest {
  @Test
  void create() {
    List<Car> cars = new ArrayList<>(Arrays.asList(new Car("pobi"), new Car("sp")));
    // 정상
    Participants participants = new Participants(cars);
    assertThat(participants.getCars()).extracting("name").contains("pobi", "sp");

    // 생성자 단게에서 방어적 복사해야 함.
    cars.remove(1);
    assertThat(participants.getCars()).extracting("name").contains("pobi", "sp");

    // 또는 new ArrayList()를 통한 방어적 복사해야 함.
    List<Car> getCars = participants.getCars();
    getCars.remove(1);
    assertThat(participants.getCars()).extracting("name").contains("pobi", "sp");
  }
}

 

 

이렇게 participants의 리스트를 얻어오고, 거기서 sp라는 Car를 지우면 어떻게 될까요?

 

 

 

 

맞습니다. 예상한 대로 이것도 주소를 공유하기때문에 방어적 복사를 취해주어야 합니다.

 

 

  public List<Car> getCars() {
    return new ArrayList<>(cars);
  }

 

 

이렇게 외부와의 관계를 또 끊어줌으로써 주소가 공유되는 것을 막을 수 있습니다. 그런데, 외부에서 이 cars에 add를 하거나 remove 연산을 못하게 막아버리고 싶다면 Collections.unmodifiableList()를 사용하면 됩니다.

 

  public List<Car> getCars() {
    return Collections.unmodifiableList(cars);
  }

 

 

이렇게 해도 마찬가지로 외부에서 값을 변경할 수 없기 때문에 주소 공유로 인한 문제가 발생할 수 있습니다. 다만, getCar() 단계에서만 Collections.unmodifiableList()를 사용하고 생성자에서는 방어 복사를 사용하지 않는다면 같은 문제가 발생하므로 주의하셔야 합니다. 여기서 불변 객체의 특징을 살리려면 Collections.unmodifiableList()를 사용하는 것이 맞다고 생각합니다.

 

 

그리고 불변 객체와 방어 복사를 배우면서 불변 객체의 장점을 알 수 있었습니다. 이 불변 객체는 변하지 않는 것을 보장하기 때문에 다른 클래스에서 이 객체를 사용하기 위해서 방어 복사를 할 필요가 없습니다.

 

길고 어려운 수업이었지만, 꽤나 유익한 시간이었다고 생각합니다.

 

 

온보딩 팀프로젝트

오늘 오후 4시부터 온보딩 팀프로젝트가 진행되었습니다. '보이는 라디오'라는 주제로 팀원끼리 줌으로 프로그래밍과 관련된 콘텐츠를 제작하여 ZOOM에서 실시간으로 발표하는 방식이었습니다.

 

우리 조는 개var자라는 팀 이름을 가졌고, 주제는 MBTI별 개발자의 특징으로 4가지 주제에 대해 대화하는 방식으로 시나리오를 작성하였습니다. 여기서 var는 여러 가지 타입의 변수를 담을 수 있기때문에 여러 가지 성격을 지닌 개발자와 어울린다고 판단하여 팀이름을 결정하였습니다.

 

시드, 도비, 나봄, 주모, 마갸와 함께 총 여섯 명이서 나름 연습을 하며 실수 없이 발표를 한 것에 만족하였습니다. 사람들의 반응도 재미있어하였고, 그 과정에서 팀원과 친해지는 좋은 계기가 되었다고 생각합니다. 또한, 하나의 목표를 달성하기 위하여 서로 설계를 하고, 의견을 모으고, 갈등이 있으면 바람직한 방향으로 대화를 하는 과정이 참 유익하였던 것 같습니다. 그리고 리허설을 통해서 부족한 부분을 보완하였는데, 이러한 과정이 개발 협업과 유사하다고 느꼈습니다. 특히.. 리팩토링의 중요성을 여실히 깨달았죠.

 

여튼.. 두 번은 못하겠는데 한 번은 해 볼만 합니다 ㅋㅋㅋㅋㅋ 제발 4기 분들은 뮤지컬했으면 좋겠습니다

 

 

정리

우테코가 시작한지 벌써 8일이 지났습니다. 이렇게 회고를 작성하니까 하루 하루 성장하는 느낌이 들고 기분이 좋습니다. 첫 번째 미션을 완수하였지만, 제이슨의 수업을 통해 더 나은 코드로 바꿔보았고, 바꾼 기법의 장점을 알아보는 과정이 저에게 많이 도움되었습니다. 이제, 미션과 온보딩 프로젝트가 모두 끝났기때문에 혼자 공부를 해야하는데, 객체지향의 사실과 오해를 열심히 읽으면서 적용하고, Git 공부를 해야겠습니다.

 

위 미션의 코드가 궁금하신 분은 이곳을 참고하시길 바랍니다.

추천 글