【リファクタリングカタログ】参照から値への変更

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

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

## カタログ：Change Reference to Value
https://refactoring.com/catalog/changeReferenceToValue.html

```javascript:当初コード、JavaScript版
class Product {
  applyDiscount(arg) {this._price.amount -= arg;}
```
```javascript:リファクタリング後
class Product {
  applyDiscount(arg) {
    this._price = new Money(this._price.amount - arg, this._price.currency);
  }
```
### Python版
```python:当初コード
class Product():
  def applyDiscount(self, arg):
    self._price.amount -= arg
```
と行きたいところですが、_price をどう定義していこうかなと書籍を見ると、class Person とclass TelephoneNumber で具体説明をしていて、ぐぬぬ、という訳で改めまして

```javascript:当初コード、JavaScript版、改
class Person{
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get officeAreaCode()    {return this._telephoneNumber.areaCode;}
  set officeAreaCode(arg) {this._telephoneNumber.areaCode = arg;}
  get officeNumber()    {return this._telephoneNumber.number;}
  set officeNumber(arg) {this._telephoneNumber.number = arg;}  
}

class TelephoneNumber{
  get areaCode()    {return this._areaCode;}
  set areaCode(arg) {this._areaCode = arg;}
  get number()    {return this._number;}
  set number(arg) {this._number = arg;} 
}
```
```javascript:リファクタリング後
class Person{
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get officeAreaCode()    {return this._telephoneNumber.areaCode;}
  set officeAreaCode(arg) {
    this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber);
  }
  get officeNumber()    {return this._telephoneNumber.number;}
  set officeNumber(arg) {
    this._telephoneNumber = new TelephoneNumber(this.officeAreaCode, arg);
  }
}

class TelephoneNumber{
  constructor(areaCode, number) {
    this._areaCode = areaCode;
    this._number = number;
  }
  get areaCode()    {return this._areaCode;}
  set areaCode(arg) {this._areaCode = arg;}
  get number()    {return this._number;}
  set number(arg) {this._number = arg;} 
}
```
### Python版、改
```python:当初コード
class Person():
  def __init__(self):
    self.__telephoneNumber = TelephoneNumber()

  @property
  def officeAreaCode(self):
    return self.__telephoneNumber.areaCode
  @officeAreaCode.setter
  def officeAreaCode(self, arg):
    self.__telephoneNumber.areaCode = arg

  @property
  def officeNumber(self):
    return self.__telephoneNumber.number
  @officeNumber.setter
  def officeNumber(self, arg):
    self.__telephoneNumber.number = arg

class TelephoneNumber():
  def __init__(self):
    self.__areaCode = ''
    self.__number = ''

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg
```

### テストコード
```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'
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.target.number = '555-0142'
    self.assertEqual(self.target.number, '555-0142')
```

### ここをこうこうこう
```python:セッター除去の準備、コンストラクタにパラメータを追加、テスト追加
class TestTelephoneNumber(TestCase):
  target = TelephoneNumber()

  def test_areaCode(self):
    self.target.areaCode = '312'
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.target.number = '555-0142'
    self.assertEqual(self.target.number, '555-0142')
  
  def test_telephoneNumber(self): # add
    target = TelephoneNumber('312', '555-0142') # add
    self.assertEqual(target.areaCode, '312') # add
    self.assertEqual(target.number, '555-0142') # add
```

```python:セッター除去の準備、変数を抽出
class TelephoneNumber():
  def __init__(self, areaCode='', number=''): # edit
    self.__areaCode = areaCode # edit
    self.__number = number # edit

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg
```

```python:参照から値への変更
class Person():
  def __init__(self):
      self.__telephoneNumber = TelephoneNumber()

  @property
  def officeAreaCode(self):
    return self.__telephoneNumber.areaCode
  @officeAreaCode.setter
  def officeAreaCode(self, arg):
    self.__telephoneNumber = TelephoneNumber(arg, self.officeNumber) # edit

  @property
  def officeNumber(self):
    return self.__telephoneNumber.number
  @officeNumber.setter
  def officeNumber(self, arg):
    self.__telephoneNumber = TelephoneNumber(self.officeAreaCode, arg) # edit
```

```python:セッターの除去、テストの削除
class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142') # edit

  def test_areaCode(self):
    # self.target.areaCode = '312' # del
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    # self.target.number = '555-0142' # del
    self.assertEqual(self.target.number, '555-0142')
  
  # def test_telephoneNumber(self): # del
  #   target = TelephoneNumber('312', '555-0142') # del
  #   self.assertEqual(target.areaCode, '312') # del
  #   self.assertEqual(target.number, '555-0142') # del
```

```python:セッターの除去
class TelephoneNumber():
  def __init__(self, areaCode='', number=''): # edit
    self.__areaCode = areaCode # edit
    self.__number = number # edit

  @property
  def areaCode(self):
    return self.__areaCode
  # @areaCode.setter # del
  # def areaCode(self, arg): # del
  #   self.__areaCode = arg # del

  @property
  def number(self):
    return self.__number
  # @number.setter # del
  # def number(self, arg): # del
  #   self.__number = arg # del
```

これで TestTelephoneNumber はイミュータブルになりました、
バリューオブジェクトとしては値ベースの同等性を判定できるようにしたいところです、
ここで書籍は終わっています、本記事では実装もしていきます

```python:同等性判定、テストを追加
class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142')

  def test_areaCode(self):
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.assertEqual(self.target.number, '555-0142')
  
  def test_equals(self): # add
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142'), True) # add
```

```python:同等性判定、まずテストグリーン
class TelephoneNumber():
  def __init__(self, areaCode='', number=''):
    self.__areaCode = areaCode
    self.__number = number

  def __eq__(self, o): # add
    return True # add

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg
```

```python:同等性判定、テストを追加、三角測量
class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142')

  def test_areaCode(self):
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.assertEqual(self.target.number, '555-0142')
  
  def test_equals(self):
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142'), True)

  def test_equals_areaCode_false(self): # add
    self.assertEqual(self.target == TelephoneNumber('312x', '555-0142'), False) # add
```

```python:同等性判定、__eq__をきちんと実装、areaCode
class TelephoneNumber():
  def __init__(self, areaCode='', number=''):
    self.__areaCode = areaCode
    self.__number = number

  def __eq__(self, o):
    return self.areaCode == o.areaCode # edit

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg
```

```python:同等性判定、テストを追加、三角測量
class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142')

  def test_areaCode(self):
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.assertEqual(self.target.number, '555-0142')
  
  def test_equals(self):
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142'), True)

  def test_equals_areaCode_false(self):
    self.assertEqual(self.target == TelephoneNumber('312x', '555-0142'), False)

  def test_equals_number_false(self): # add
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142x'), False) # add
```

```python:同等性判定、__eq__をきちんと実装、number
class TelephoneNumber():
  def __init__(self, areaCode='', number=''):
    self.__areaCode = areaCode
    self.__number = number

  def __eq__(self, o):
    return self.areaCode == o.areaCode and self.number == o.number # edit

  @property
  def areaCode(self):
    return self.__areaCode

  @property
  def number(self):
    return self.__number
```

Python 3.7 以降ではこのようなクラスはもっと簡単に実装できます

```python:dataclass
from dataclasses import dataclass

@dataclass(frozen=True)
class TelephoneNumber():
  areaCode: str = ''
  number: str = ''
```

### 出来上がり
dataclass の採用もテストグリーンな準備をしてこそ安心して出来ますね

以上

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