【リファクタリングカタログ】関数群のクラスへの集約

## 書籍 [リファクタリング―既存のコードを安全に改善する 第2版](https://www.yodobashi.com/product/100000009003204206/)
または[Web版](https://martinfowler.com/articles/access-refactoring-web-edition.html)（の方が完全版なんですが）には、
これを補完する [リファクタリングカタログ](https://refactoring.com/catalog/) が公開されています
## これは何
書籍およびカタログはサンプルコードが JavaScript です、
カタログをもとに Python でリファクタリングのサンプルコードを示します

詳細解説は本にお任せして、「ここをこうこうこう」くらいのノリで示します

## カタログ：Combine Functions into Class
https://refactoring.com/catalog/combineFunctionsIntoClass.html

```javascript:当初コード、JavaScript版
function base(aReading) {...}
function taxableCharge(aReading) {...}
function calculateBaseCharge(aReading) {...}
```
```javascript:リファクタリング後
class Reading {
  base() {...}
  taxableCharge() {...}
  calculateBaseCharge() {...}
}
```
### Python版
```python:当初コード
def base(aReading):
  ...
```
と行きたいところですが、書籍を見ると、client1, client2, client3 で具体説明をしていて、ぐぬぬ、という訳で改めまして
さらに書籍には無い具体コードを参考動画から拝借しまして

```javascript:当初コード、JavaScript版、改
const reading = { customer: "ivan", quantity: 10, month: 5, year: 2017 };

const acquireReading = () => { return reading }
const baseRate = (month, year) => 10
const taxThreshold = (year) => 40

class Client1 {
  #baseCharge
  constructor() {
    const aReading = acquireReading();
    this.#baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity;
  }
  get baseCharge() { return this.#baseCharge }
}

class Client2 {
  #taxableCharge
  constructor() {
    const aReading = acquireReading();
    const base = (baseRate(aReading.month, aReading.year) * aReading.quantity);
    this.#taxableCharge = Math.max(0, base - taxThreshold(aReading.year));
  }
  get taxableCharge() { return this.#taxableCharge }
}

class Client3 {
  #basicChargeAmount
  constructor() {
    const aReading = acquireReading();
    this.#basicChargeAmount = calculateBaseCharge(aReading);
  }
  get basicChargeAmount() { return this.#basicChargeAmount }
}

function calculateBaseCharge(aReading) {
  return baseRate(aReading.month, aReading.year) * aReading.quantity;
}
```
```javascript:リファクタリング後
const reading = { customer: "ivan", quantity: 10, month: 5, year: 2017 };

const acquireReading = () => { return reading }
const baseRate = (month, year) => 10
const taxThreshold = (year) => 40

class Reading {
  constructor(data) {
    this._customer = data.customer
    this._quantity = data.quantity
    this._month = data.month
    this._year = data.year
  }
  get customer() { return this._customer}
  get quantity() { return this._quantity}
  get month() { return this._month}
  get year() { return this._year}
  get baseCharge() {
    return baseRate(this.month, this.year) * this.quantity
  }
  get taxableCharge() {
    return Math.max(0, this.baseCharge - taxThreshold(this.year))
  }
}

class Client1 {
  #reading
  constructor() {
    this.#reading = new Reading(acquireReading())
  }
  get baseCharge() { return this.#reading.baseCharge }
}

class Client2 {
  #reading
  constructor() {
    this.#reading = new Reading(acquireReading())
  }
  get taxableCharge() { return this.#reading.taxableCharge }
}

class Client3 {
  #reading
  constructor() {
    this.#reading = new Reading(acquireReading())
  }
  get basicChargeAmount() { return this.#reading.baseCharge }
}
```
### Python版、改
```python:当初コード
from collections import namedtuple

reading = namedtuple("reading", ["customer", "quantity", "month", "year"])(
    "ivan", 10, 5, 2017
)


def acquireReading():
    return reading


def baseRate(month, year):
    return 10


def taxThreshold(year):
    return 40


class Client1:
    def __init__(self):
        aReading = acquireReading()
        self.__baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity

    @property
    def baseCharge(self):
        return self.__baseCharge


class Client2:
    def __init__(self):
        aReading = acquireReading()
        base = baseRate(aReading.month, aReading.year) * aReading.quantity
        self.__taxableCharge = max(0, base - taxThreshold(aReading.year))

    @property
    def taxableCharge(self):
        return self.__taxableCharge


class Client3:
    def __init__(self):
        aReading = acquireReading()
        self.__basicChargeAmount = calculateBaseCharge(aReading)

    @property
    def basicChargeAmount(self):
        return self.__basicChargeAmount


def calculateBaseCharge(aReading):
    return baseRate(aReading.month, aReading.year) * aReading.quantity
```

### テストコード
```python:テストコード
from unittest import TestCase
from unittest.main import main


class TestA(TestCase):
    def test_client1(self):
        client = Client1()
        self.assertEqual(client.baseCharge, 100)

    def test_client2(self):
        client = Client2()
        self.assertEqual(client.taxableCharge, 60)

    def test_client3(self):
        client = Client3()
        self.assertEqual(client.basicChargeAmount, 100)


if __name__ == "__main__":
    main()
```

### ここをこうこうこう
```python:クラスReadingを導入
from collections import namedtuple
from dataclasses import dataclass

reading = namedtuple("reading", ["customer", "quantity", "month", "year"])(
    "ivan", 10, 5, 2017
)


def acquireReading():
    return reading


def baseRate(month, year):
    return 10


def taxThreshold(year):
    return 40


@dataclass  # add
class Reading:  # add
    customer: str  # add
    quantity: int  # add
    month: int  # add
    year: int  # add


class Client1:
    def __init__(self):
        aReading = acquireReading()
        self.__baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity

    @property
    def baseCharge(self):
        return self.__baseCharge


class Client2:
    def __init__(self):
        aReading = acquireReading()
        base = baseRate(aReading.month, aReading.year) * aReading.quantity
        self.__taxableCharge = max(0, base - taxThreshold(aReading.year))

    @property
    def taxableCharge(self):
        return self.__taxableCharge


class Client3:
    def __init__(self):
        aReading = acquireReading()
        self.__basicChargeAmount = calculateBaseCharge(aReading)

    @property
    def basicChargeAmount(self):
        return self.__basicChargeAmount


def calculateBaseCharge(aReading):
    return baseRate(aReading.month, aReading.year) * aReading.quantity
```
方針としてコード全文を載せていきたいと思ってましたが、あまりに長いのでClient1に注目していきます

```python:Readingを活用、脱aReading
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int


class Client1:
    def __init__(self):
        # aReading = acquireReading() # del
        temporary = Reading(*acquireReading())  # add
        self.__baseCharge = (
            baseRate(temporary.month, temporary.year) * temporary.quantity  # edit
        )

    @property
    def baseCharge(self):
        return self.__baseCharge
```
```python:baseChargeを関数化、temporary変数リネーム
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int


class Client1:
    def __init__(self):
        aReading = Reading(*acquireReading())

        def baseCharge():  # add
            return baseRate(aReading.month, aReading.year) * aReading.quantity  # add

        self.__baseCharge = baseCharge()  # edit

    @property
    def baseCharge(self):
        return self.__baseCharge
```
```python:関数baseChargeをクラスReadingに移動
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property  # add
    def baseCharge(self) -> int:  # add
        return baseRate(self.month, self.year) * self.quantity  # add


class Client1:
    def __init__(self):
        aReading = Reading(*acquireReading())

        # def baseCharge():  # del
        #     return baseRate(aReading.month, aReading.year) * aReading.quantity  # del

        self.__baseCharge = aReading.baseCharge  # edit

    @property
    def baseCharge(self):
        return self.__baseCharge
```
```python:aReadingを内部変数にもつ
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity


class Client1:
    def __init__(self):
        aReading = Reading(*acquireReading())
        self.__reading = aReading  # add
        self.__baseCharge = self.__reading.baseCharge  # edit

    @property
    def baseCharge(self):
        return self.__baseCharge
```
```python:変数のインライン化ですっきり
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity


class Client1:
    def __init__(self):
        # aReading = Reading(*acquireReading())  # del
        self.__reading = Reading(*acquireReading())  # edit
        # self.__baseCharge = self.__reading.baseCharge  # del

    @property
    def baseCharge(self):
        return self.__reading.baseCharge  # edit
```
```python:Client1できあがり
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity


class Client1:
    def __init__(self):
        self.__reading = Reading(*acquireReading())

    @property
    def baseCharge(self):
        return self.__reading.baseCharge
```
つぎはClient2に注目します

```python:Readingを活用、脱aReading
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity


class Client2:
    def __init__(self):
        # aReading = acquireReading()  # del
        temporary = Reading(*acquireReading())  # add
        base = temporary.baseCharge  # edit
        self.__taxableCharge = max(0, base - taxThreshold(temporary.year))  # edit

    @property
    def taxableCharge(self):
        return self.__taxableCharge
```
```python:変数のインライン化
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity


class Client2:
    def __init__(self):
        temporary = Reading(*acquireReading())
        # base = temporary.baseCharge  # del
        self.__taxableCharge = max(
            0, temporary.baseCharge - taxThreshold(temporary.year)  # edit
        )

    @property
    def taxableCharge(self):
        return self.__taxableCharge
```
```python:taxableChargeを関数化、temporary変数リネーム
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity


class Client2:
    def __init__(self):
        aReading = Reading(*acquireReading())

        def taxableCharge():  # add
            return max(0, aReading.baseCharge - taxThreshold(aReading.year))  # add

        self.__taxableCharge = taxableCharge()  # edit

    @property
    def taxableCharge(self):
        return self.__taxableCharge
```
```python:関数taxableChargeをクラスReadingに移動
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property  # add
    def taxableCharge(self) -> int:  # add
        return max(0, self.baseCharge - taxThreshold(self.year))  # add


class Client2:
    def __init__(self):
        aReading = Reading(*acquireReading())

        # def taxableCharge():  # del
        #     return max(0, aReading.baseCharge - taxThreshold(aReading.year))  # del

        self.__taxableCharge = aReading.taxableCharge  # edit

    @property
    def taxableCharge(self):
        return self.__taxableCharge
```
```python:aReadingを内部変数にもつ
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client2:
    def __init__(self):
        aReading = Reading(*acquireReading())
        self.__reading = aReading  # add
        self.__taxableCharge = self.__reading.taxableCharge  # edit

    @property
    def taxableCharge(self):
        return self.__taxableCharge
```
```python:変数のインライン化ですっきり
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client2:
    def __init__(self):
        # aReading = Reading(*acquireReading())  # del
        self.__reading = Reading(*acquireReading())  # edit
        # self.__taxableCharge = self.__reading.taxableCharge  # del

    @property
    def taxableCharge(self):
        return self.__reading.taxableCharge  # edit
```
```python:Client2できあがり
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client2:
    def __init__(self):
        self.__reading = Reading(*acquireReading())

    @property
    def taxableCharge(self):
        return self.__reading.taxableCharge
```
つぎはClient3に注目します

```python:Readingを活用、脱aReading
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client3:
    def __init__(self):
        # aReading = acquireReading()  # del
        temporary = Reading(*acquireReading())  # add
        self.__basicChargeAmount = calculateBaseCharge(temporary)  # edit

    @property
    def basicChargeAmount(self):
        return self.__basicChargeAmount


def calculateBaseCharge(aReading):
    return baseRate(aReading.month, aReading.year) * aReading.quantity
```
```python:calculateBaseCharge(temporary)はtemporary.baseChargeのこと、calculateBaseCharge削除
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client3:
    def __init__(self):
        temporary = Reading(*acquireReading())
        self.__basicChargeAmount = temporary.baseCharge  # edit

    @property
    def basicChargeAmount(self):
        return self.__basicChargeAmount


# def calculateBaseCharge(aReading):  # del
#     return baseRate(aReading.month, aReading.year) * aReading.quantity  # del
```
```python:aReadingを内部変数にもつ
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client3:
    def __init__(self):
        temporary = Reading(*acquireReading())
        self.__reading = temporary  # add
        self.__basicChargeAmount = self.__reading.baseCharge  # edit

    @property
    def basicChargeAmount(self):
        return self.__basicChargeAmount
```
```python:変数のインライン化ですっきり
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client3:
    def __init__(self):
        # temporary = Reading(*acquireReading())  # del
        self.__reading = Reading(*acquireReading())  # edit
        # self.__basicChargeAmount = self.__reading.baseCharge  # del

    @property
    def basicChargeAmount(self):
        return self.__reading.baseCharge  # edit
```
```python:Client3できあがり
@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client3:
    def __init__(self):
        self.__reading = Reading(*acquireReading())

    @property
    def basicChargeAmount(self):
        return self.__reading.baseCharge
```
```python:リファクタリング後、全体
from collections import namedtuple
from dataclasses import dataclass

reading = namedtuple("reading", ["customer", "quantity", "month", "year"])(
    "ivan", 10, 5, 2017
)


def acquireReading():
    return reading


def baseRate(month, year):
    return 10


def taxThreshold(year):
    return 40


@dataclass
class Reading:
    customer: str
    quantity: int
    month: int
    year: int

    @property
    def baseCharge(self) -> int:
        return baseRate(self.month, self.year) * self.quantity

    @property
    def taxableCharge(self) -> int:
        return max(0, self.baseCharge - taxThreshold(self.year))


class Client1:
    def __init__(self):
        self.__reading = Reading(*acquireReading())

    @property
    def baseCharge(self):
        return self.__reading.baseCharge


class Client2:
    def __init__(self):
        self.__reading = Reading(*acquireReading())

    @property
    def taxableCharge(self):
        return self.__reading.taxableCharge


class Client3:
    def __init__(self):
        self.__reading = Reading(*acquireReading())

    @property
    def basicChargeAmount(self):
        return self.__reading.baseCharge
```

### 出来上がり
これで Reading クラスにメソッドが集まり、各クライアントがシンプルになりました

以上

## 参考
(Youtube)マーチンファウラーによろしく - リファクタリングカタログ - 関数群のクラスへの集約 @ Tommy109
<iframe width="560" height="315" src="https://www.youtube.com/embed/zrIfUGqveEc" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>