リファクタリング ループの分割

元のコード

ループの中で複数の処理を行なっているとコードの見通しが悪くなる。

const data = [
  {
    id: 1,
    price: 1200,
    type: 0,
  },
  {
    id: 2,
    price: 900,
    type: 1,
  },
  ・・・
]

const calcSale = (data) => {
  let total = 0
  let primeTotal = 0
  let minPrice = (data[0] && data[0].price) ? data[0].price : Infinity

  data.forEach(d => {
    total += d.price
    if(d.type === 1) primeTotal += d.price * 0.5
     if(d.price < minPrice) minPrice = d.price
  })

  return {
    total,
    primeTotal,
    minPrice
  }
}

上のコードでは、ループの中でtotalとprimeTotalを更新している。

  • total はdata.priceを合計した数値になる
  • primeTotal は data.typeが1のときのみ、data.priceの0.5の値を合計した数値になる
  • minPrice は一番小さいpriceになる

三つの値のみの更新なのでまだわかりやすいがさらに増えると読みづらくなる。

ループを分割する

const data = [
  {
    id: 1,
    price: 1200,
    type: 0,
  },
  {
    id: 2,
    price: 900,
    type: 1,
  },
  ・・・
]

const calcSale = (data) => {
  let total = 0
  let primeTotal = 0
  let minPrice = (data[0] && data[0].price) ? data[0].price : Infinity

  data.forEach(d => {
    total += d.price
  })

  data.forEach(d => {
    if(d.type === 1) primeTotal += d.price * 0.5
  })

  data.forEach(d => {
    if(d.price < minPrice) minPrice = d.price
  })

  return {
    total,
    primeTotal,
    minPrice
  }
}

処理を関数化する

ループ処理を関数として抽出して何をやっているか一目でわかるようにする。

const data = [
  {
    id: 1,
    price: 1200,
    type: 0,
  },
  {
    id: 2,
    price: 900,
    type: 1,
  },
  ・・・
]

const calcSale = (data) => {
  const getTotal = () => {
    return data.reduce((total, d) =>  total += d.price, 0)
  }

  const getPrimeTotal = () => {
    return data.reduce((total, d) => {
  	  if(d.type !== 1) return total
  	  return primeTotal += d.price * 0.5
    }, 0)
  }

  const getMinPrice = () => {
    return Math.min(...data.map(d => d.price))
  }

  return {
        total: getTotal(),
        primeTotal: getPrimeTotal(),
        minPrice: getMinPrice(),
    }
}

メリット

可読性が上がる。calcSale内の一時変数(total, primeTotal, minPrice)が消えたことにより、これらの変数を意識しないで済むようになった。

一時変数が多いと、コードを読む時にそれらがどこでどのように更新されているかなどをきにする必要がある。特に変数のスコープが広いと、読む際に上に戻ったりして理解する必要があるため大変。

calcSale, getPrimeTotal, getMinPrice 単体でのテストがしやすくなるので、今後これらのロジックが複雑になっても対応可能。

デメリット

ループの回数が3倍になっているため、パフォーマンスに影響がある。

実際に試してみたところ、dataが10万個でリファクタリング前で4ミリ秒、リファクタリン後13ミリ秒だった。

体感的にはあまり気にならないと思う。

パフォーマンスとの兼ね合いも見てこういったリファクタリングも検討する余地があると思う。