Published on

객체에 대한 쓰기 작업 제한

Overview

Introduction

"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures."

—Alan Perlis

코드를 읽기 어렵게 만드는 요소 중 하나는 잦은 변경(Mutation)이다

읽는 사람은 코드 흐름을 따라가는 동시에 변수의 상태를 기억해야 한다

이런 문제를 해결하기 위해서 constObject.freeze를 이용해 변경을 금지할 수도 있지만 때론 변경이 필요할 때가 있다

const result = {}
try {
  result.data = callApi()
} catch (e) {
  result.data = handleError()
}

이런 식으로 오류를 처리할 때 선언문과 할당이 분리된다

코드를 쓴 사람은 result.data가 언제 어떻게 변경되고 어떤 시점에 사용되는지 안다

그러나 코드를 읽는 사람은 전혀 알 수 없다

만약 코드를 작성한 사람이 result.data가 한 번만 변경된다는 메시지를 줄 수 있다면 코드를 읽는 사람은 상태 변경을 걱정할 필요가 없다

Restrict Mutation Using Proxy

프록시를 사용하면 객체에 대한 작업을 재정의 할 수 있다

객체의 쓰기 작업(set)을 재정의해 상태 변경을 줄일 수 있다

const result = new Proxy(
  {},
  {
    set(obj, prop, value) {
      const FLAG = '__SET_DISABLED'
      if (FLAG in obj) throw new Error('value already set')
      obj[FLAG] = true
      obj[prop] = value
    },
  }
)

try {
  result.data = callApi()
} catch (e) {
  result.data = handleError()
}

첫 번째 인자로 받은 객체의 쓰기 동작을 재정의했다

result의 속성을 한 번만 변경할 수 있으며 두 번 변경을 하면 오류가 발생한다

error.value-already-set

함수로 만들면 코드를 읽는 사람에게 분명한 메시지를 줄 수 있다

const setOnce = (target) => {
  let isSetBlocked = false
  return new Proxy(target, {
    set(obj, prop, value) {
      if (isSetBlocked) throw new Error('value already set')
      isSetBlocked = true
      obj[prop] = value
    },
  })
}
// test
const apple = setOnce({})

apple.value = 3
apple.value = 100 // ❌ Uncaught Error: value already set
apple.grade = 'gold' // ❌ Uncaught Error: value already set

Intellisense 도움을 받으려면 prop을 미리 선언하는 게 좋다

조건을 추가해보자

const setPropOnce = (target) => {
  const disabledKeys = {}
  return new Proxy(target, {
    set(obj, prop, value) {
      if (!(prop in obj)) throw new Error(`${prop} is not defined`)
      if (prop in disabledKeys) throw new Error(`${prop} is already set`)
      disabledKeys[prop] = true
      obj[prop] = value
    },
  })
}
// test
const fruits = setPropOnce({ apple: 0, banana: 0 })

fruits.apple = 100
fruits.apple = 3 // ❌ Uncaught Error: apple is already set
fruits.banana = 200
fruits.banana = 1000 // ❌ Uncaught Error: banana is already set
fruits.pineapple = 300 // ❌ Uncaught Error: pineapple is not defined

References

Proxy, MDN

Proxy, The Modern JavaScript Tutorial