書籍 リファクタリング―既存のコードを安全に改善する 第2版
またはWeb版(の方が完全版なんですが)には、
これを補完する リファクタリングカタログ が公開されています
これは何
書籍およびカタログはサンプルコードが JavaScript です、
カタログをもとに Python でリファクタリングのサンプルコードを示します
詳細解説は本にお任せして、「ここをこうこうこう」くらいのノリで示します
カタログ:Combine Functions into Class
当初コード、JavaScript版
function base(aReading) {...}
function taxableCharge(aReading) {...}
function calculateBaseCharge(aReading) {...}
リファクタリング後
class Reading {
base() {...}
taxableCharge() {...}
calculateBaseCharge() {...}
}
Python版
当初コード
def base(aReading):
...
と行きたいところですが、書籍を見ると、client1, client2, client3 で具体説明をしていて、ぐぬぬ、という訳で改めまして
さらに書籍には無い具体コードを参考動画から拝借しまして
当初コード、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;
}
リファクタリング後
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版、改
当初コード
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
テストコード
テストコード
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()
ここをこうこうこう
クラス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に注目していきます
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
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
関数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
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
変数のインライン化ですっきり
@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
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に注目します
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
変数のインライン化
@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
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
関数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
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
変数のインライン化ですっきり
@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
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に注目します
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
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
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
変数のインライン化ですっきり
@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
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
リファクタリング後、全体
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