포스트

2021-01-18-TIL

리액티브 선언문의 용어

리액티브 선언문 용어집은 선언문에 나오는 비동기, 배압(Back-Pressure), 탄력성, 메시지 기반 같은 용어를 정의한다. 용어집에서 비동기는 클라이언트가 보낸 요청이 이후 임의의 시점에 처리되고, 클라이언트는 서비스 안의 실행을 직접 보거나 동기화할 수 없다는 뜻이다. 배압은 과부하에 걸린 컴포넌트가 상류 컴포넌트에 자신의 상태를 알려 부하를 줄이게 하는 피드백이고, 탄력성은 확장성에 자동 자원 관리가 더해진 개념으로 설명한다.


Java Stream

스트림으로 합계 구하기

스트림의 합계를 구하는 방법은 여러 가지다. Baeldung 글은 reduce(0, Integer::sum) 같은 리듀스 연산, Collectors.summingInt()를 쓰는 collect(), 그리고 mapToInt()로 IntStream으로 바꾼 뒤 sum()을 호출하는 방법을 비교한다. 객체 리스트의 필드를 더할 때는 mapToInt(x -> x.getPrice()).sum()처럼 매핑 후 합계를 구하는 형태가 가장 짧다.

1
2
3
int total = prices.stream()
        .mapToInt(Integer::intValue)
        .sum();

스트림과 for 루프

JDriven 글은 성능, 가독성, 익숙함 세 가지 논점을 다룬다. 작은 리스트에서는 for 루프가 빠르고, 아주 큰 리스트에서는 병렬 스트림이 유리할 수 있지만 병렬 스트림은 오버헤드가 커서 확실할 때만 쓰라고 한다. 대신 for 루프 안에서 하던 “조건 검사”와 “값 꺼내기”가 각각 filter와 map이라는 이름을 갖게 되므로 의도가 코드에 드러난다는 점을 스트림의 장점으로 든다.


프로그래밍 패러다임

프로그래밍 패러다임은 프로그램을 구상하고 구조화하는 상대적으로 높은 수준의 방식이고, 한 언어가 여러 패러다임을 지원할 수 있다. 위키백과의 분류에서 명령형은 실행 흐름과 상태 변경을 코드가 직접 제어하는 방식이고, 그 아래에 프로시저 단위로 조직하는 절차적 프로그래밍과 데이터와 동작을 객체로 묶는 객체지향이 있다. 선언형은 무엇을 계산할지를 기술하는 쪽이며 함수형 프로그래밍이 여기에 속한다.


순수 함수

순수 함수는 두 조건을 만족하는 함수다. 같은 인자에 대해 항상 같은 값을 반환하고(정적 변수, 비지역 변수, 가변 참조 인자, 입력 스트림에 따라 결과가 바뀌지 않는다), 부수 효과가 없다(비지역 변수나 가변 참조 인자를 바꾸거나 입출력을 하지 않는다).

특징: 지역변수만 사용, call-by-value만 사용, 영향을 미치는 범위가 블록 내부에 한정

메서드를 순수 메서드로 바꾸기

  1. 클래스 변수를 지역 변수로 변경

순수 메서드가 꼭 static이어야 하는지에 대한 Stack Overflow 답변은 static이 순수함을 보장하지 않는다고 설명한다. static 메서드도 다른 정적 상태에 기대면 순수하지 않고, 반대로 인스턴스 메서드도 객체를 0번째 인자로 보고 그 객체가 불변이면 순수하다고 볼 수 있다.


클로저와 1급 객체

클로저는 자신이 만들어질 때의 렉시컬 스코프에 있던 변수를 묶어 둔 1급 함수다. Java는 8 이전에는 익명 내부 클래스로 이를 흉내 냈고, Java 8부터 람다로 표현한다. 다만 람다 안에서 쓰는 바깥 지역 변수는 final이거나 사실상 final이어야 한다(JLS 15.27.2). 그래서 Java 람다는 값을 캡처할 수는 있어도 바깥 변수를 바꿀 수는 없다.

