순수 함수를 사용한 프로그래밍이다.
모든 입출력은 메소드 이름, 인수, 반환 타입으로 구성된 메소드 시그니처에 명시해야 한다.
순수 함수는 호출 횟수에 상관없이 주어진 입력에 대해 동일한 출력을 생성한다.
public class PriceEngine {
public BigDecimal calculateDiscount(Product... products) {
BigDecimal discount = BigDecimal.valueOf(products.length * 0.01);
return discount.min(BigDecimal.valueOf(0.2));
}
}
위 calculateDiscount메소드는 하나의 입력(products)과 하나의 출력(Bigdecimal 타입)이 있으며, 둘 다 메소드 시그니처에 명시되어 있다. 이 메소드는 순수 함수에 해당한다.
메소드 시그니처
public BigDecimal calculateDiscount(Product... products)
출력 이름 입력
calculateDiscount는 입력과 출력 모두 메소드 시그니처에 명시되어있으므로 순수 함수다.
입출력을 명시한 순수 함수는 이에 따르는 테스트가 짧고 간결하며 이해하고 유지 보수하기 쉬우므로 테스트하기가 매우 쉽다.
출력 기반 테스트를 적용할 수 있는 메소드 유형은 순수 함수뿐이다. 유지 보수성이 뛰어나고 거짓 양성 빈도가 낮다.
숨은 입출력은 코드를 테스트하기 힘들게 하며, 가독성도 떨어진다.
부작용: 메소드 시그니처에 표시되지 않은 출력이다. 클래스 인스턴스의 상태 변경, 디스크 파일 업데이트 등
예외: 메소드가 예외를 던지면, 프로그램 흐름에 메소드 시그니처에 명시되지 않는 경로를 만드는 것이기 때문에 숨은 출력이다.
내외부 상태에 대한 참조: 정적 속성(static)를 사용해 가져오는 메솓, 데이터 베이스, 비공개 변경 가능 필드 참조 등 의 작업들은 모두 메소드 시그니처에 없는 입력이다. (숨은 입력)
메소드가 순수 함수인지 판별하는 가장 좋은 방법은 프로그램의 동작을 변경하지 않고 해당 메소드에 대한 호출을 반환 값으로 대체할 수 있는지 확인하는 것이다.
메소드 호출을 해당 값으로 바꾸는 것을 "참조 투명성" 이라고 한다.
public class PureFunction {
public int increment(int x) {
return x + 1;
}
}
int y = increment(4);
int y = 5;
public class ImpureFunction {
int x = 0;
public int increment() {
x++;
return x;
}
}
위 incerement메소드는 순수 함수가 아니다.
반환 값이 메소드의 출력을 모두 나타내지 않으므로 반환 값으로 대체할 수 없다.
위 예제에서 숨은 출력은 필드 x의 변경(부작용)이다.
public class Comment {
private String text;
private List<Comment> comments = new ArrayList<>();
public Comment(String text) {
this.text = text;
}
public Comment addComment(String text) {
Comment comment = new Comment(text);
comment.comments.add(comment);
return comment;
}
}
어떤 부작용도 일으키지 않는 애플리케이션을 만드는 것은 불가능하다.
함수형의 목표는 비즈니스 로직을 처리하는 코드와 부작용을 일으키는 코드를 분리하는 것이다.
부작용을 비즈니스 연산 끝으로 몰아서 비즈니스 로직을 부작용과 분리한다.
결정을 내리는 코드(함수형 코어): 부작용이 필요 없기 때문에 수학적 함수를 사용하여 작성
해당 결정에 따라 작용하는 코드(가변 셸): 데이터베이스 변경 등
함수형 코어와 가변 셸의 협력
가변 셸은 모든 입력을 수집한다.
함수형 코어는 결정을 생성한다.
셸은 결정을 부작용으로 변환한다.