함수 인자의 기본값에 대해

as3는 함수의 인자를 선언할 때 기본값을 줄 수 있습니다. 기본값을 주는 의미를 명확히 이해하지 않으면 독이 되기 쉽기 때문에 한 번쯤 되짚어보는 시간을 마련했습니다.

as3에는 overload가 없다.

as3에는 overload가 없긴 하죠. 하지만 왜 overload를 사용하는지를 이해하면 딱히 없다고도 말하기 힘듭니다. 먼저 overload가 된다는 가정 하에 다음과 같은 두 개의 시그니쳐를 보죠.

function double( $val:int ):Number{
	return $val*2;
}

function double( $val:Number ):Number{
	return $val*2;
}

기대하셨던 overload입니다. 하지만 위의 코드는 심각한 문제를 초래합니다. double( 3 ) 라고 할 때 어떤 함수를 호출할지 컴파일러도 결정하기 어렵기 때문입니다. as3와 같은 동적언어가 아닌 엄격한 c라 할지라도 숫자형은 오토캐스팅을 기본으로 처리합니다. 따라서 이 구문은 컴파일 오류를 발생시키게 됩니다. 만약 컴파일 오류가 발생하지 않는다면 더욱 상황이 안 좋습니다. 함수에 정수형을 넣으면 overload목록중 int형이 실행되더라 같은 이상한 규칙을 외워야 하는 상황이 됩니다. 이 상황은 곧 급속도로 악화되어 Main에서 최초 로딩되는 swf의 내부에서 함수를 쓰면 정수를 인자로 보낸 경우 int를 인자로 받는 쪽이 호출되더라 라는 식으로 거의 엽기가 되어갑니다.

따라서 이 경우에는 아예 overload를 포기하고 함수를 2개 만드는 편이 좋습니다.

function doubleInt( $val:int ):Number{
	return $val*2;
}

function doubleNumber( $val:Number ):Number{
	return $val*2;
}

이 방식은 템플릿이 안되는 c에 흔히 등장하는 방식으로 openGL등 다양한 라이브러리에서도 채택하고 있는 스타일입니다. 여튼 코드 자체만 보면 overload가 제거되었습니다. 그럼 또 다른 예인 상이한 인자타입을 받는 경우를 볼까요.

function double( $val:Number ):Number{
	return $val*2;
}

function double( $val:DisplayObject ):Number{
	return $val.width*$val.height*2;
}

이 경우 double( 3 ), double( sprite ) 라는 식으로 확연히 구분된 인자가 들어가기 때문에 앞의 예와 같은 문제가 없습니다. 하지만 상이한 인자라는 것은 같은 이름의 함수지만 내부에서 매우 다른 일을 하고 있다는 반증이기도 합니다. 이 예에서 double이란 의미는 두 배로 한다 라는 추상적인 의미라면 Number를 받는 경우의 double은 숫자에 2를 곱한다로 정의할 수 있고 DisplayObject를 받는 경우에 넓이에 *2를 한다로 정의할 수 있습니다.

상세정의가 달라지는 마당에 double을 유지할 이유가 없습니다. 아래와 같이 바꾸는 게 현명한 처사입니다. 따라서 다음과 같은 코드가 됩니다.

function doubleNumber( $val:Number ):Number{
	return $val*2;
}

function doubleArea( $val:DisplayObject ):Number{
	return $val.width*$val.height*2;
}

위의 소스코드가 주는 교훈은 간단합니다. 인자의 형이 상이한 경우에도 overload를 쓸 가능성은 거의 없다는 것입니다. 따라서 인자의 수가 같은 경우 overload가 필요한 경우는 실질적으로 거의 없습니다.

인자의 수가 다른 경우

그럼 이제 인자의 수가 다른 경우를 살펴보겠습니다.

function sum( $val1:int, $val2:int ):Number{
	return $val1 + $val2;
}

function sum( $val1:int, $val2:int, $val3:int ):Number{
	return $val1 + $val2 + $val3;
}

이 형태에서 인자의 의미는 ‘차별성 없는 요소’가 됩니다, 즉 첫 번째 요소와 두 번째 요소는 덧셈에 사용될 재료라는 점에서 동일한 의미고 따라서 세 번째 요소도 마찬가지 의미입니다. 보통 이런 경우에는 인자를 배열로 받는 게 상책입니다.

function sum( $vals:Array ):Number{
	for( var i:int = 0, result:int = 0 ; i < $vals.length ; ++i ){
		result += $vals[i];
	}
	return result;
}

진정 의미가 있는 인자 수의 차이는 점층형 기능 확장 함수에서 발생합니다.

function load( $url:String ):void
function load( $url:String, $retry:int ):void
function load( $url:String, $retry:int, $timeOut:int ):void
function load( $url:String, $retry:int, $timeOut:int, $complete:Function ):void

위의 함수는 인자의 개수에 따라 상세적으로 처리될 수준이 달라집니다. 하지만 보통 overload된 함수는 중복을 제거하기 위해 가장 상세한 함수를 호출하도록 리펙토링하는게 일반적입니다. 따라서 위의 함수는 실제로는 다음과 같이 구현됩니다.

function load( $url:String ):void{
	load( $url, 5, 1000, null );
}
function load( $url:String, $retry:int ):void{
	load( $url, $retry, 1000, null );
}
function load( $url:String, $retry:int, $timeOut:int ):void{
	load( $url, $retry, $time, null );
}
function load( $url:String, $retry:int, $timeOut:int, $complete:Function ):void{
	//실제구현
}

