자바 8의 stream을 사용하도록 리팩토링한다.

2017-11-09 15:53

오늘 코드는 로또 구현 과정에서 가져왔다.

로또를 구매할 때 사용자가 수동으로 선택한 값을 입력할 수 있다. 예를 들어 3장의 로또를 수동으로 구매하는 경우 입력 값은 다음과 같다.

1,2,3,4,5,6
11,12,13,14,15,16
21,22,23,24,25,26

위 값을 입력하면 3장의 로또를 구매해야 한다. 구현 과정에서 로또 한장은 UserLotto 클래스로 추상화했고, UserLotto를 여러 장 가지는 클래스는 Lottos로 추상화했다.

public class Lottos {
    private List<UserLotto> lottos;

    public Lottos(List<UserLotto> lottos) {
        this.lottos = lottos;
    }

    [...]
}
public class UserLotto {
    public static final int MONEY_PER_TICKET = 1000;
    
    private List<Integer> lotto;

    public UserLotto(List<Integer> lotto) {
        Collections.sort(lotto);
        this.lotto = lotto;
    }

    [...]
}

앞에서 사용자가 입력한 값을 활용해 Lottos를 구현하는 코드는 다음과 같다.

    public static Lottos generateByUserInput(String input) {
      String[] userInput = input.split("\n");
      ArrayList<UserLotto> lottos = new ArrayList<>();
      for (int i = 0; i < userInput.length; i++) {
          ArrayList<Integer> temp = new ArrayList<>();
          String[] numbers = userInput[i].split(",");
          for (int j = 0; j < numbers.length; j++) {
              int target = Integer.parseInt(numbers[j].trim());
              if (target < 1) {
                throw new NegativeIntegerException("number input should be bigger than 0");
              }
              temp.add(target);
          }
          lottos.add(new UserLotto(temp));
      }
      return new Lottos(lottos);
    }

위 코드는 에러 처리도 부족하고, 코드도 이해하기 힘들다. 이 코드를 자바 8의 stream을 활용해 리팩토링하는 것이 오늘의 요구사항이다. Optional과 Stream을 적절히 활용하면 좀 더 깔끔한 코드를 구현할 수 있으리라 기대한다.

위 샘플 예제는 코드스쿼드 에서 새롭게 진행 중인 마스터즈 코스에서 발췌한 코드입니다. 코드스쿼드의 마스터즈 코스는 코드 리뷰 방식의 개인별 맞춤 학습 방법입니다.

1개의 의견 from FB

2개의 의견 from SLiPP

2017-11-10 00:15

제가 리팩토링한 코드는 아래와 같습니다.

public static List<Integer> makeNumListFromString(String input){
		return Arrays.asList(input.split(" ")).stream()
                                .mapToInt(s ->Integer.parseInt(s))
				.boxed().collect(Collectors.toList());
	}

예외 처리 등이 되지 않은 코드인데요, 최소한의 예외처리와 약간의 리팩토링을 하면 다음과 같이 되겠네요.

public static List<Integer> makeNumListFromString(String input){
		return Arrays.stream(input.split(" "))
		       .mapToInt(s -> (Integer.parseInt(s)))
		       .peek(i -> {
			if (i < 1) throw new IllegalArgumentException("입력된 숫자가 너무 작습니다.");
				})
		       .boxed().collect(Collectors.toList());
	}

물론 이 코드는 List를 만들어주고 있고 Lottos 객체를 만들어주지는 않네요.

나름 리팩토링을 해 보았는데 썩 만족스럽진 않습니다. ㅜㅜ 중복된 숫자가 있지 않도록 방어해야 하고, Integer.parseInt()에서 나올 NumberFormatException도 적절히 처리해줘야 할 것 같습니다.

2017-11-10 14:35

저는 로또 하나의 값만 처리하는 코드를 만들어 봤어요. 에러를 다음과 같이 처리했네요.

public class LottoNo {
	private int no;
	
	private LottoNo(int no) {
		if (no < 1 || no > 46) {
			throw new IllegalArgumentException(no + "는 유효하지 않은 로또 숫자입니다.");
		}
		
		this.no = no;
	}
	
	public static LottoNo of(int no) {
		return new LottoNo(no);
	}
	
	public static LottoNo of(String no) {
		return new LottoNo(Integer.parseInt(no));
	}
}

로또 번호 하나를 LottoNo으로 추상화했어요.

6개의 번호 목록을 List가 아닌 Set으로 관리해 중복 값이 있는지의 여부를 체크하도록 했네요.

public class ManualLotto {
    private Set<LottoNo> lotto;

    public ManualLotto(Set<LottoNo> lotto) {
        if (lotto.size() != 6) {
          throw new IllegalArgumentException(lotto + "는 6개의 숫자여야 합니다.");
        }
        
        this.lotto = lotto;
    }

    @Override
    public String toString() {
        return "ManualLotto [lotto=" + lotto + "]";
    }
}

위와 같이 기반 클래스를 구현한 후 메소드는 다음과 같이 구현해 봤네요.

	static ManualLotto generateManualLotto(String lotto) {
		Set<LottoNo> userLotto = Arrays.stream(lotto.split(","))
			.map(no -> LottoNo.of(no))
			.collect(Collectors.toSet());
		return new ManualLotto(userLotto);
	}

String lotto 값이 null이나 공백 문자에 대한 예외는 처리하지 않았네요. Optional 써서 가능할 듯..

의견 추가하기