1급 객체는 변수에 할당할 수 있고, 인자로 넘길 수 있고, 반환값으로 돌려줄 수 있는 대상이다. 참고한 글은 Kotlin의 함수는 이 세 조건을 만족하지만 Java의 메서드 자체는 그렇지 않다고 비교한다.


학습 목표

  • 클로저 closure에 대해 학습하고 정리한다. (예제 코드가 있다면 더 좋다. 리뷰할 때는 다른 언어와 차이점도 비교한다.)
  • 순수함수 pure function에 대해 학습하고 정리한다. (예제 코드가 있다면 더 좋다.)
  • 고차 함수 higher-order function에 대해 학습하고 정리한다. (예제 코드가 있다면 더 좋다. 언어별로 다른 점을 비교해보자.)
  • 프로그래밍 패러다임이 무엇인지 알아보고, 왜 필요한지 토론한다.
  • 객체지향 패러다임과 함수형 패러다임 공통점과 차이점을 토론한다.

Java 초보 때 자주하는 실수

  • 모두 static 필드 및 메서드로 선언해버린다. (main이 static이니깐 자연스럽게 에러 제거를 위해서)
  • 접근제한자를 모두 default나 public으로 선언해버린다.
  • main이나 한 메서드의 덩치가 너무 커져버린다.
  • 메서드는 단일 책임을 가져야 하는데 그렇지 않게 된다.
  • 클래스 단위와 이름이 명확하지 않고, 그 내용 또한 그러하다.
  • 메서드를 너무 잘게 쪼개서 오히려 가독성이 떨어진다.
  • 변수명에 a, b, c 또는 약자를 사용한다. 이는 가독성을 떨어뜨릴 뿐이다. IDE에서 자동완성 기능을 제공하므로, 이를 이용하여 약자를 사용하지 않도록 하는 것이 좋다.
  • 들여쓰기가 3번 이상 나오는 경우가 많다. for ~ if ~ if … 이런 경우 우선은 들여쓰기를 제거하도록 노력해본다. 하지만 불가피한 경우 내부 블록의 일부를 메서드로 빼낸다.
  • 주입을 하지 않고 생성자를 마구 호출한다.

빌더 패턴과 상속 구조의 메서드 체이닝

메서드 체이닝은 체이닝할 메서드가 this를 반환하게 만들면 된다. 그런데 상속 구조에서는 부모 클래스의 메서드가 부모 타입을 반환하므로, 부모 메서드를 호출한 뒤에는 자식 메서드를 이어서 호출할 수 없고 캐스팅이 필요해진다. 참고한 글은 부모 클래스에 제네릭 타입 T extends Pet<T>와 추상 메서드 getThis()를 두고, 자식 클래스가 getThis()에서 자기 자신을 반환하게 해서 이 문제를 푼다. 빌더 패턴을 상속 구조에 적용할 때도 같은 기법을 쓴다.


Questions

Q. 매개변수로 함수를 받는게 순수 함수인가? 그 함수도 순수함수라면?

Q. static final을 붙여야 순수 함수인가?

Q. final 클래스 변수를 사용하면 그것도 퓨어 함수인가? 로컬변수만

Q. HashSet을 넣으면 외부에서 약수가 아닌 다른 HashSet을 넣으면 문제가 된다?

위의 정의에 비추어 보면, static은 순수함의 조건이 아니고 핵심은 결과가 인자에만 의존하고 부수 효과가 없는지다. 값이 바뀌지 않는 final 상수만 읽는 함수는 같은 인자에 같은 결과를 내므로 첫 번째 조건을 깨지 않는다. 반대로 가변 객체(HashSet 등)를 인자로 받아 내용을 바꾸면 가변 참조 인자를 변경하는 부수 효과가 된다.


Tips

Tip. 그룹핑

Tip. range closed 사용하면 인덱스 + 1 안해줘도 된다.

IntStream.range(a, b)는 끝값 b를 포함하지 않고, IntStream.rangeClosed(a, b)는 포함한다.

참고한 자료외부 출처 37

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다