도서/기술

[책] 객체에서 함수로 - 1장 애플리케이션 준비하기

egg528 2025. 2. 16. 14:36

 책에서는 함수형 프로그래밍의 본질이 참조 투명성이라 말한다. 참조 투명성을 가졌다는 건 쉽게 말해 동일한 입력값에 대해 항상 동일한 결과를 보장한다는 뜻이다. 참조 투명성을 검증하는 법 즉, 코드가 함수형인지를 검증하는 방법은 간단한데, 식을 그 값으로 바꿀 수 있다면 함수형이라 말할 수 있다.

 

fun add(x: Int, y: Int): Int {
    return x + y
}

// 식으로 표현
fun main() {
    val sum = add(3, 4)
    
    println("sum: $sum")
}

// 값으로 표현
fun main() {
    val sum = 7

    println("sum: $sum")
}

 간단한 예를 들면 위 add 함수를 식으로 봤을 때 이를 결과 값으로 대체해도 결과는 동일하다. 이는 add 메서드가 함수형이라는 것을 의미한다.

 

 

 

OOP와 반대되는 FOP의 특징들


 FOP는 OOP와 조금 다른 부분들이 존재한다. (1)OOP는 책임과 역할에 집중하다 보니 어떤 대상이 어떤 동작을 수행하는 지에 집중한다. 반면 데이터 변환에 집중한다는 점이다. 특정 식이 어떤 입력값을 어떤 결과값으로 처리하는 지가 핵심이다. (2)OOP에서 메서드가 없는 클래스는 흔히 빈약한 객체라는 안티 패턴으로 분류된다. 하지만 FOP에서 메서드가 없는 불변 객체는 매우 일반적인 패턴이다. (3)OOP에서 구체적인 클래스에 의존하지 않기 위해 인터페이스 사용을 권장하고 선호한다. 하지만 FOP에서는 인터페이스 선언 보다는 함수의 시그니처에만 의존하도록 권장한다. (4)마지막으로 제네릭 타입에 대한 관점이 다르다. FOP에서는 제네릭을 일종의 타입 빌더로 본다. 즉, List<T>는 그 자체로 타입이 아닌 모든 구체 타입을 생성할 수 있게 해주는 빌더이다. 때문에 너무 많인 제네릭 타입을 사용하지 않는 이상 제네릭 타입을 복잡성으로 보지 않는다.

 

 

 

FOP 테스트


 OOP에서 일반적으로 많이 사용되는 테스트 양식은 given/when/then 방식이다. OOP의 테스트는 대개 특정 조건에서 특정 객체의 동작이 촉발되는 것을 테스트하기 때문에 목적에 부합하는 양식이다. 하지만 FOP에서 테스트는 여러 입력값에 대해 올바른 결과값이 나오는지를 검사해야 한다. 이런 테스트를 진행하는 방식에는 2가지가 존재한다.

 

 첫째는 예제 기반 테스트로 입력과 결과에 대한 여러 예제를 제공하는 것이다. 일반적으로 가장 먼저 떠올릴 수 있는 방식이고 프로덕션 시스템에서 얻은 구체적인 몇몇 데이터를 재현해보고 싶을 때 유용하다. 둘째는 속성 기반 테스트로 A -> B 변환 과정에서 성립해야 하는 속성을 정의하고 여러 입력값을 기반으로 함수를 실행한 후 속성을 만족하는지 테스트하는 기법이다. (속성 기반 테스트는 감이 안 오는데.. 4장의 주제인 만큼 우선 지금은 넘어가자)

 

 입력값에 대한 올바른 결과값을 검사하는 FOP 테스트의 특징은 Mock과 Stub을 사용하지 않도록 만든다. OOP는 객체들이 협력하는 세상이다. 때문에 어떤 동작을 테스트하려고 할 때 여러 객체들이 사용되는데, 이 과정에서 객체간의 상호작용을 테스트하거나 테스트 범위를 줄이기 위해 Mock과 Stub가 사용된다. 하지만 FOP 테스트에서는 단순히 입력에 대한 결과, 입력에 대해 속성을 만족하는지를 테스트하지 내부 상태나 개체 호출의 순서를 살펴볼 필요가 없기 때문에 Mock, Stub을 사용하지 않고 테스트를 짤 수 있다.