KoreanFoodie's Study

[C++ 함수형 프로그래밍] 특성 기반 테스트 , 모나드, 이벤트 소싱 본문

R & D/Software Engineering

[C++ 함수형 프로그래밍] 특성 기반 테스트 , 모나드, 이벤트 소싱

GoldGiver 2023. 10. 4. 14:26

함수형 프로그래밍 패러다임에 대해 알아보며 이를 C++ 를 이용한 소프트웨어 개발에 어떻게 적용하면 좋을지 알아보겠습니다.

핵심 :

1. 특성 기반 테스트를 유닛 테스트와 함께 활용하면 테스트를 간략화하면서 견고한 테스트가 가능하다.

2. 모나드는 특정 인자를 타입으로 받아, unit/bind operator 를 통해 특정 중복을 제거하는 것에 유용하게 사용될 수 있다.

3. 이벤트 소싱을 활용하면, 데이터가 어떤 연유로 현재 상태에 이르게 되었는지 쉽게 파악이 가능하다.

특성 기반 테스트

소프트웨어 개발에서 유닛 테스트는 매우 중요하다. 결국 프로그램이라는 것이 특정 입력을 넣으면 특정 출력이 나오도록 하는 것이므로, 원하는 결과가 나오는지 배포 전에 항상 점검해야 한다.

기존에는 사실 특정 상황에 대해 프로그램이 잘 동작하는지를 점검하기 위해 많은 테스트 케이스를 돌려야 했지만, 특성 기반 테스트를 활용하면 테스트 케이스의 수를 줄이거나, 테스트를 범주화할 수 있다.

예를 들어, power 함수를 구현하고 이를 테스트한다고 해 보자. 만약 유닛 테스트를 실시한다고 하면, 엣지 케이스를 몇개 포함하여 랜덤하게 수많은 수에 대해 일일히 값이 맞는지를 검사해야 할 것이다.

하지만 프로그램의 '특성'을 살펴보면, 결과를 다음과 같이 범주화할 수 있다 :

완벽한 것은 아니지만, 위의 특성을 기반으로 테스트 함수를 작성하고, 일부 엣지 케이스를 추가하면 특성 기반 테스트를 진행할 수 있을 것이다 😉

다만 특성 테스트는 유닛 테스트를 대체하는 것이라기보다, 보조재로 받아들이는 것이 좋다! 🤣

 

 

모나드

사실 함수형 프로그래밍으로 리팩토링을 할 때는, 아래의 3 가지를 유념하면 된다.

  1. 순수 함수를 추출한다.
  2. 테스트하고 리팩터링한다.
  3. 고응집 원칙으로 순수 함수를 클래스로 다시 묶는다.

책에는 길고 긴 예제와 함께 다양한 설명이 있었지만, 결국 위의 3 가지 원칙을 위배하지 않는다면 핵심에서 크게 벗어나지는 않을 것이다.

 

추가로, 책에서는 '모나드' 라는 개념을 소개하는데...

모나드란 타입을 인자로 받아 해당 타입에 대한 함수를 합성하고 계산을 할 수 있도록 하는 디자인 패턴이다.

모나드가 되기 위해서는 다음 조건을 만족해야 한다 : (모나드를 M, 인자 타입을 T 라고 가정)

  1. 타입을 인자로 받아야 함
  2. unit(return) operator 가 있어야 함 : 타입 T 를 받아, M 을 반환해야 함
  3. bind operator 가 있어야 함 : 타입 T 를 받아, M 타입을 반환하는 함수가 있어야 한다

가장 대표적인 예시인 Maybe 모나드를 보자.

template<typename ValueType>
struct Maybe
{
	// 연산 타입
	typedef function <optional<ValueType>(const ValueType, const ValueType) OperationType;
	
	// 값 캡슐화
	const optional<ValueType> value;
	
	optional<ValueType> appply(const OperationType operation, const optional<ValueType> second) const 
	{
		if (value == nullopt || second == nullopt) 
			return nullopt;
		return operation(value.value().second.value());
	}
};

/***/

// 0 으로 값을 나누는 상황을 고려
function<optional<int>(const int, const int)> divideEvenWith0 = []
	(const int first, const int second) -> optional<int>
	{
		return (second == 0) ? nullopt : make_optional(first / second);
	};

/***/

// Maybe<int>{3}
Maybe<int>{1}.appply(plus<int>(), 2);

// Maybe<int>{nullopt}
Maybe<int>{2}.appply(divideEvenWith0, nullopt)
Maybe<int>{2}.appply(divideEvenWith0, 0)

실제로, apply 를 통해 연산을 하기 위해 각 연산자 람다와 값을 건네주는 것을 확인할 수 있다.

위의 모나드를 활용하면 연산에 있어 특정 중복을 없앨 수 있다. Maybe 모나드 말고도 여러 모나드가 있으니, 궁금하면 더 검색해 보자 😊

 

 

이벤트 소싱

이벤트 소싱이란, 데이터를 저장함에 있어 결과값을 애플리케이션에서 모든 상태를 일으키는 이벤트를 순서에 맞게 저장하여 Status 를 만들어내는 기법이다. 이벤트 소싱에 대해서는 이 글에 매우 잘 정리가 되어 있어, 일부 내용을 발췌했다.

그림으로 표현하면, 아래와 같다 :

이벤트 소싱을 사용하면 해당 데이터가 어떤 연유로 현재 상태에 도달했는지 파악하기 쉽다! 😁

또한 Write 시, 실제 연산을 수행할 필요가 없으므로 저장 속도가 빠르다. 대신 Read 성능은 떨어질 것이다 😅 더 자세한 장/단점은 앞서 소개한 링크를 참고하자!

함수형 프로그래밍을 접목하면, 각 연산에 대한 항목을 람다로 만들어 저장해 놓고, 필요할때 조합하여 호출하면 될 것이다.

Comments