본문 바로가기

SW 이야기

여러분 프로그래밍 언어는 이렇게 할 수 있어요?

여러분 프로그래밍 언어는 이런거 됩니까 

(2004년 3월 2일 화요일)


어느 날, 여러분은 코드를 훑어보다가 언뜻 보기에 거의 똑같은 코드 블록들을 발견 합니다.

// 시시한 예제: alert("I'd like some Spaghetti!"); alert("I'd like some Chocolate Moose!");

코드 중복은 나쁜거니까, 함수를 만듭니다.
    function SwedishChef( food )
    {
        alert("I'd like some " + food + "!");
    }
	
    SwedishChef("Spaghetti");
    SwedishChef("Chocolate Moose");

이 코드는 처음보다 더 낫습니다. 유지보수성, 가독성, 추상화 등등의 이유 때문에.


다른 예제를 살펴보겠습니다.

    alert("get the lobster");
    PutInPot("lobster");
    PutInPot("water");

    alert("get the chicken");
    BoomBoom("chicken");
    BoomBoom("coconut");

중복을 없애려면 함수를 다른 함수의 인자로 넘겨야 합니다. 함수를 인자로 넘기는 능력은 굉장히 중요합니다.

함수를 인자로 넘길 수 있다면 좀 더 깊은 수준에서 중복된 코드를 찾아 제거할 수 있습니다.

    function Cook( i1, i2, f )
    {
        alert("get the " + i1);
        f(i1);
        f(i2);
    }

    Cook( "lobster", "water", PutInPot );
    Cook( "chicken", "coconut", BoomBoom );

보세요! 우리는 방금 함수를 인자로 넘겼습니다. 

여러분의 언어는 이렇게 할 수 있나요?


잠시만... 하나 더.

함수 PutInPot, BoomBoom을 미리 정의해야 할까요?

이 함수들을 다른 곳에서 선언하지 않고, 인라인으로 정의해서 넘기면 더 멋지지 않을까요?

Cook( "lobster", 
          "water", 
          function(x) { alert("pot " + x); }  );
    Cook( "chicken", 
          "coconut", 
          function(x) { alert("boom " + x); } );

와우, 정말 깔끔하군요. 필요한 시점에 함수를 바로 정의해서 다른 함수에 인자로 넘겼습니다.

함수 이름을 짓는 귀찮은 짓을 하지 않아도 됩니다.


몇 걸음 더 나가 봅시다.

아래와 같은 코드가 여기저기 흩어져 있군요.

배열 각 요소에 뭔가를 하는 작업 말이에요

    var a = [1,2,3];
	
    for (i=0; i<a.length; i++)
    {
        a[i] = a[i] * 2;
    }
	
    for (i=0; i<a.length; i++)
    {
        alert(a[i]);
    }

배열 모든 요소에 뭔가를 하는 작업은 공통이니까, 다음과 같은 함수를 만듭니다.

    function map(fn, a)
    {
        for (i = 0; i < a.length; i++)
        {
            a[i] = fn(a[i]);
        }
    }

이제 이 함수를 사용하면 위 코드는 이렇게 됩니다.

    map( function(x){return x*2;}, a );
    map( alert, a );


(마지막입니다 집중 하세요)

배열의 모든 값을 더하는 또 다른 예제입니다.

    function sum(a)
    {
        var s = 0;
        for (i = 0; i < a.length; i++)
            s += a[i];
        return s;
    }
    
    function join(a)
    {
        var s = "";
        for (i = 0; i < a.length; i++)
            s += a[i];
        return s;
    }
    
    alert(sum([1,2,3]));
    alert(join(["a","b","c"]));


sum과 join은 거의 똑같아요. 

추상화 하고 싶으니까, 공통(generic) 함수를 만듭니다.

    function reduce(fn, a, init)
    {
        var s = init;
        for (i = 0; i < a.length; i++)
            s = fn( s, a[i] );
        return s;
    }
    
    function sum(a)
    {
        return reduce( function(a, b){ return a + b; },  a, 0 );
    }
    
    function join(a)
    {
        return reduce( function(a, b){ return a + b; },  a, "" );
    }

(끝!)

(이 까지는 내가 아는 부분 이었고, C/C++을 사용하는 나는 별로 와닿지 않았다.

그래서 뭐?)


...중략


모든 배열 요소 각각을 대상으로 연산을 수행하는 하찮은 부분을 추상화해낼 수 있는 능력이 뭐 그리 대단하다고 난리를 치냐고요?

위의 map함수를 다시 보면,

모든 배열 요소 각각을 대상으로 연산을 수행한다면, 작업 순서는 전혀 문제되지 않습니다.

그렇죠? CPU가 n개일 때, 각 CPU가 배열을 1/n씩 나누어 처리하도록 코드를 작성했더니 

브라보, 갑자기 map 함수가 n배 빨라집니다.


만약, 인터넷에 올라온 모든 웹 페이지를 담을 만큼 엄청나게 큰 배열이 있다면?

모든 웹 페이지를 대상으로 검색을 한다면?


단순 문자열 검색 함수를 하나 만들어 수만 대 서버에서 돌아가는 map함수에 인자로 넘기기만 하면 

수만 대 서버가 일을 나눠서 하기 때문에 번개같이 결과가 튀어 나옵니다.

와우!

..........................................................


이것이 구글의 맵리듀스 개념이라고 한다.

조엘이 2004년에 작성한 이 글을 이제야 읽고 이제야 이런 개념을 이해를 하다니...

나의 문제인가, 우리나라 IT 회사/문화의 문제인가,







<출처>

1. http://www.joelonsoftware.com/items/2006/08/01.html

2. 조엘온 소프트웨어를 넘어서, 221p