[cpp] 사이드 이펙트, 그리고 함수 디자인

@codemaru · March 12, 2014 · 6 min read

class Downloader
{
public:
	xstatus DownloadUrl(string url, void *buffer, size_t buffer_size);
};

위와 같은 함수를 생각해 봅시다. url로 넘어온 경로를 다운로드 해서 buffer에다 저장하는 함수입니다. 결과 상태를 리턴 합니다. 그런데 이 함수에는 한 가지 단점이 있습니다. 웹 서버가 404 오류 같은 것을 뿜어도 성공을 리턴한다는 것이죠. 404 페이지를 그냥 다운로드 받습니다. 뭐 함수 디자인이 그렇게 된 것이니 버그는 아닌 셈이죠. 여기에다 404 오류 코드 같은 결과 값을 알 수 있는 기능을 추가한다고 생각해 봅시다. 어떻게 작업 하는 것이 좋을까요? 당연한 이야기겠지만 그 결과값은 DownloadUrl 함수를 수행하는 동안에만 알 수 있습니다.

class Downloader
{
public:
	xulong return_code_;

	xstatus DownloadUrl(string url, void *buffer, size_t buffer_size)
	{
		// processing
		return_code_ = get_return_code();
		return status;
	}

	xulong GetReturnCode()
	{
		return return_code_;
	}
};

위의 코드는 그러한 작업을 해 본 한 가지 샘플 코드입니다. DownloadUrl의 인터페이스를 수정하지 않아서 기존 코드와의 호환성을 유지 시켰습니다. 그리고 GetReturnCode라는 함수를 추가해서 DownloadUrl을 수행하는 동안 발생한 리턴 코드 값을 확인할 수 있도록 하는 인터페이스를 추가했죠. 호환성을 유지하면서 잘 확장한 디자인 같아 보이지만 이는 함정입니다.

첫번째 문제는 DownloadUrl과 GetReturnCode 사이에 어떠한 연관성도 찾을 수 없다는 점입니다. 즉, 이 클래스의 내부를 보지 않은 프로그래머가 DownloadUrl의 리턴 코드 값을 확인하기 위해서 GetReturnCode를 호출해야 한다는 점을 알 수 없습니다. 매뉴얼을 보고 익혔다고 하더라도 나중에 계속 기억하기가 쉽지 않죠.

두번째는 스레드 세이프하지 않다는 점입니다. DownloadUrl 함수를 호출하면 return_code_ 값이 변경됩니다. 이렇게 함수 호출로 인해서 다른 상태 값이 변경되는 것을 우리는 사이드 이펙트라고 부릅니다. 일반적으로 이러한 사이드 이펙트가 많으면 많을 수록 스레드에 안전하도록 만들기가 어렵고 복잡해 집니다. 특히나 요즘 같이 멀티 스레드가 대세인 환경에서는 다른 프로그래머에게 이 함수는 스레드 세이프하지 않아. 라고 아이기를 해주어야 하죠.

끝으로 마지막 문제는 스레드 세이프하지 않다는 점에 더해서 그 스레드 세이프하지 않음을 외부에서 제어할 방법이 전혀 없다는 점입니다. DownloadUrl과 GetReturnCode를 하나로 외부에서 묶기가 힘들다는 것이죠. 결국 외부에서 묶으려면 이를 포장한 Downloader2라는 클래스를 새로 만들어서 소스 코드 전체에 그 녀석을 쓰는 방법 밖에는 없습니다.

class Downloader
{
public:
	xulong return_code_;

	xstatus DownloadUrl(string url, void *buffer, size_t buffer_size);
	{
		return DownloadUrl(NULL, url, buffer, buffer_size);
	}

	xstatus DownloadUrl(xpulong rcode, string url, void *buffer, size_t buffer_size)
	{
		// processing
		if(rcode)
			*rcode = get_return_code();
		return status;
	}
};

위 코드는 일반적으로 올바른 방향의 재구조화된 코드를 보여줍니다. 앞선 코드와 가장 큰 차이점은 사이드 이펙트를 없앴다는 점입니다. 기존 함수 인터페이스는 유지하고 새로운 return_code를 전달 받는 함수 시그니처를 추가했습니다. 기존 함수 코드는 새로운 함수에 옮겨 쓰고, return_code 인자가 있는 경우에는 해당 값을 전달하도록 만든 것이죠. 그리고 기존 함수는 새로운 함수를 호출하도록 만들어서 소스 코드의 중복을 없애고 있습니다.

대체로 함수형 언어가 멀티 코어에 유리한 이유는 사이드 이펙트가 없기 때문입니다. 함수 디자인을 할 때에도 대체로 사이드 이펙트가 없도록 디자인 하는 것이 멀티 스레드, 멀티 코어 환경에서 훨씬 수월하게 동작할 수 있도록 만드는 지름길입니다. 함수에 어떤 사이드 이펙트가 존재한다면 이게 반드시 필요한 것인지 항상 물어보는 습관을 가지는 것이 중요하겠습니다.

@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중