티스토리 뷰

반응형

관심사의 분리(SoC)는 소프트웨어 개발에서 가장 기본적인 원칙 중 하나이며, SOLID 원칙 5개 중 2개(단일 책임 및 인터페이스 분리)가 이 개념에서 직접 파생될 정도로 매우 중요합니다.

 

원칙은 간단합니다. 프로그램을 하나의 단일 블록으로 작성하지 말고 작은 조각으로 나누어 각각 간단한 개별 작업을 완료할 수 있도록 만드는 것입니다.

 

이 글에서, 이 원리를 추상화 수준부터 실제 적용하는 부분까지 설명을 하도록 하겠습니다. 

SoC for programming functions

가장 낮은 레벨(실제 프로그래밍 코드)에서 SoC는 긴 복잡한 함수를 쓰지 말라고 이야기하는 것입니다. 함수의 크기가 커지기 시작하면 함수가 너무 많은 작업을 처리한 다는 것이고, 이는 함수를 분리 해야하는 신호입니다.

 

이러한 경우 SoC에서는 이를 리팩터링하여 더 간결한 모양으로 만들어야 합니다. 이 프로세스 중에 원래 알고리즘의 일부가 내보내지고 개인 액세스 수준을 가진 별도의 작은 함수로 캡슐화됩니다. 이렇게하면, 코드가 명료해지고 다른 부분에서 재사용할 수 있게 됩니다.

 

복잡한 코드

    internal class Program
    {
        private static int[] _numbers = new int[] { 1, 3, 5, 7, 6, 2 };

        private static int GetMinMaxSum(int[] numbers)
        {
            var sortedNumbers = numbers.OrderBy(n => n).ToList();
            var min = sortedNumbers.Count == 0 ? 0 : sortedNumbers[0];
            var max = sortedNumbers.Count == 0 ? 0 : sortedNumbers[sortedNumbers.Count - 1];
            return min + max;
        }

        private static void Main(string[] args)
        {
            Console.WriteLine($"Numbers : {string.Join(',', _numbers)}");
            Console.WriteLine($"MinMaxSum : {GetMinMaxSum(_numbers)}");
        }
    }

SoC 리팩터링된 코드

    internal class Program
    {
        private static int[] _numbers = new int[] { 1, 3, 5, 7, 6, 2 };

        private static int GetMaxOfSortedNumbers(int[] sortedNumbers)
        {
            if (sortedNumbers.Length == 0)
                return 0;
            return sortedNumbers.Last();
        }

        private static int GetMinMaxSum(int[] numbers)
        {
            var sortedNumbers = numbers.OrderBy(n => n).ToArray();
            var min = GetMinOfSortedNumbers(sortedNumbers);
            var max = GetMaxOfSortedNumbers(sortedNumbers);
            return min + max;
        }

        private static int GetMinOfSortedNumbers(int[] sortedNumbers)
        {
            if (sortedNumbers.Length == 0)
                return 0;
            return sortedNumbers.First();
        }

        private static void Main(string[] args)
        {
            Console.WriteLine($"Numbers : {string.Join(',', _numbers)}");
            Console.WriteLine($"MinMaxSum : {GetMinMaxSum(_numbers)}");
        }
    }

SoC for modules

조금 더 높은 수준에서 이 원칙은 각각 논리적 상관관계가 분명한 단일 작업 세트를 만들고, 모듈하에서 함수를 그룹화하도록 합니다.

 

이 프로세스는 우리가 함수에 대해 수행해야 했던 것과 매우 유사합니다. 덜 밀접하게 관련된 기능을 분리하고 동일한 고유한 목적을 제공하는 기능들을 그룹화합니다.

 

수직과 수평 모듈 분할 예시

Cohesion and Coupling

SoC의 적용은 결합의 감소와 응집력의 증가라는 두 가지 과정을 포함합니다.

 

응집력은 직무의 집합, 세부사항의 수준 및 지역별 유사성의 척도입니다. 예를 들어, drawCircle 및 drawTriangle 함수는 도면을 담당하는 동일한 모듈에 속할 만큼 응집력이 있으며, 이 두 함수를 코드(높은 유사성 ~ 높은 응집성)에 서로 가깝게 배치하는 것은 자연스러운 것입니다.

 

