いつも悩むこと-設計-プログラム-どっちも同じ結果を出す-だから悩みの種
パターン1 初期化(=インスタンス変数)でデータを渡す
class ModelScore:
"""モデルの性能を評価するクラス"""
def __init__(self, test_data: np.ndarray, predict_data: np.ndarray):
self.test_data = test_data
self.predict_data = predict_data
def score(self):
# 各種評価指標を計算
mae = mean_absolute_error(self.test_data, self.predict_data)
mse = mean_squared_error(self.test_data, self.predict_data)
r2 = r2_score(self.test_data, self.predict_data)
print(f"MAE: {mae:.2f}")
print(f"MSE: {mse:.2f}")
print(f"R²: {r2:.2f}")
パターン2 メソッドで受け取る
class ModelScore:
"""モデルの性能を評価するクラス"""
@staticmethod
def score(test_data: np.ndarray, predict_data: np.ndarray):
# 各種評価指標を計算
mae = mean_absolute_error(test_data, predict_data)
mse = mean_squared_error(test_data, predict_data)
r2 = r2_score(test_data, predict_data)
print(f"MAE: {mae:.2f}")
print(f"MSE: {mse:.2f}")
print(f"R²: {r2:.2f}")
結果としてはパターン1とパターン2どちらでも動くし、同じ結果を得られる。
でも使い勝手は異なる
# パターン1 初期化(=インスタンス変数)でデータを渡す
scorer = ModelScore(test_data, predict_data)
scorer.score() # 複数回呼び出せる
scorer.score() # 同じデータに対して何度でも評価できる
# パターン2 メソッドで受け取る設計
scorer = ModelScore()
scorer.score(test_data, predict_data) # 毎回データを渡す必要がある
scorer.score(other_test, other_predict) # 異なるデータでも評価できる
っていう感じで
同じ結果は得られるけど、使うときに差が出る。
で、どっちがいいんだって話・・・
以下のようにどっちも取り入れちゃえってこともできそうだが
class ModelScore:
def __init__(self, test_data=None, predict_data=None):
self.test_data = test_data
self.predict_data = predict_data
def score(self, test_data=None, predict_data=None):
# 引数が渡された場合はそれを使用し、そうでない場合はインスタンス変数を使用
test = test_data if test_data is not None else self.test_data
pred = predict_data if predict_data is not None else self.predict_data
# 評価指標の計算
...
趣味でやっていた頃はこんなこと気にも留めていなく、とりあえず動けば良いじゃんって感じでやっていたので、まだまだ設計については疎く、最近の悩みの種です。
※ただ個人的にプログラミングを長く続けられているのは上記のような正解のない問いが度々発生するおかげでもある。
結論としては「パターン1 初期化(=インスタンス変数)でデータを渡す」を採用しました。
以下を理由にしました。
・初期化で受け取る場合、オブジェクトの作成時点で「このクラスは特定のデータセットに関連付けられている」という意図が明確になると思った。(書籍:良いコード悪いコードにも似たような話があったような)
・いろんなデータセットに対してメソッドを使う予定がない
・今後、他のメソッドを追加するかもと思った。
上記の結論に至った経緯、意思決定までの流れをメモってみた。
それぞれのメリット・デメリットを考えてみた
パターン1のような__init__で受け取る設計
メリット
- 同じデータに対して複数回評価を行える
- データの前処理や検証をコンストラクタで行う処理を入れるなら、こっちの方が良い。
-「このクラスは指定されたデータセットに基づいてスコアを計算する」という明確な意図を示せる
デメリット
- インスタンス時に受け取ったデータに対してのみメソッドが機能するので汎用性はやや低下?
パターン2の設計
メリット
- 様々なデータセットに対して評価を行うことができる
- メモリ使用量を抑えることができるらしい
- インスタンスが複数の異なるデータセットに対して使用できるため、再利用性が高くなる(同じインスタンスで複数のデータセットを扱いたい場合には便利ってこと
デメリット
- 毎回メソッドを呼び出すときにデータを渡すはめになる
それぞれの設計が伝える内容はどのようになるのか?
※(書籍:「良いコード・悪いコード」)を参考に思考を巡らせてみた(書籍にこの手の話にドンピシャな話が書いてあるわけではないですが参考になる話はちらほら出てきます)
__init__で受け取る場合
クラスをインスタンス化する時点でデータ(test_dataとpredict_data)がオブジェクトに紐づけられる。このクラスが「指定されたデータセットに基づいてスコアを計算する責務を持つ」という明確な責務になる。メソッドで受け取る場合
score(test_data, predict_data)とする設計は、クラス自体がデータセットに依存しない汎用的な「計算器」として機能することになる。この場合、オブジェクトが何度も異なるデータセットに対してスコアを計算できることを表すことになる。
現状、どういう意図で使用していくかを振り返った
クラスがいくつもの異なるデータセットに対して評価する予定はあるか→No
クラスにメソッドを追加して、かつ、それは同じデータセットを使うことになるか → 追加しそう!Yes!
ってことで結論のパターン1を採用しようと思った。
現時点では自分は以下を判断基準としていこうと思った。
初期化で受け取る設計
データの「状態」をオブジェクトに保持させたい場合
複数のメソッドが同じデータに依存する場合
メソッドで受け取る設計
各メソッドが異なるデータを受け取る場合
一回のメソッド呼び出しだけで完結させたい場合 (データの状態を保持する必要性がない場合の意味)
開発をスタートさせる前に設計を熟考してからやれば、このようなことで悩む必要はないんだろうけど、趣味の開発だと思い付きで描き始めちゃったりするので結構悩みます。