프론트 공부

관심사 분리 (feat: 관심사 분리와 수직분리, 수평분리) 1편

홍구리당당 2023. 12. 16. 01:04

어떻게 폴더 구조를 짤까...

0. 오늘의 배울 것

프로젝트를 짜다보면 폴더 구조를 어떻게 나눠야 할지 자꾸 고민이 된다.

물론 리액트 공식문서 측에서는 폴더 구조에 대해 너무 신경을 쓰지 말라고는 하지만, (폴더 구조에 너무 시간 들이느라 다른 일을 미루는 멍청한 짓 - 배보다 배꼽이 커지는 일 - 을 하지 말라는 뜻인듯.) 그래도 난 여전히 신경이 쓰인다.

그러다 알게 된 개념이 바로 관심사 분리, seperation of concerns 이다.

이번엔 이 관심사 분리가 무엇인지 알아보고 이를 폴더 구조에 적용하는 방법에 대해 알아볼 것이다! 그런데 양이 많으니 쪼개서 1편은 관심사 분리의 개념, 2편은 수직 분리를 폴더 구조에 적용하기, 3편은 수평 분리를 볼더 구조에 적용하기 내용으로 써보겠다.

1. 관심사 분리?

관심사 분리, SoC (seperation of concern)이란 소프트웨어 상에서 구조를 패턴, 역할, 기능 등을 섹션 별로 분리해 작성하는 것이다.간단히 말하자면 코드를 쓸 때 역할이나 기능, 패턴 등으로 잘개 쪼개서 분리하여 쓰라는 디자인 원칙이다. 이 때 SoC의 적용은 결합도를 감소시키고 응집도를 증가시키는 방향으로 나아가야 한다.

관심사가 뭔데??

관심사란, 컴퓨터 프로그램 코드에 영향을 미치는 정보의 집합을 말한다. 쉽게 말하자면 하나의 모듈이 수행하고자 하는 목적 이다!! 여기서 모듈은 함수나 클래스 등 하나의 단위라 여겨도 좋다.

예시로 아래의 코드를 보자.

// 관심사 분리가 안 된 코드
// numArr 숫자 배열 내 가장 큰 값에서 가장 작은 값을 뺀 값을 리턴하는 함수.
function calc_not_SoC(numArr){
  // 관심사 1. 정렬 기능
  numArr = numArr.sort((a, b)=>a-b);
  // 관심사 2. 최솟값 기능
  let minNum = numArr[0];
  // 관심사 3. 최댓값 기능
  let maxNum = numArr[numArr.length - 1];
  // 관심사 4. 최댓값 + 최솟값 리턴 기능
  return (minNum + maxNum);
}

//관심사 분리를 한다면?
// 관심사 1. 정렬 기능
function sortArr(arr){
  return arr.sort((a-b)=>a-b);
}

// 관심사 2. 최솟값 기능
function getMin(arr){
  return arr[0];
}

// 관심사 3. 최댓값 기능
function getMax(arr){
  return arr[arr.length -1];
}

// 관심사 4. 최댓값과 최솟값을 더하기.
function calc_SoC(numArr){
  // 관심사 1~3은 다른 함수들에게 맡긴다.
  sortedArr = sortArr(numArr);
  let minN = getMin(sortedArr);
  let maxN = getMax(sortedArr);
  return (minN + maxN);
}

위의 예제는 간단한 코드라 실제로는 이렇게까지 관심사를 나누진 않는다. 그냥 관심사를 어떻게 분리할지 정도를 눈여겨 보자.

cohesioin과 coupling

관심사 분리를 적용할 때엔 응집도(cohesion)과 결합도(coupling) 이 두 가지 개념을 알아야 한다!!

  1. 응집도, cohesion: 한 프로세스의 집합들 내부 시스템들이 얼마나 유사한지?
  2. 결합도, coupling: 한 시스템이 다른 외부 시스템에게 얼마나 의존하는지?

꿀팁. 결합도 낮추는 법?

A 기능을 쓰기 위해 B 기능에서부터 return 값을 받아와 쓴다면, A, B 기능은 결합도가 높다. 이 둘을 떨어뜨리기 위해 그 사이에 추상화된 인터페이스를 만들어주면 좋다.

예를 들어 dog 필드와 feedDog, washDog 메소드를 가진 User 클래스가 있다고 하자. 그리고이 메소드들은 Dog 클래스로부터 정보값을 가져와 사용한다고 해보자.

const Dog = {
  name: "choco"
}
const User = {
  dog: Dog.name,
  feedDog: function (){
      console.log(`feeding ${Dog.name}`);
  }
  washDog: function (){
      console.log(`washing ${Dog.name}`);
  }
}

만약 Dog가 아니라 Cat을 키운다면?

만약 Dog도 키우고 Cat도 키운다면?

Dog와 User은 서로 결합도가 너무 높아서, 수정하기 힘든 면이 있다. 따라서 Dog와 User 사이 징검다리 역할을 해줄 interface를 만들어 결합도를 낮추자.

interface Pet{
    name: string;
}
const Dog: Pet = {
  name: "choco"
}
const User = {
  pet: Dog.name,
  feedPet: function (){
      console.log(`feeding ${Dog.name}`);
  }
  washPet: function (){
      console.log(`washing ${Dog.name}`);
  }
}

2. 관심사 분리를 하는 이유?

프로그램을 짜다보면 점점 코드 양이 늘어나 내가 어디에 무슨 함수를 짰는지, 어떤 기능을 구현해야 했는지 등을 까먹을 수도 있다. 생각나는대로 마구잡이로 짜다보면 내 코드가 스파게티 코드, 에일리언 코드가 되는 걸 느낄 수가 있다.

이를 막기 위해 SoC를 적용한다!!

하나의 모듈에 하나의 목적(관심사)만 가지게 되다보니, 내가 이 모듈을 수정할 때엔 해당 기능만 신경 쓰면서 고치면 된다는 것이다. 예를 들어 한 모듈 안에 기능 A, 기능 B, 기능 C ... 기능 Z 를 몽땅 몰아넣었다고 생각하자. 만약 a 코드를 수정하고 싶다면 해당 파일로 가서 그 많은 코드들 중 기능 A 부분 코드를 찾아야 하고, 해당 기능이 다른 기능에 영향을 미치는 의존성 높은 코드라면 그 부분도 수정하느라 고생 깨나 할 것이다.

SoC를 적용하면 최소한의 기능, 관심사 단위로 잘개 쪼개 분리하여 결합도를 낮추되 비슷한 관심사를 가진 시스템들을 폴더 별로 묶어 관리해서 응집도를 높이는 식으로 코드를 관리하고 유지보수할 수 있다. 즉!! SoC는 소프트웨어의 유지보수, 변화에 유연하게 대응할 수 있게 해준다

3. 관심사 분리와 비슷한 개념들...

관심사의 분리는 sw 프로그래밍에서 가장 기본적인 원칙이다. 그래서 다른 프로그래밍 원칙이나 패턴을 찾아볼 때 비슷한 개념들을 발견할 수 있다.

단일 책임 원칙:

정보 처리 기사에서 주구장창 나오는 그 원칙. 여기서 '책임'을 '관심사' 라 생각하면 된다. 각 모듈들은 각기 하나의 책임만 가져야 한다는 원칙. single Responsibility principle, SRP 라고도 줄여서 말한다.