【89】【Rails】リファクタリングトレーニング_ダック・タイピングは凄い!!
この記事を読むと問題のあるコードや改善の余地があるコードを学び、ダック・タイピングというテクニックを使ってリファクタリングする能力を養うことができます。
問題のあるコード
下記のコードを見て、危険なコードだ!とすぐに分かりましたか?
また、どのような点が危険かをすぐに説明することが出来ますか?
そして、どのようにリファクタリングしますか?
class Trip
attr_reader :bicycles,:customers,:vehicle
def initialize(args)
@bicycles = args[:bicycles]
@customers = args[:customers]
@vehicle = args[:vehicle]
end
def prepare(prepares)
prepares.each do |prepare|
prepare.prepare_bicycles(bicycles) if prepare == Mechanic
prepare.buy_foods(customers) if prepare == TripCoordinator
prepare.gas_up(vehicle) if prepare == Driver
prepare.fill_water_tank(vehicle) if prepare == Driver
end
end
end
class Mechanic
def self.prepare_bicycles(bicycles)
puts "#{bicycles}の準備が出来ました"
end
end
class TripCoordinator
def self.buy_foods(customers)
puts "#{customers}様の食事を用意しました"
end
end
class Driver
def self.gas_up(vehicle)
puts "#{vehicle}の補給をしました。"
end
def self.fill_water_tank(vehicle)
puts "#{vehicle}内の飲料水を準備しまし
テスト
require_relative '3_2_1_before_refactoring'
RSpec.describe do
prepares = [Mechanic,TripCoordinator,Driver]
args = {
:bicycles => "ママチャリ",
:customers => "稲葉浩志",
:vehicle => "Crazy Rendezvous"
}
text = <<TEXT
ママチャリの準備が出来ました
稲葉浩志様の食事を用意しました
Crazy Rendezvousの補給をしました。
Crazy Rendezvous内の飲料水を準備しました
TEXT
it '#prapare' do
trip = Trip.new(args)
expect {trip.prepare(prepares)}.to output(text).to_stdout
end
end
改善したい点
def prepare(prepares)
prepares.each do |prepare|
prepare.prepare_bicycles(bicycles) if prepare == Mechanic
prepare.buy_foods(customers) if prepare == TripCoordinator
prepare.gas_up(vehicle) if prepare == Driver
prepare.fill_water_tank(vehicle) if prepare == Driver
end
end
問題山積みのコードですね。
他のクラスに依存しすぎています。
引数で他のクラスを受け取り、該当するクラスがあれば、該当したクラス内のメソッドを実行する形です。
依存しているクラス名の変更、メソッドの変更、などでバグが十分に起こりえます。
また、機能が拡張がすれば、このメソッドも変更しなければいけません。
そして、拡張した分だけコードが長くなっていきます。
機能が数十、数百となった場合のコードはどうなるでしょうか?
何十、何百と連なるコードとなっていしまいます。
この問題山積みのコードをどのように改善しますか?
リファクタリング
class Trip
attr_reader :bicycles,:customers,:vehicle
def initialize(args)
@bicycles = args[:bicycles]
@customers = args[:customers]
@vehicle = args[:vehicle]
end
def prepare(prepares)
prepares.each do |prepare|
prepare.prepare_trip(self)
end
end
end
class Mechanic
def self.prepare_trip(trip)
self.prepare_bicycles(trip.bicycles)
end
def self.prepare_bicycles(bicycles)
puts "#{bicycles}の準備が出来ました"
end
end
class TripCoordinator
def self.prepare_trip(trip)
self.buy_foods(trip.customers)
end
def self.buy_foods(customers)
puts "#{customers}様の食事を用意しました"
end
end
class Driver
def self.prepare_trip(trip)
self.gas_up(trip.vehicle)
self.fill_water_tank(trip.vehicle)
end
def self.gas_up(vehicle)
puts "#{vehicle}の補給をしました。"
end
def self.fill_water_tank(vehicle)
puts "#{vehicle}内の飲料水を準備しました"
end
end
テスト
require_relative '3_2_2_refactoring'
RSpec.describe do
prepares = [Mechanic,TripCoordinator,Driver]
args = {
:bicycles => "ママチャリ",
:customers => "稲葉浩志",
:vehicle => "Crazy Rendezvous"
}
text = <<TEXT
ママチャリの準備が出来ました
稲葉浩志様の食事を用意しました
Crazy Rendezvousの補給をしました。
Crazy Rendezvous内の飲料水を準備しました
TEXT
it '#prapare' do
trip = Trip.new(args)
expect {trip.prepare(prepares)}.to output(text).to_stdout
end
end
ポイント
新たに、prepare_tripメソッド作り、依存関係のある各クラスに置きました。このメソッドは依存関係のあるクラス間で共通したパブリックインターフェースとなっています。
劇的に改善しましたね。まずコードがかなりスッキリしました。
def prepare(prepares)
prepares.each do |prepare|
prepare.prepare_trip(self)
end
end
依存関係もリファクタリング前より緩やかになり、疎結合になりました。
この例のようにクラスをまたぐパブリックインターフェースを用意して、依存関係のあるコードを改善しました。
このようなテクニックをダック・タイピングと言います。
ダック・タイピングは定義のみ説明だと理解しづらいですが、実際にダック・タイピングを使ってリファクタリングしてみると、このテクニックの凄さを実感できると思います。
注意点!
リファクタリング後のコードが完全な形になっていっているわけではありません。前よりかは良くなったと捉えてください。
関連する記事
参考文献
オブジェクト指向設計実践ガイド
~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
最後に
私がブログを書く目的は、素晴らしい本や、素晴らしい方々の技術記事を知って頂きたいからです。ぜひ、上記の参考文献を見て下さい。(noteなので広告とかは一切ありません。)
現在、株式会社grabssに行くために最後の悪あがきをしています!!
現在の進行状況
この記事は89件目の投稿。目標達成!!!!
目標再設定
20日までに100件目指す!!(89件目)
20日超えたが、寝るまでは20日営業日!!(まだ寝てない、ちょっと寝##峠を超え目が覚めた!)
よろしければ、スキボタン及びサポートお願いします。勉強の励みになります。
この記事が気に入ったらサポートをしてみませんか?