[우아한테크코스] 2월 22일 TIL

2 minute read

오늘 배운 점 정리

  1. 페어와 함께 피드백 리뷰 후 반영해서 리팩토링

로또 미션 리팩토링 - 에어 리뷰어 피드백

  1. 인스턴스 변수의 초기화는 하나의 생성자를 통해서 하도록 하자.
    검증 로직과 초기화 로직의 중복을 방지할 수 있다.
    그래서 바이트 코드를 뜯어봤다.
     public LottoCount(Money money) {
         this.lottoCount = money.divideMoney(LOTTO_PRICE);
     }
    

    위 코드의 바이트 코드는 아래와 같다.

     // access flags 0x1  
     public <init>(Llotto/money/Money;)V  
     L0  
         LINENUMBER 14 L0  
         ALOAD 0  
         INVOKESPECIAL java/lang/Object.<init> ()V  
     L1  
         LINENUMBER 15 L1  
         ALOAD 0  
         ALOAD 1  
         SIPUSH 1000  
         INVOKEVIRTUAL lotto/money/Money.divideMoney (I)I  
         PUTFIELD lotto/lottogame/LottoCount.lottoCount : I  
     L2  
         LINENUMBER 16 L2  
         RETURN  
     L3  
         LOCALVARIABLE this Llotto/lottogame/LottoCount; L0 L3 0  
         LOCALVARIABLE money Llotto/money/Money; L0 L3 1  
         MAXSTACK = 3  
         MAXLOCALS = 2  
    


     public LottoCount(Money money) {
         this(money.divideMoney(LOTTO_PRICE));
     }
    

    위 코드의 바이트 코드는 아래와 같다.

     // access flags 0x1
     public <init>(Llotto/money/Money;)V  
     L0  
         LINENUMBER 15 L0  
         ALOAD 0  
         ALOAD 1  
         SIPUSH 1000  
         INVOKEVIRTUAL lotto/money/Money.divideMoney (I)I  
         INVOKESPECIAL lotto/lottogame/LottoCount.<init> (I)V  
     L1  
         LINENUMBER 16 L1  
         RETURN  
     L2  
         LOCALVARIABLE this Llotto/lottogame/LottoCount; L0 L2 0  
         LOCALVARIABLE money Llotto/money/Money; L0 L2 1  
         MAXSTACK = 3  
         MAXLOCALS = 2  
    

    같은 행위를 위한 것임에도 두개의 생성자를 따로 사용하는 것이 비용이 더 많이 드는 것을 알 수 있었다.
    사실은 같은 행위를 하는 것이므로 코드의 중복도 줄일 수 있다.(?)
    인스턴스의 초기화는 하나의 생성자에서 해주는 것이 좋다.
    이미 존재하는 인스턴스 초기화임에도 불구하고 활용하지 않을 이유가 없다.

  2. EnumMap 사용
    애초에 로또 미션에서 Statistics라는 클래스에서 Map으로 Rankings와 그 개수를 관리하고 있었는데, EnumMap이라는게 존재한다는 걸 알고 적용시켜보았다.
     private final EnumMap<Ranking, Integer> statistics = new EnumMap<>(Ranking.class);
    

    위와 같은 방식으로 구현할 수 있다.
    enumMap을 사용하는 경우, hashMap을 사용할 때에는 일정 개수 이상의 자료가 들어오면 resizing하는 과정을 거쳐야하는데, enumMap의 경우에는 이미 정해진 enum 개수만큼 사용하게 되므로 문제되지 않는다.
    또한, enumMap은 순서가 보장되어 다시 순서를 조작하지 않고 사용할 수 있다.

  3. stream API collectingAndThen 사용
     List<LottoNumber> numbers = new ArrayList<>();
     for (String value : values.split(COMMA_DELIMITER)) {
         numbers.add(makeValidatedNumber(value));
     }
     return makeValidatedNumbers(numbers);
    

    위와 같은 코드를

    return Arrays.stream(values.split(COMMA_DELIMITER))
                 .map(this::makeValidatedNumber)
                 .collect(Collectors.collectingAndThen(Collectors.toList(), this::makeValidatedNumbers));
    

    아래와 같이 구현했다.
    collect(Collectors.collectingAndThen(이렇게 모은 것을, 이렇게 바꿀래))이다 :)

  4. 스레드 세이프란? RandomNumberGenerator 이슈 관련
    • 스레드: 프로세스의 하나의 실행 흐름으로 자바에서는 하나의 프로그램에서 여려개의 스레드를 만들고 실행할 수 있다.
    • 멀티스레드: 여러 일을 한꺼번에 처리할 수 있도록 해 속도 상승을 위해 사용한다.
    • 스레드가 생기는 방법:
    • Runnable 인터페이스를 상속한 클래스를 만든다.
    • 상속한 클래스의 객체를 만든다.
    • 객체를 가진 실행 객체를 만든다.
    • 실행 객체를 실행시킨다.
      - 스레드풀: 최대 개수를 지정해 쓰레드를 생성한다. - 스레드세이프: 멀티스레드로 동작하는 프로그램에서 잘 동작하는 것
      두 스레드가 같은 객체를 공유하며 서로 간섭해 예상치 못한 결과를 내는 것
    • syncronized 선언을 이용해 보장할 수 있다. 하지만 스레드가 침범하지 않아도 항상 lock을 걸어 비용이 비싸다.
    • stream도?
      - 관련 좋은 글

  5. Ranking의 코드를 수정하지 않고 확장하는 방법? second stream에서 분리?
    ?? 몰까나..