0
1

More than 3 years have passed since last update.

【リファクタリングカタログ】関数群のクラスへの集約

Last updated at Posted at 2021-01-28

書籍 リファクタリング―既存のコードを安全に改善する 第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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1