소프트웨어가 복잡해지는 예

JavaScript로 캐시를 작성하고 단순하게 유지한다는 것이 무엇을 의미하는지 봅시다.

우리는 항상 소프트웨어 개발자가 일을 단순하게 유지하고 복잡성을 제어해야 한다고 말하는 것을 듣습니다. 동시에 우리는 코드를 재사용 및 공유하고 확장하기 쉽게 만들 것을 지지합니다.

소프트웨어를 작성할 때 너무 많은 일을 하려고 하고 작업하기 어려운 코드로 끝나는 것은 매우 쉽습니다.

모든 사람들은 단순하게 유지하라고 말합니다.
그리고 기본적으로 우리 모두는 일반적으로 그것이 합리적인 일처럼 들린다는 데 동의합니다.
우리 모두가 우리의 목표를 알고 있다면, 시간이 지남에 따라 프로젝트가 발전함에 따라 어떻게 일이 그렇게 엉망이 되고 작업하기가 어렵습니까?

어쩌면 우리는 단순한 해결책을 위해 노력하는 것이 무엇을 의미하는지에 대한 더 많은 예가 필요합니다.

간단한 캐시를 만들어 봅시다.

캐시를 사용하면 키-값 쌍을 설정하고 값을 한 번에 검색할 수 있습니다.

간단한 구현은 다음과 같습니다.

const cache = () => {
  const store = {}

  const set = (key, value) => {
    store[key] = value
  }

  const remove = key => {
    const value = store[key]
    delete store[key]
    return value
  }

  return { set, remove }
}

// Let's use the cache

const simpleCache = cache()

simpleCache.set('a', 1)
simpleCache.set('b', 2)
simpleCache.set('b', 3)

console.log(simpleCache.remove('a')) // 1
console.log(simpleCache.remove('b')) // 3
console.log(simpleCache.remove('b')) // undefined


이제 프로젝트가 발전함에 따라 새로운 요구 사항이 발생하고 캐시도 캐시에 저장된 항목을 만료해야 합니다. TTL(Time to Live)을 지정하고 캐시 항목이 만료될 때마다 콜백 함수를 실행해야 합니다. 그에 따라 코드를 변경합니다.

const cache = (ttl, expirationHandler) => {
  const store = {}

  const set = (key, value) => {
    // Clear existing timer
    const record = store[key]
    if (record) {
      clearTimeout(record.timer)
    }
    // Set expiration timer
    const timer = setTimeout(() => {
      expirationHandler(key, store[key].value)
      delete store[key]
    }, ttl)
    // Store timer and value
    store[key] = { timer, value }
  }

  const remove = key => {
    // Find record
    const record = store[key]
    if (!record) {
      return undefined
    }
    delete store[key]
    const { timer, value } = record
    // Clear timer and store
    clearTimeout(timer)
    return value
  }

  return { set, remove }
}


const expirationHandler = (key, value) => {
  console.log(`expired ${key}: ${value}`) // expired b: 2
}
const expiringCache = cache(1000, expirationHandler)

expiringCache.set('a', 1)
expiringCache.set('b', 2)

console.log(expiringCache.remove('a')) // 1
console.log(expiringCache.remove('a')) // undefined
setTimeout(() => {
  console.log(expiringCache.remove('b')) // undefined
}, 1100)


모든 것이 훌륭하게 작동하지만 코드를 검토하는 동안 동료는 캐시의 항목이 만료되지 않도록 엄격하게 요구하는 다른 상황에서 동일한 캐시가 사용된다는 것을 알게 됩니다.

이제 코드 기반에서 이전 및 새 캐시 구현을 단순히 유지할 수 있지만 항목을 유지하는 것을 선호합니다 DRY .

따라서 대신 두 사용 사례를 모두 지원하도록 새 캐시를 조정합니다.

const cache = (ttl, expirationHandler) => {
  const store = {}

  const set = (key, value) => {
    // If no TTL is specified, behave as before and return early
    if (!ttl) {
      store[key] = value
      return
    }
    // Clear existing timer
    const record = store[key]
    if (record) {
      clearTimeout(record.timer)
    }
    // Set expiration timer
    const timer = setTimeout(() => {
      expirationHandler(key, store[key].value)
      delete store[key]
    }, ttl)
    // Store timer and value
    store[key] = { timer, value }
  }

  const remove = key => {
    // Find record
    const record = store[key]
    if (!record) {
      return undefined
    }
    delete store[key]
    // If no TTL is specified, behave as before and return early
    if (!ttl) {
      return record
    }
    const { timer, value } = record
    // Clear timer and store
    clearTimeout(timer)
    return value
  }

  return { set, remove }
}

