書籍 リファクタリング―既存のコードを安全に改善する 第2版
またはWeb版(の方が完全版なんですが)には、
これを補完する リファクタリングカタログ が公開されています
これは何
書籍およびカタログはサンプルコードが JavaScript です、
カタログをもとに Python でリファクタリングのサンプルコードを示します
詳細解説は本にお任せして、「ここをこうこうこう」くらいのノリで示します
カタログ:Change Reference to Value
当初コード、JavaScript版
class Product {
applyDiscount(arg) {this._price.amount -= arg;}
リファクタリング後
class Product {
applyDiscount(arg) {
this._price = new Money(this._price.amount - arg, this._price.currency);
}
Python版
当初コード
class Product():
def applyDiscount(self, arg):
self._price.amount -= arg
と行きたいところですが、_price をどう定義していこうかなと書籍を見ると、class Person とclass TelephoneNumber で具体説明をしていて、ぐぬぬ、という訳で改めまして
当初コード、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;}
}
リファクタリング後
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版、改
当初コード
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
テストコード
テストコード
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')
ここをこうこうこう
セッター除去の準備、コンストラクタにパラメータを追加、テスト追加
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
セッター除去の準備、変数を抽出
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
参照から値への変更
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
セッターの除去、テストの削除
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
セッターの除去
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 はイミュータブルになりました、
バリューオブジェクトとしては値ベースの同等性を判定できるようにしたいところです、
ここで書籍は終わっています、本記事では実装もしていきます
同等性判定、テストを追加
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
同等性判定、まずテストグリーン
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
同等性判定、テストを追加、三角測量
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
同等性判定、__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
同等性判定、テストを追加、三角測量
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
同等性判定、__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 以降ではこのようなクラスはもっと簡単に実装できます
dataclass
from dataclasses import dataclass
@dataclass(frozen=True)
class TelephoneNumber():
areaCode: str = ''
number: str = ''
出来上がり
dataclass の採用もテストグリーンな準備をしてこそ安心して出来ますね
以上
参考
(Youtube)マーチンファウラーによろしく - リファクタリングカタログ - 参照から値への変更 @ Tommy109