brunch

You can make anything
by writing

C.S.Lewis

by 이남주 NJ Namju Lee Apr 15. 2020

프로그래밍 구조와 패턴 2) 스콥 & 흐름

Computational Design

이번 시간에는 프로그램의 기본적인 흐름과 그에 따른 범위를 어떻게 나누는 것이 좋을까?라는 다소 추상적인 이야기를 나누어봐요. 끝까지 보시면 이해되실 거예요!


프로그래밍과 패턴 그리고 흐름


CAD 프로그램을 만들 때(소프트웨어 아키텍처)를 할 때,  디자인 패턴 혹은 OOP 프로그래밍에 대한 경험이 있으신 분들은 무의식적으로 코드를 작성하면서 펑션들을 나누고 합치고 하면서 종합적인 사고를 하시면서 코드 작성하고  흐름을 디자인하시죠.


따라서, 아주 간단한 상황을 놓고 어떤 장단점이 있는지 간단히 알아봐요.


하나의 펑션 A 이 있다고 가정을 해보죠. 그리고 10개의 Points를 loop 하면서 각각의 Points를 바탕으로 Circle을 만들려고 합니다.


List <Circle> GetCircles() { // Using RhinoCommon

    List <Circle> cls = new List <Circle>();

    for(int i = 0 ; i < 10; ++i ) {

        Point 3 d p = new Point3d(i, 0, 0);

        Plane plane = p.GetPlane();

        Circle c = new Circle(plane, 2.5);

        cls.Add(c);

    }

    return cls;

}


다시 설명하면 GetCircle 펑션을 실행하면, 내부적으로 (1) cls라는 리스트를 만들고 (2) 10번의 루프를 돌게 되죠. 그 각각의 루프 안에서 (3) 포인트를 생성하고, (4) 포인트에서 플레인을  생성하고 (5) 플레인을 ㅂ ㅏ탕으로 원을 생성한 후, (6) 앞서 만들어 놓은 리스트에 원을 담죠. (7) 그리고 그 결과 값으로 모든 원들이 담긴 리스트를 리턴 시키면서 평션이 마무리가 되죠.


위의 코드의 장점은 무엇일까요?


굉장히 콤펙트 하게 설계되어 시각 복잡도 O(n^2)의 간결한 코드가 나오죠. 즉 for loop를 한번 순차적으로 돌면서 그 안에서 모든 일들을 마무리하게 되는 것이죠.


그렇다면 단점은 무엇일까요?

만약, 반복 문안에서 그 포인트를 바탕으로 다른 일들을 해야 할 경우 우리는 포인트만 따로 생성하는 루프를 돌고, 그다음에 플레인을 만들 수 있겠죠. 가령 아래와 같겠죠.


List<Circle> GetCircles() { // Using RhinoCommon

    List<Point3d> pts = new List<Point3d>();

    for(int i = 0 ; i < 10; ++i ) {

        Point3d p = new Point3d(i, 0, 0);

        pts.Add(p);

    }

    List<Circle> cls = new List<Circle>();

    for(int i = 0 ; i < 10; ++i ) {

        Point3d p = pts[i];

        Plane plane = p.GetPlane();

        Circle c = new Circle(plane, 2.5);

        cls.Add(c);

    }

    return cls;

}


결국 두 번의 루프 롤 돌면서 (1) 포인트를 생성하는 루프 (2) 플레인과 원을 생성하는 루프로 나누어지겠죠? 위의 코드의 단점은 시각 복잡도가 상승한 것이죠. 하지만 디버깅을 하거나 포인트를 가지고 다른 무언가를 할 경우 우리는 코드 자체를 명백하게 다른 루프로 만들었기 때문에, 중간에 개입해요 다른 알고리즘을 적용할 수 있게 되는 것이죠.


조금 더 잘게 쪼개 볼까요?


List<Circle> GetCircles() { // Using RhinoCommon

    List<Point3d> pts = new List<Point3d>();

    for(int i = 0 ; i < 10; ++i ) {

        Point3d p = new Point3d(i, 0, 0);

        pts.Add(p);

    }


    List<Plane> planes = new List<Plane>();

    for(int i = 0 ; i < 10; ++i ) {

        Plane plane = pts[i];

        planes.Add(plane);

    }


    List<Circle> cls = new List<Circle>();

    for(int i = 0 ; i < 10; ++i ) {

        Plane plane = planes[i];

        Circle c = new Circle(plane, 2.5);

        cls.Add(c);

    }

    return cls;

}


3번의 for loop를 반복함으로써 원을 생성하게 되는 코드인데요. 1) 포인트를 생성하고 2) 플레인을 생성하고 3) 플레인을 기준으로 원을 생성한 후 리턴하게 되는 것이죠. 어떻게 보면 비 효율적일 수 있죠. 시간 복잡도는 한 번의 반복으로 해결될 수 있는 문제를 3번의 반복문을 통해서 해결했으니까요(O(n) + O(n) + O(n)  = 3 O(n) ). 하지만 3개의 단계가 명백하게 분리됨으로써 따로따로 뜯어서 사용될 수 도 있고, 변형과 조합이 좀 더 유리해졌죠.


각각의 반복문을 펑션으로 만들어 볼까요?


 List<Point3d> GetPoints() { // Using RhinoCommon

    List<Point3d> pts = new List<Point3d>();

    for(int i = 0 ; i < 10; ++i ) {

        Point3d p = new Point3d(i, 0, 0);

        pts.Add(p);

    }

    return pts;

}


List<Plane> GetPlanes(List<Point3d> pts) { // Using RhinoCommon

    List<Plane> planes = new List<Plane>();

    for(int i = 0 ; i < 10; ++i ) {

        Plane plane = pts[i];

        planes.Add(plane);

    }

    return planes;

}


List<Circle> GetCircles(List<Plane> planes) { // Using RhinoCommon

    List<Circle> cls = new List<Circle>();

    for(int i = 0 ; i < 10; ++i ) {

        Plane plane = planes[i];

        Circle c = new Circle(plane, 2.5);

        cls.Add(c);

    }

    return cls;

}


그리고 메인 함수에서 각각의 펑션을 수행함으로써 프로그래밍을 좀 더 깔끔하고 명맥 하게 작성할 수 있죠.


Void Main() {

    List<Point3d> pts = GetPoints();

    List<Plane> planes = GetPlanes(pts);

    return GetCircles(planes);

}


재활용성, 간결성


3줄로 각각의 오퍼레이션을 이행하고 결과물을 가져와서 또 다른 펑션을 수행함으로써 관리 차원에서 좀 더 직관적으로 코드를 관리할 수 있을 뿐 아니라, 다른 알고리듬을 작성할 때도, 각각의 펑션을 재사용도 쉽게 할 수 있게 됐죠! 



위의 예제를 이해했다고, 소프트웨어의 아키텍처나 파이프 라인을 디자인할 수 있는 건 아니에요. 디자인 패턴, 클래스의 추상화 상속 등등을 이해하고, 수많은 실수와 반복을 거쳐 좀 더 합리적이고, 효율적인 디자인 툴을 만들 수 있게 되는 것이죠.


트레이드 오프 / trade off


하지만 위의 예제들의 이해를 통해서, 어떤 부분을 버리고, 취할 것인지, 장단점을 이해하고 전략적으로 디자인 알고리즘을 짜는 훈련을 시작한다면, 중장기적으로 좋은 습관일 가질 수 있어요.



revision 01 -2020/4/12

틀린 부분은 댓글로 남겨주세요!

매거진의 이전글 프로그래밍 구조와 패턴 1) 시간 복잡도?
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari