書籍 リファクタリング―既存のコードを安全に改善する 第2版
またはWeb版(の方が完全版なんですが)には、
これを補完する リファクタリングカタログ が公開されています
これは何
書籍およびカタログはサンプルコードが JavaScript です、
カタログをもとに Python でリファクタリングのサンプルコードを示します
詳細解説は本にお任せして、「ここをこうこうこう」くらいのノリで示します
カタログ:Extract Class
当初コード、JavaScript版
class Person {
get officeAreaCode() {return this._officeAreaCode;}
get officeNumber() {return this._officeNumber;}
}
リファクタリング後
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版
当初コード
from dataclasses import dataclass
@dataclass()
class Person:
officeAreaCode: str = ""
officeNumber: str = ""
テストコード
テストコード
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")
ここをこうこうこう
クラス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
クラスTelephoneNumberを追加
from dataclasses import dataclass
class TelephoneNumber: # add
pass # add
@dataclass()
class Person:
officeAreaCode: str = ""
officeNumber: str = ""
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
TelephoneNumberにofficeAreaCodeを移動
from dataclasses import dataclass
@dataclass() # add
class TelephoneNumber:
officeAreaCode: str = "" # add
@dataclass()
class Person:
officeAreaCode: str = ""
officeNumber: str = ""
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 = ""
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
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
変数名の変更、テスト
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
変数名の変更
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
今後のメンテのためにテストを整える
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 は触れませんでした
- 「関数群のクラスへの集約」 のときは流石に全体が大きくてクラス単位にはしましたが、コピーしやすい単位のはず
- 動画の人は書籍に倣って toString までされていますね、いいなー
以上
参考
(Youtube)マーチンファウラーによろしく - リファクタリングカタログ - クラスの抽出 @ Tommy109