본문 바로가기

부트캠프/우아한테크코스

[우테코 6기] 프리코스 3주차 회고 - 로또

반응형

[문제] 프리코스 3주차 GitHub

 

GitHub - woowacourse-precourse/java-lotto-6

Contribute to woowacourse-precourse/java-lotto-6 development by creating an account on GitHub.

github.com

 

[풀이] 프리코스 3주차 GitHub

 

GitHub - kyum-q/java-lotto-6

Contribute to kyum-q/java-lotto-6 development by creating an account on GitHub.

github.com


개발 요구사항 - 구현 기능 목록

 

https://github.com/kyum-q/java-lotto-6/tree/kyum-q/docs


소감 - 느낀 점 

 

이번 과제는 전보다 까다로운 조건이 추가되서 기능 구현에 많은 시간이 소요되었다.

 

함수가 단일책임원칙을 따르도록 하려고 계속해서 노력을 해왔지만 이번에는 전보다 더 까다롭게 함수를 분리하였다. 과제 조건에 15줄 이상 금지 조항으로 분리하지 않으면 기능 구현이 불가능했기 때문이다. 

 

또한 else 사용 금지 조항은 처음 해보는 코드 제한이어서 나를 더욱 힘들게 하였다. 하지만 이번 과제를 통해 else를 사용하지 않아도 함수로 분리하여 return 하는 방식을 다시끔 알게 되었다. 이렇게 코드를 작성하니 전보다 깔끔한 코드가 된 것 같고 앞으로도 else문을 자제해야겠다 생각했다.

 

이 외에도 잘 사용하지 않아 코드 작성법이 서툴렀던 enum 을 사용하면 enum의 장점을 알게되었다. 전에는 상수를 사용할 때 단순 전역변수를 사용하거나 static final 을 사용해왔다. 이것만 알고 있었다보니 불편함을 몰랐다. 하지만 이번에 enum을 사용하면서 enum의 편리함과 내가 전에 사용했던 방식들의 불편함을 알게되었다.

 

그리고 코드 분리, 함수 분리 뿐만 아니라 도매인 분리를 하면서 패키지도 생성하였다. 이 과정을 거치면서 기능과 관련된 함수를 찾기에 편리하였다. 그래서 앞으로도 패키지를 분리하고 클래스를 분리하고 함수를 분리하는 과정을 착실히 해야겠다고 생각하였다. 

 

테스트 코드 작성에 관해서는 저번 프리코스에 이어서 두번째 작성해보는데 그래도 전에 작성해본 경험이 있다고 생각보다 수월하게 되었다. 이렇듯 프리코스 과정을 밟으면서 점차 성장하고 있는 나를 발견하니 뿌듯하고 기분이 좋았다. 다음에도 더 성장한 나를 위해 열심히 프로젝트를 진행할 것이다.

 


피드백

 

함수(메서드) 라인에 대한 기준

  • 코드가 15라인을 넘어간다면 함수 분리를 위한 고민을 해야한다.

발생할 수 있는 예외 상황에 대해 고민

  • 정상적인 경우를 구현하는 것보다 예외 상황을 모두 고려해 프로그래밍하는 것이 더 어렵다. 예외 상황을 고려해 프로그래밍하는 습관을 들인다
    • 잠깐 자랑) 참고로 나는 열심히 했다 ㅎㅎ

 

비즈니스 로직과 UI 로직을 분리

 

연관성이 있는 상수는 static final 대신 enum을 활용한다

  • enum : "Enumeration: 열거"의 약자 (열거형)
    • 즉, 열거형(enum)은 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형
    • 쉽게 상수 데이터들의 집합
  • 요일, 계절 등 사용할 수 있는 상수 데이터가 한정된 경우,
    열거형 타입(enum)으로 사용하면 보다 구조적으로 프로그래밍 가능

 

final 키워드를 사용해 값의 변경을 막는다

  • final : 변수가 한번 지정하면 바뀌지 않게 설정 (생성자에서 변수 할당해야한다.)
  • static : 메모리에 한번만 할당하게 설정
  • 최근에 등장하는 프로그래밍 언어들은 기본이 불변 값이다. 
  • 자바는 final 키워드를 활용해 값의 변경을 막을 수 있다.

 

객체의 상태 접근을 제한한다

  • 인스턴스 변수의 접근 제어자는 private으로 구현한다.

 

객체는 객체스럽게 사용한다

  • 객체가 단순 인스턴스를 가지고 있고 getter, setter 만 존재하는 것은 좋지 않
  • getter를 사용하는 대신 객체에 메시지를 보내자
    • 객체지향 프로그래밍은 객체가 스스로 일을 하도록 하는 프로그래밍
    • Getter를 너무 사용하면 안좋은 점 : 객체스럽지 못함
      1. Getter로 상태값을 꺼내 그 값으로 객체 외부에서 로직을 수행한다면, 객체가 로직(행동)을 갖고 있는 형태가 아니고 메시지를 주고 받는 형태도 아니게 된다.
      2. 객체 스스로 상태값을 변경하는 것이 아니고, 외부에서 상태값을 변경할 수 있는 위험성도 생길 수 있다.
      3. 디미터의 법칙을 위반할 가능성도 생기고, 가독성이 떨어지는 문제가 발생할 수 있다.
