【リファクタリングカタログ】クラスの抽出

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

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

## カタログ：Extract Class
https://refactoring.com/catalog/extractClass.html

```javascript:当初コード、JavaScript版
class Person {
  get officeAreaCode() {return this._officeAreaCode;}
  get officeNumber()   {return this._officeNumber;}
}
```
```javascript:リファクタリング後
class Person {
  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  get officeNumber()   {return this._telephoneNumber.number;}
}
class TelephoneNumber {
  get areaCode() {return this._areaCode;}
  get number()   {return this._number;}
}
```
### Python版
```python:当初コード
from dataclasses import dataclass


@dataclass()
class Person:
    officeAreaCode: str = ""
    officeNumber: str = ""
```

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


class TestPerson(TestCase):
    target = Person()

    def test_officeAreaCode(self):
        self.target.officeAreaCode = "312"
        self.assertEqual(self.target.officeAreaCode, "312")

    def test_officeNumber(self):
        self.target.officeNumber = "555-0142"
        self.assertEqual(self.target.officeNumber, "555-0142")
```

### ここをこうこうこう
```python:クラスTelephoneNumberを追加、テスト追加
from unittest import TestCase


class TestPerson(TestCase):
    target = Person()

    def test_officeAreaCode(self):
        self.target.officeAreaCode = "312"
        self.assertEqual(self.target.officeAreaCode, "312")

    def test_officeNumber(self):
        self.target.officeNumber = "555-0142"
        self.assertEqual(self.target.officeNumber, "555-0142")


class TestTelephoneNumber(TestCase):  # add
    target = TelephoneNumber()  # add
```

```python:クラスTelephoneNumberを追加
from dataclasses import dataclass


class TelephoneNumber:  # add
    pass  # add


@dataclass()
class Person:
    officeAreaCode: str = ""
    officeNumber: str = ""
```

```python:TelephoneNumberにofficeAreaCodeを移動の準備、テスト
from unittest import TestCase


class TestPerson(TestCase):
    target = Person()

    def test_officeAreaCode(self):
        self.target.officeAreaCode = "312"
        self.assertEqual(self.target.officeAreaCode, "312")

    def test_officeNumber(self):
        self.target.officeNumber = "555-0142"
        self.assertEqual(self.target.officeNumber, "555-0142")


class TestTelephoneNumber(TestCase):
    target = TelephoneNumber()

    def test_officeAreaCode(self):
        self.assertEqual(self.target.officeAreaCode, "")  # add
```
```python:TelephoneNumberにofficeAreaCodeを移動
from dataclasses import dataclass


@dataclass()  # add
class TelephoneNumber:
    officeAreaCode: str = ""  # add


@dataclass()
class Person:
    officeAreaCode: str = ""
    officeNumber: str = ""
```
```python:PersonのofficeAreaCodeはTelephoneNumberを参照
from dataclasses import dataclass


@dataclass()
class TelephoneNumber:
    officeAreaCode: str = ""


@dataclass()
class Person:
    __telephoneNumber: TelephoneNumber = TelephoneNumber()  # add
    officeAreaCode: str = __telephoneNumber.officeAreaCode  # edit
    officeNumber: str = ""
```
```python:officeNumberも同様に、テスト
from unittest import TestCase


class TestPerson(TestCase):
    target = Person()

    def test_officeAreaCode(self):
        self.target.officeAreaCode = "312"
        self.assertEqual(self.target.officeAreaCode, "312")

    def test_officeNumber(self):
        self.target.officeNumber = "555-0142"
        self.assertEqual(self.target.officeNumber, "555-0142")


class TestTelephoneNumber(TestCase):
    target = TelephoneNumber()

    def test_officeAreaCode(self):
        self.assertEqual(self.target.officeAreaCode, "")

    def test_officeNumber(self):  # add
        self.assertEqual(self.target.officeNumber, "")  # add
```
```python:officeNumberも同様に
from dataclasses import dataclass


@dataclass()
class TelephoneNumber:
    officeAreaCode: str = ""
    officeNumber: str = ""  # add


@dataclass()
class Person:
    __telephoneNumber: TelephoneNumber = TelephoneNumber()
    officeAreaCode: str = __telephoneNumber.officeAreaCode
    officeNumber: str = __telephoneNumber.officeNumber  # edit
```
```python:変数名の変更、テスト
from unittest import TestCase


class TestPerson(TestCase):
    target = Person()

    def test_officeAreaCode(self):
        self.target.officeAreaCode = "312"
        self.assertEqual(self.target.officeAreaCode, "312")

    def test_officeNumber(self):
        self.target.officeNumber = "555-0142"
        self.assertEqual(self.target.officeNumber, "555-0142")


class TestTelephoneNumber(TestCase):
    target = TelephoneNumber()

    def test_areaCode(self):  # edit
        self.assertEqual(self.target.areaCode, "")  # edit

    def test_number(self):  # edit
        self.assertEqual(self.target.number, "")  # edit
```
```python:変数名の変更
from dataclasses import dataclass


@dataclass()
class TelephoneNumber:
    areaCode: str = ""
    number: str = ""


@dataclass()
class Person:
    __telephoneNumber: TelephoneNumber = TelephoneNumber()
    officeAreaCode: str = __telephoneNumber.areaCode
    officeNumber: str = __telephoneNumber.number
```
```python:今後のメンテのためにテストを整える
from unittest import TestCase


class TestPerson(TestCase):
    target = Person()

    def test_officeAreaCode(self):
        self.target.officeAreaCode = "312"
        self.assertEqual(self.target.officeAreaCode, "312")

    def test_officeNumber(self):
        self.target.officeNumber = "555-0142"
        self.assertEqual(self.target.officeNumber, "555-0142")


class TestTelephoneNumber(TestCase):
    target = TelephoneNumber()

    def test_areaCode(self):
        self.target.areaCode = "312"  # add
        self.assertEqual(self.target.areaCode, "312")  # edit

    def test_number(self):
        self.target.number = "555-0142"  # add
        self.assertEqual(self.target.number, "555-0142")  # edit
```

### 出来上がり
書籍では何を目的にしてモチベーション持ってリファクタリングするの？まで解説するため、
クラス Person に name, telephoneNumber があり、 
さらにクラス TelephoneNumber にプロパティ telephoneNumber を作り、 
TelephoneNumber.telephoneNumber() はイケてないから TelephoneNumber.toString() に 関数名の変更 します、
が本記事のお目当ての カタログ”クラスの抽出” の手順はここまでにします

#### （言い訳）
* 書籍だとコード片だけ書かれていて、組み合わせて、実際に動かして確認するのしんどいじゃないすか
* 紙版だと写経、電子版だとコピーですが、それでも全体に対して欠片を組み合わせるのしんどいです
* すぐ動かして貰いたい！Qiita記事ではコード部の右上のボタンでコピーできる！なので、なるべく全部載せしてます
* が、全部載せすると、当然ながーくなって、読みづらくもなるので、name, telephoneNumber は触れませんでした
* 「[関数群のクラスへの集約](/kfjt/items/8f2ef0ebdf199405e2e1)」 のときは流石に全体が大きくてクラス単位にはしましたが、コピーしやすい単位のはず
* 動画の人は書籍に倣って toString までされていますね、いいなー

以上

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