반면에 커플링은 시스템의 나머지 부분에 대한 부품의 의존도를 측정하는 것입니다(저의존도 ~ 느슨한 커플링).

 

앞서 언급한 drawCircle과 drawTriangle은 또 다른 함수 drawCybertruck에 의해 사용될 수 있습니다. 우리는 이 기능을 드로우 모듈에 넣을 수 있지만, drawCybertruck이 물리 엔진과 외부 상태에 의존한다면. 이 것으로 인해 전체 드로우 모듈의 재사용률이 낮아지고 몇 가지 다른 필수 구성요소를 가지게 될 수 있습니다.

 

그래서, 원시 드로잉 함수와 drawCybertruck은 추상화 및 논리 복잡성이 다른 수준에 속하므로 서로 다른 모듈에 상주해야 한다는 것을 알 수 있습니다.

 

어느날 우리가 다른 프로젝트에서 드로잉 모듈을 사용하기로 결정한다고 했을 때, 이 모듈이 물리 엔진에 의존하지 않기 때문에 더 쉽게 사용할 수 있을 것입니다.

 

구현 목표

  • Decoupling is good - loose coupling을 목표로 해야합니다.
  • Cohesive code is good - 높은 응집력을 목표로 해야합니다.

알고리즘의 논리를 작성할 때 기능이나 모듈 사이를 뛰어다니게 된다면, 이것은 코드의 응집도가 낮다는 것을 의미하는데, 이것은 종종 스파게티 코드라고 불립니다.

Benefits of the Loose Coupling and High Cohesion

관심사의 분리 원칙을 준수하면 코드의 다양한 특성을 개선할 수 있습니다.

  • 더 나은 코드 명료성. 각 모듈이 논리적으로 범위가 지정된 일련의 메소드를 가진 간결하고 명확한 API를 가질 때 프로그램에서 어떤 일이 벌어지는지 이해하는 것이 훨씬 더 쉽습니다.
  • 코드 재사용성 향상(DRY 원리). 코드를 재사용하는 주된 이점은 유지관리 비용 절감입니다. 기능을 확장하거나 버그를 수정해야 할 때마다 한 곳만 수정을하는 것이 훨씬 덜 고통스럽습니다.
  • 더 나은 테스트 가능성. 적절한 범위의 기능과 앱의 나머지 부분으로부터 격리된 독립 모듈을 테스트하는 것이 더 쉽습니다. 모듈이 어떻게 작동하는지 보기 위해 전체 환경을 파악할 필요는 없습니다. 인접한 실제 모듈을 더미 mock 또는 가짜 데이터 소스로 교체하기만 하면 충분합니다. 이렇게 수정한 후 출력하여 모듈을 블랙박스로 테스트하거나 연결된 모듈(BDD)에서 호출되는 메서드를 확인하여 화이트박스로 테스트할 수 있습니다.
  • 프로젝트 개발 및 유지보수 성능이 좋아집니다. 새로운 기능이든 기존 기능이든 모듈 격리는 프로그램의 변경사항으로 영향을 받을 수 있는 영역을 범위를 지정하는 데 도움이 되며, 따라서 개발 속도가 빨라집니다.
  • 여러 엔지니어에 의한 동시 개발이 더 용이합니다. 서로 간섭하지 않도록 하기 위해 어떤 모듈을 진행 중인지 합의하면 됩니다. 모듈의 API 업데이트만이 다른 개발자에게 명시적으로 알리는 플래그가 될 수 있으며, 대부분의 변경사항은 다른 제공자의 즉각적인 주의 없이 추가할 수 있습니다. 우수한 테스트 적용 범위와 결합되면 병렬 개발은 단독으로 작업하는 각 개별 엔지니어의 누적 생산성만큼 효율적이 됩니다(보통 더 느림).
결합과 응집력은 궁극적으로 프로그래머의 관점에서 코드 작업의 편의성에 영향을 미치는 특성입니다.

References :

Separation of Concerns in Software Design - Alexey Naumov (nalexn.github.io)

관심사의 분리 SoC(Separation Of Concerns) - Gibongs Blog (gyim1345.netlify.app)

반응형
댓글