디미터 법칙 : 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 것을 의미

디미터 법칙을 위반한 전형적인 코드의 형태
- object.getChild().getContent().getItem().getTitle();
: 접근자(getter) 메서드가 기차 객차처럼 연이어 이어진 경우

https://johngrib.github.io/wiki/law-of-demeter/#%EB%94%94%EB%AF%B8%ED%84%B0-%EB%B2%95%EC%B9%99%EC%9D%84-%EC%9C%84%EB%B0%98%ED%95%9C-%EC%BD%94%EB%93%9C---%EA%B8%B0%EC%B0%A8-%EC%B6%A9%EB%8F%8C

 

  • 무작정 사용 금지 ? NO !
    Getter를 사용하고 싶은 경우 외부에서 변경하지 못하도록, Collections.unmodifiableList() 을 사용하도록 한다.
    ex) return Collections.unmodifiableList(cars);

 

필드(인스턴스 변수)의 수를 줄이기 위해 노력한다

  • 필드(인스턴스 변수)의 수가 많은 것은 객체의 복잡도를 높이고, 버그 발생 가능성을 높일 수 있다. 
  • 필드에 중복이 있거나, 불필요한 필드가 없는지 확인해 필드의 수를 최소화한다.

 

테스트 코드도 코드다

  • 테스트 코드도 코드이므로 리팩터링을 통해 개선해 나가야 한다. 특히 반복적으로 하는 부분을 중복되지 않게 만들어야 한다. 예를 들어 단순히 파라미터의 값만 바뀌는 경우라면 아래와 같이 테스트할 수 있다.
@DisplayName("천원 미만의 금액에 대한 예외 처리")
@ValueSource(strings = {"999", "0", "-123"})
@ParameterizedTest
void underLottoPrice(Integer input) {
    assertThatThrownBy(() -> new Money(input))
            .isInstanceOf(IllegalArgumentException.class);
}

 

private 함수를 테스트 하고 싶다면 클래스(객체) 분리를 고려한다

  • 가독성 이상의 역할을 하는 경우, 테스트하기 쉽게 구현하기 위해서는 해당 역할을 수행하는 다른 객체를 만들 타이밍이 아닐지 고민해 볼 수 있다. 
  • 다음 단계를 진행할 때에는 너무 많은 역할을 하고 있는 함수나 객체를 어떻게 의미 있는 단위로 분할할지에 초점을 맞춰 진행한다.

 

+ Stream을 이용하면 코드 길이가 줄어든다

  • 15줄이라는 제한 + else 금지 + depth 3이하로 작성 조항으로 코드 길이를 어떻게 하면 줄일까?에 대한 고민을 많이 했다. 그 과정에서 Stream을 알게 되었다. 이는 생각보다 많은 편리함과 깔끔함을 전달해줬다.
  • Stream : 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과 필터링, 가공된 결과 획득을 도움
    • 병렬 처리 가능
    • 람다를 활용할 수 있는 기술

 

Stream 에 대해

 

Stream 생성

  • Arrays.stream 사용
Stream<String> stream = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
  • Stream.builder() 으로 하나씩 추가
 Stream.<String>builder().add("Eric").add("Elena").add("Java").build();
  • Stream.generate() 으로 하나의 값으로 초기화 된 n 개 생성
Stream.generate(() -> "el").limit(5); // [el, el, el, el, el]

 

 

 가공하기

  • Filtering : 필터(filter)은 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업 (새로운 스트림 생성)
names.stream().filter(name -> name.contains("a"));
  • Mapping : 맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환 (새로운 스트림 생성)
names.stream().map(String::toUpperCase);

 

 

결과 만들기

  • sum() , count(), min(), max(), average()
  • Collectors.toList() List로 변환
stream().map(Product::getName).collect(Collectors.toList());
  • Matching
    • 하나라도 조건을 만족하는 요소가 있는지(anyMatch)
    • 모두 조건을 만족하는지(allMatch)
    • 모두 조건을 만족하지 않는지(noneMatch)
boolean anyMatch = names.stream().anyMatch(name -> name.contains("a"));
boolean allMatch = names.stream().allMatch(name -> name.length() > 3);
boolean noneMatch = names.stream().noneMatch(name -> name.endsWith("s"));

 

  • foreach : 요소를 돌면서 실행되는 최종 작업
names.stream().forEach(System.out::println); // name을 다 출력

 

 

stream에서 사용하는 :: 이란?
:: 기준으로 왼쪽 객체의 오른쪽 메소드를 사용

System.out::println → 해당 stream에 담겨져있는 것들을 System.out 객체에 println 메소드를 사용한다
 System.out.println(stream에 담겨져 있는 것)

 

반응형