아니 뭘 귀찮게 이렇게 쓰나요. 걍 짜피 맨날 저렇게 기술되는 게 일반적이라면 처음부터 그렇게 기술하는 게 편합니다.

function load( $url:String, $retry:int = 5, $timeOut:int = 1000, $complete:Function = null ):void{
	//실제구현
}

바로 이것이 as3에 도입되어있는 인자의 기본값이라는 기능입니다. 따라서 인자의 기본값은 일단 설정되면 그 다음에 나오는 인자가 전부 기본값이 있어야 한다는 가정도 자연스레 이해됩니다.

결론

오늘은 왠지 집중이 잘 안되는 날이군요(어제 5시에 집에 들어갔더니…) 해야 할 말이 두 배 정도는 많은데 마구 마감해버렸습니다.

요점은 as3가 지원하는 기본값은 overload의 일반적인 리펙토링 형태로 오히려 overload를 지원하는 언어에서 노가다 해야 할 일을 줄여주는 역할을 합니다.

따라서 기본값을 쓰려고 하신다면 전체적인 함수의 사용을 고려하여 상세화 수준을 결정하는 게 맞는지(기본값을 쓰는 게 맞는지) 새로운 함수를 분리하는 게 맞는지 잘 고민해야 합니다.

일반적으로 overload, override, 기본값 등은 함수가 구조적으로 잘못되었다는 걸 암시하고 있습니다. 어쩔 수 없는 경우가 아니라면 되도록 함수 자체를 기능에 적합하게 새로 만드는 방안을 생각하는 편이 좋습니다.

Browser does not supports flash movie


관련된 글:

  1. Cstack
  2. swf로 ajax를 대신하기
  3. 함수인자와 null, undefined의 관계
  4. CcircleStack
  5. CSpvLoader 핵심 로직 설명

4 Comments

  1. 하늘아래 says:

    음 그냥 if 문으로 들어온 데이터형에 따라 다른 처리를 해줘도 되지 않을까요?

  2. vulcan says:

    프레임웍 차원에서 제공되는 메서드가 있을때 하위 프로젝트에서는 이 메서드를 override하여 사용하게 됩니다.
    하지만 하위 프로젝트에서 어찌어찌하여 메서드에 인자를 하나더 건네주고 싶을때가 있습니다.
    이런 경우 override한 메서드 하나와 overload된 새로운 메서드하나 이렇게 병행해서 쓰면
    참 좋을텐데 하는 생각을 많이 갖습니다.
    특히 프레임웍 차원의 메서드를 수정할 수 없는 경우엔 빙~돌아가야 하는 경우도 생기게 되는것 같습니다.
    어차피 코드의 질은 코더의 몫이니까 있어도 좋지 안겠냐는 입장입니다.

    • admin says:

      일단 override만 얘기하면 그 기능 자체야 큰 문제는 없습니다. 훅이나 템플릿으로 씌여야만 하기도 하고. 단지 구현을 강제할 수 없다는 점이 as3의 단점입니다. 따라서 as3언어차원에서 virtual 같은 키워드가 제공되지 않는다면 프레임 웍을 상속받아 구상하는 개발자의 경우 어디까지 메서드를 재구현할지 애매하게 됩니다. as3가 현재 언어차원에서 함수구상을 강제하는 수단은 인터페이스밖에 없으므로 역할을 분리하여 클래스와 인터페이스를 동시에 구상하도록 하는 수밖에 없지만 이것도 복잡하기 때문에 인터페이스를 무조건 구상하고 액션객체를 소유하는 스타일을 많이 씁니다.
      이는 자바의 후기 개발기조를 반영한 것으로 as3가 만들어질 당시의 ECMA위원회의 머릿 속이 뭐가 들었나를 알 수 있게 해줍니다(짝퉁같은..)

      그리고 원래 이넘의 ecmascript는 좀 교조적인 언어입니다. 규칙이 있다기보다 위원회가 만들었으니 이 방식대로 써라라는게 구지 오버로딩을 보지 않아도 언어 곳곳에 있습니다.

      하지만 그렇다고 해도 프레임웍에서 오버로딩을 허용하여 다시 상속층을 갖게 되면 그것만큼 난해한 것도 없습니다. 또한 프레임웍이 커질 수록 상속모델을 사용하게 되면 쓰지도 않으면서 포함되는 클래스 수가 기하급수적으로 많아져 큰 용량을 쓸데 없이 차지합니다.

      대표적인 사례가 flex입니다. 단지 alert창을 띄우는데 500k를 먹습니다. alert창을 띄우기 위해 직접적으로 관련된 클래스도 있지만 프레임웍에 소속되어 상속층을 타기 때문에 메모리에선 한번도 올라오지 않는데 applicationDomain에 한가득 잡혀있는 클래스들 덕분에 저런 용량이 되죠.

      이보다 훨씬 광대한 클래스군을 갖고 있는 MFC의 경우는 절대로 상속구상모델을 사용하지 않습니다. 그랬다간 용량이 감당안되기 떄문이죠.
      그렇다고 MFC구조가 아름답다고는 절대로 말씀못드리겠습니다만, 반대로 오버로딩, 오버라이딩이 심한 프레임웍일 수록 용량이 커질건 분명한 사실이죠.

Leave a Reply