[JS] Inversion of Control(IoC)

Suyeon·2021년 7월 11일
2
post-thumbnail

원문: Inversion of Control by Kent C. Dodds

상황

개발을 하다보면, 아래와 같은 시나리오를 겪어본적이 있을 것이다.

  1. 재사용 가능한 클린 코드 작성 (컴포넌트, hook등)
  2. 해당 코드에 대한 새로운 기능 요청
  3. 1에서 작성한 코드에 추가 로직을 작성
  4. 1~3 반복..

결국 가장 처음 작성했던 재사용성 높았던 클린 코드는, 더이상 클린 코드가 아니게 되며 시간이 지날수록 유지보수가 어려워진다.

Invertion of Control 소개

이러한 시나리오를 방지하기 위해서, kent는 Inversion of Control 패턴을 적용한 리액트 패턴을 소개한다.

...in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code. -- Wikipedia

Ioc는 이름 그대로, 함수의 control을 역전 시키는 기법이다. Ioc을 통해, 우리의 코드에서 추상화(abstraction)는 일을 덜 하게 만들고, 대신 유저가 일을 하게 만들 수 있다.

대부분의 경우, 추상화는 아주 유용하다. 추상화를 통해 복잡한 디테일은 감추고, 나머지 코드를 간결하고 클린하게 유지할 수 있다. 하지만 위에 묘사한 상황같이, 때로는 추상화가 우리의 생각만큼 완벽하게 들어맞지 않을 때도 있다는 점을 유의해야한다.


예시

기존 코드

Array.prototype.filter 메소드가 없다고 가정하고, filter 메소드를 직접 구현한다고 가정해보자.
아래의 함수는 null, undefined를 필터링한다.

function filter(array) {
  let newArray = []
  for (let index = 0; index < array.length; index++) {
    const element = array[index]
    if (element !== null && element !== undefined) {
      newArray[newArray.length] = element
    }
  }
  return newArray
}

// Call
filter([0, 1, undefined, 2, null, 3, 'four', ''])
// [0, 1, 2, 3, 'four', '']

만약 우리가 앞서 작성한 코드에서, nullundefined뿐만 아니라, '', 0도 필터링 하고 싶다면, 함수의 두번째 인자로 option을 전달할 수 있다.

function filter(
  array,
  {
    filterNull = true,
    filterUndefined = true,
    filterZero = false,
    filterEmptyString = false,
  } = {},
) {
  let newArray = []
  for (let index = 0; index < array.length; index++) {
    const element = array[index]
    if (
      (filterNull && element === null) ||
      (filterUndefined && element === undefined) ||
      (filterZero && element === 0) ||
      (filterEmptyString && element === '')
    ) {
      continue
    }
    newArray[newArray.length] = element
  }
  return newArray
}

// Call 
filter([0, 1, undefined, 2, null, 3, 'four', ''])
// [0, 1, 2, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], {filterNull: false})
// [0, 1, 2, null, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], {filterUndefined: false})
// [0, 1, 2, undefined, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], {filterZero: true})
// [1, 2, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], {filterEmptyString: true})
// [0, 1, 2, 3, 'four']

위의 코드는 문제 없이 잘 동작하지만, 옵션의 대상이 많아질수록 코드는 방대해지고 추후에 디버깅을 하기도 까다로워진다.

IoC 적용

자, 이제 IoC를 적용해서 다시 코드를 작성해보자!

더이상 필터링할 대상을 외부로 부터 전달받아서 abstraction 안에서 필터링하지 않고, 필터링 함수를(filterFn) 외부로 받아옴으로써, 책임을 내부에서 외부로 역전시켰다.

function filter(array, filterFn) {
  let newArray = []
  for (let index = 0; index < array.length; index++) {
    const element = array[index]
    if (filterFn(element)) {
      newArray[newArray.length] = element
    }
  }
  return newArray
}

// (1) 
filter(
  [0, 1, undefined, 2, null, 3, 'four', ''],
  el => el !== undefined && el !== null && el !== '',
)
// [0, 1, 2, 3, 'four']

// (2) 아래와 같이 복잡한 필터링도 손쉽게 구현할 수 있다.
filter(
  [
    {name: 'dog', legs: 4, mammal: true},
    {name: 'dolphin', legs: 0, mammal: true},
    {name: 'eagle', legs: 2, mammal: false},
  ],
  animal => animal.legs === 0,
)
// [{name: 'dolphin', legs: 0, mammal: true}]

우리가 처음에 작성했던 코드에 Ioc를 결합해서 사용할 수도 있다.

// (1) 옵션과 함께 사용 
function filterWithOptions(
  array,
  {
    filterNull = true,
    filterUndefined = true,
    filterZero = false,
    filterEmptyString = false,
  } = {},
) {
  return filter(
    array,
    element =>
      !(
        (filterNull && element === null) ||
        (filterUndefined && element === undefined) ||
        (filterZero && element === 0) ||
        (filterEmptyString && element === '')
      ),
  )
}

// (2) 다른 예시
function filterByLegCount(array, legCount) {
  return filter(array, animal => animal.legs === legCount)
}

filterByLegCount(
  [
    {name: 'dog', legs: 4, mammal: true},
    {name: 'dolphin', legs: 0, mammal: true},
    {name: 'eagle', legs: 2, mammal: false},
    {name: 'elephant', legs: 4, mammal: true},
    {name: 'robin', legs: 2, mammal: false},
    {name: 'cat', legs: 4, mammal: true},
    {name: 'salmon', legs: 0, mammal: false},
  ],
  0,
)
// [
//   {name: 'dolphin', legs: 0, mammal: true},
//   {name: 'salmon', legs: 0, mammal: false},
// ]
profile
Hello World.

1개의 댓글

comment-user-thumbnail
2021년 7월 26일

좋은 공부되었습니다. 감사합니다!

답글 달기