【92】【Rails】リファクタリングトレーニング_フックメッセージを使う
この記事を読むと問題のあるコードや改善の余地があるコードを学び、リファクタリングする能力を養うことができます。
改善の余地があるコード
問題があるコードというわけではありません。
しかし、まだ改善の余地があります。
どの点を改善すべきか説明できますか?
そして、どのようにリファクタリングしますか?
class Bicycle
attr_reader :size,:chain,:tire_size
def initialize(args)
@size = args[:size]
@chain = args[:chain] || default_chain
@tire_size = args[:tire_size] || default_tire_size
end
def spares
{
tire_size: tire_size,
chain: chain
}
end
def default_chain
"10-speed"
end
def default_tire_size
raise NotImplementedError,
"This #{self.class} cannot respond do to:"
end
end
class RoadBike < Bicycle
attr_reader :tape_color
def initialize(args)
@tape_color = args[:tape_color]
super(args)
end
def spares
super.merge({tape_color: tape_color})
end
def default_tire_size
"23"
end
end
class MountainBike < Bicycle
attr_reader :front_shock,:rear_shock
def initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
super(args)
end
def spares
super.merge({rear_shock: rear_shock})
end
def default_tire_size
"2.1"
end
end
テスト
require_relative '4_3_1_before_refactoring'
RSpec.describe "Bicycleモデル" do
bike = RoadBike.new(size: "L",tape_color: "red")
it '#spares' do
expect(bike.spares)
.to eq({:chain=>"10-speed", :tape_color=>"red", :tire_size=>"23"})
end
end
RSpec.describe "Bicycleを継承したテスト用のサブクラス" do
class Foo < Bicycle
attr_reader :tape_color
def initialize(args)
@tape_color = args[:tape_color]
super args
end
def spares
super.merge({tape_color: tape_color})
end
def default_tire_size
"23"
end
end
it '#spares' do
bike = Foo.new(size: "L",tape_color: "red")
expect(bike.spares)
.to eq({:chain=>"10-speed", :tape_color=>"red", :tire_size=>"23"})
end
end
改善点したい点
改善点を解説するために、リファクタリング前のテストを修正します。
どの点が修正されたか分かりますか?注意して見ないと分からない修正です。
こういった時テストって便利ですよね。
まず、下記のテストをやってみましょう。
require_relative '4_3_1_before_refactoring'
RSpec.describe "Bicycleモデル" do
bike = RoadBike.new(size: "L",tape_color: "red")
it '#spares' do
expect(bike.spares)
.to eq({:chain=>"10-speed", :tape_color=>"red", :tire_size=>"23"})
end
end
RSpec.describe "Bicycleを継承したテスト用のサブクラス" do
class Foo < Bicycle
attr_reader :tape_color
def initialize(args)
@tape_color = args[:tape_color]
end
def spares
super.merge({tape_color: tape_color})
end
def default_tire_size
"23"
end
end
it '#spares' do
bike = Foo.new(size: "L",tape_color: "red")
expect(bike.spares)
.to eq({:chain=>"10-speed", :tape_color=>"red", :tire_size=>"23"})
end
end
テストが通りません!!
なぜでしょうか?
got: {:chain=>nil, :tape_color=>"red", :tire_size=>nil}
nilを返しているから、テストが通らないようです。
なぜnilを返すのか??
super(args)の記入漏れ
def initialize(args)
@tape_color = args[:tape_color]
end
super(args)の記入漏れが原因でした。
この記入漏れをすぐ気づきましたか?
今回はテストで意図的に再現しましたが、実際に起こり得る可能性は十分にあります。このコードの改善点が見つかりましたね。
リファクタリング
class Bicycle
attr_reader :size,:chain,:tire_size
def initialize(args)
@size = args[:size]
@chain = args[:chain] || default_chain
@tire_size = args[:tire_size] || default_tire_size
post_initialize(args)
end
def post_initialize(args)
nil
end
def spares
{
tire_size: tire_size,
chain: chain
}.merge(local_spares)
end
def local_spares
{}
end
def default_chain
"10-speed"
end
def default_tire_size
raise NotImplementedError,
"This #{self.class} cannot respond do to:"
end
end
class RoadBike < Bicycle
attr_reader :tape_color
def post_initialize(args)
@tape_color = args[:tape_color]
end
def local_spares
{tape_color: tape_color}
end
def default_tire_size
"23"
end
end
class MountainBike < Bicycle
attr_reader :front_shock,:rear_shock
def post_initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
end
def local_spares
{rear_shock: rear_shock}
end
def default_tire_size
"2.1"
end
end
テスト
require_relative '4_3_2_refactoring'
RSpec.describe "Bicycleモデル" do
bike = RoadBike.new(size: "L",tape_color: "red")
it '#spares' do
expect(bike.spares)
.to eq({:chain=>"10-speed", :tape_color=>"red", :tire_size=>"23"})
end
end
RSpec.describe "Bicycleを継承したテスト用のサブクラス" do
class Foo < Bicycle
attr_reader :tape_color
def post_initialize(args)
@tape_color = args[:tape_color]
end
def local_spares
{tape_color: tape_color}
end
def default_tire_size
"23"
end
end
it 'default_tire_sizeを定義しないと、NotImplementedErrorが発生する' do
expect{bike = Foo.new(size: "L",tape_color: "red")}
.not_to raise_error(NotImplementedError)
end
it '#spares' do
bike = Foo.new(size: "L",tape_color: "red")
expect(bike.spares)
.to eq({:chain=>"10-speed", :tape_color=>"red", :tire_size=>"23"})
end
end
ポイント
では、superを使わないような書き方はどのようにすればいいのでしょうか?
各クラスに共通したメソッドを用意し、そのメソッドをフックメッセージの役割を果たせばよいわけです。
フックメッセージを使うとsuperを使わずに情報を送り、スーパークラスとサブクラスをより疎結合にすることができます。
この例では下記のメソッドがフックメッセージとして役割を果たしました。
def local_spares
{}
end
def post_initialize(args)
nil
end
注意点!
リファクタリング後のコードが完全な形になっていっているわけではありません。前よりかは良くなったと捉えてください。
関連する記事
参考文献
オブジェクト指向設計実践ガイド
~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
最後に
私がブログを書く目的は、素晴らしい本や、素晴らしい方々の技術記事を知って頂きたいからです。ぜひ、上記の参考文献を見て下さい。(noteなので広告とかは一切ありません。)
現在、株式会社grabssに行くために最後の悪あがきをしています!!
現在の進行状況
この記事は92件目の投稿。目標達成!!!!
目標再設定
20日までに100件目指す!!(92件目)
20日超えたが、寝るまでは20日営業日!!(まだ寝てない、ちょっと寝##峠を超え目が覚めた!)
気持ちは20日!
よろしければ、スキボタン及びサポートお願いします。勉強の励みになります。
この記事が気に入ったらサポートをしてみませんか?