// Let's use the simple cache

const simpleCache = cache()

simpleCache.set('a', 1)
simpleCache.set('b', 2)
simpleCache.set('b', 3)

console.log(simpleCache.remove('a')) // 1
console.log(simpleCache.remove('b')) // 3
console.log(simpleCache.remove('b')) // undefined

// Let's use the expiring cache

const expirationHandler = (key, value) => {
  console.log(`expired ${key}: ${value}`) // expired b: 2
}
const expiringCache = cache(1000, expirationHandler)

expiringCache.set('a', 1)
expiringCache.set('b', 2)

console.log(expiringCache.remove('a')) // 1
console.log(expiringCache.remove('a')) // undefined
setTimeout(() => {
  console.log(expiringCache.remove('b')) // undefined
}, 1100)


그것은 빨랐다. 두 개의 IF 문을 추가하기만 하면 됩니다.

그리고 이것이 상황이 복잡해지는 방법입니다. 단순 캐시는 더 이상 단순하지 않고 만료되는 캐시와 얽혀 있습니다. 단순한 시나리오는 이해하기 어려워지고 느려지고 버그를 도입할 기회가 더 많아졌습니다.

단순히 하나의 IF 문을 더 추가하여 기능을 구현할 때마다 기능을 더욱 확장하는 데 도움이 됩니다the big ball of mud.

원본 캐시를 단순하게 유지하려면 어떻게 해야 합니까?

단순한 것을 복잡하게 만드는 대신 코드를 복제하십시오.

코드를 복사하면 공유하고 재사용할 수 있는 부분을 더 쉽게 확인할 수 있습니다.

각각 한 가지 작업을 수행하는 특수 도구를 구축합니다. 그리고 이러한 도구를 구성하여 다른 도구를 구축합니다.

이것은 말했다 many times before .

간단한 캐시를 복잡하게 하지 않고 만료되는 캐시를 어떻게 만들 수 있습니까?

이 예에서 만료 동작은 초기 캐시 구현 위에 쉽게 구축할 수 있습니다.

const cache = () => {
  const store = {}

  const set = (key, value) => {
    store[key] = value
  }

  const remove = key => {
    const value = store[key]
    delete store[key]
    return value
  }

  return { set, remove }
}

const expire = (cache, ttl, expirationHandler) => {
  const timers = {}

  const set = (key, value) => {
    // Store value
    cache.set(key, value)
    // Clear existing timer
    clearTimeout(timers[key])
    // Set expiration timer
    timers[key] = setTimeout(() => {
      const value = cache.remove(key)
      delete timers[key]
      expirationHandler(key, value)
    }, ttl)
  }

  const remove = key => {
    clearTimeout(timers[key])
    delete timers[key]
    return cache.remove(key)
  }

  return { set, remove }
}

// Let's use the simple cache

const simpleCache = cache()

simpleCache.set('a', 1)
simpleCache.set('b', 2)
simpleCache.set('b', 3)

console.log(simpleCache.remove('a')) // 1
console.log(simpleCache.remove('b')) // 3
console.log(simpleCache.remove('b')) // undefined

// Let's use the expiring cache

const expirationHandler = (key, value) => {
  console.log(`expired ${key}: ${value}`)
}
const expiringCache = expire(cache(), 1000, expirationHandler)

expiringCache.set('a', 1)
expiringCache.set('b', 2)

console.log(expiringCache.remove('a')) // 1
console.log(expiringCache.remove('a')) // undefined
setTimeout(() => {
  console.log(expiringCache.remove('b')) // undefined
}, 1100)


이 예제 도구와 같은 일부 경우에는 잘 구성됩니다. 다른 시나리오에서는 부품만 재사용할 수 있습니다. 로직의 일부를 별도의 기능으로 이동하면 기능을 공유하고 자체적으로 도구로 사용할 수 있습니다.

기존 프로그램에 새로운 조건을 도입할 때마다 주의해야 합니다. 어떤 부품이 별도의 재사용 가능한 도구가 될 수 있는지 생각해 보십시오. 코드 복사를 두려워하지 마십시오.

좋은 웹페이지 즐겨찾기