Ruby
リファクタリング
webdbpress

いいコードって何だろう?

More than 1 year has passed since last update.

最近、リーダブルなコードを書くためにはどんなことを意識すれば良いのだろう?と常々考えるようになり、本屋巡りをしていたところ、web db press vol.99のいいコードって何だろう?特集が目に入り手にとってみた次第です。備忘録としてまとめて起きます。

第1章 いいコードを書く理由

なぜいいコードが必要なのか

普遍的な良いコードとは、

  • 名前付けがわかりやすく読みやすい
  • 適切な粒度で分割されている
  • 実行時のパフォーマンスが良い
  • 十分にテストされている
  • ビジネス的な価値が高い

かの有名なリーダブルコードの内容がそのまま当てはまるような感覚ですね。

その中でも、コードを書くことに費やせる時間には限りがあり、トレードオフが発生する。

その中で特に重視すべき指標を見つけることが重要である。

良いコードとは何かをチーム内あるいはサービス内で定義しておくのは重要かもしれませんね。

可読性なのかパフォーマンスなのか変更に強いのかをmustとwantで分けるのも手かも。どれかが解決すれば芋づる式に解決しそう。

良いコードとは何か?

webDBpress特集内では、

  • 書き手の意図通りに動く
  • 書き手の意図が読み手に伝わる
  • 将来の変更に強い

リーダブルかつ重複を避け変更箇所が少なく書かれていることが良いコードと定義づけています。

変更に強い

重複がある変更に弱いコード
if hour < 12
  puts "*** AM #{hour} 時 ***"
else
  puts "*** PM #{hour - 12} 時 ***"
重複のない変更に強いコード
def decorate_string(str)
  " *** #{str} *** "
end

def hour < 12
  puts decorate_string("AM #{hour} 時")
else
  puts decorate_string("PM #{hour - 12} 時")
end

囲む文字を * から # に変更しようと思った時に、二個目の方が変更箇所が少ない。

修正する範囲が小さく限定されているコードは、変更に強いコード(変更しやすい、変更した時の影響範囲が少ない、デバッグが容易になる)

より変更に強いコード
def decorate_string(str, enclosure: "***")
  "#{enclosure} #{str} #{enclosure}

第2章 変数、定数、メソッド

変数

いい変数と悪い変数

いい変数 = 「開発効率の向上」に貢献できる変数

悪い変数が及ぼす影響

悪い変数 = 情報量の少ない変数
(例) a, bなどどんなものを保持しているかわからない変数
-> 開発者の理解を妨げる問題

いい変数はスコープが狭い

foo.rb
class Foo
  attr_accessor :bar 

  def print(content)
    @bar + content
  end
end

foo = Foo,new
foo.bar = "WEB+DB press vol."

method1(foo)
method2(foo)
method3(foo)

foo.print("99") # => NoMethodError: undefined method `+'
for nil:NilClass

変数のスコープは、広いほど変更を加えられる可能性が上がります。

定数

一般に、何らかの設定値を書いたりエラーハンドリングだったりと手堅い部分でお世話になることが多い

マジックナンバーの意味を共有しよう

tweet_before.rb
class Tweet
  def initialize(content)
    @content = content
  end

  def post!
    if @content.length <= 140
      # 投稿
    else
      # エラー処理
    end
  end
end

おもむろに出て来た140という数字は何なんでしょうか?
マジックナンバー:何かしらの意味を持つが、書いた人にしかわからなくなるような数値
このような場合に定数を使うべき。

tweet_after.rb
class Tweet
  MAX_TWEET_LENGTH = 140

  def initialize(content)
    @content = content
  end

  def post!
    if @content.length <= MAX_TWEET_LENGTH
      # 投稿
    else
      # エラー処理
    end
  end
end

これにより、140という数値に明確な意味づけができるようになった。

メソッド

いいメソッドと悪いメソッドとは

メソッドに切り出す理由:
1. 関心ごとを集約させるため
2. 再利用性を高めるため

コンテキストが違うメソッドの共通化は、かえって影響範囲を広げてしまうデメリットがあるので注意が必要

クラス

クラスとは何か

責務という考え方

日頃から問いかけを忘れない
* このクラスは、この処理を担当すべきなのか?
* このクラスは、多くのことをやりすぎてないだろうか?

クラスを活用してコードのアウトラインを作る

名詞と動詞を洗い出していく

(例)動画投稿サイトの動画を再生する機能
「動画を再生する」の「動画」は名詞で、「再生する」は動詞です。名詞はクラスの、動詞はメソッドのヒントになってくれる。

video.rb
class Video
  def play
    # 動画を再生する
  end
end
playlist.rb
class Playlist
  def add(video)
    # プレイリストに動画を追加する
  end

  def play_all
    # プレイリストに含まれている動画を全て再生する
  end
end

クラスを活用しよう

デザインパターンの中からFacadeパターン、Strategyパターンを使う

Facade(ファサード)パターン

複雑な手続きを誰かに代行してもらおうというパターン

(例)コーヒーショップでカプチーノを注文するシーン
客はカプチーノをコーヒーショップの店員さんに注文します。お金を払ってしばらく待つとカプチーノが出て来ます。
客がやったことは、「カプチーノをください」と店員さんに伝えてお金を払い、カプチーノを受け取るだけです。しかし、豆やミルクなどの材料は提供するから、後は自由に作ってくださいというコーヒーショップの場合はどうか?カプチーノを飲むためにカプチーノの入れ方を覚える必要があります。Facadeパターンはこのような煩わしい手続きを代行するパターンです。

brew_cappuccino.rb
class Customer
  def order
    espresso = Espresso.new
    milk = Milk.new
    cup = Cup.new
    cup.receive(espresso.brew, milk.steam)
  end
end

(省略)

customer = Customer.new
puts customer.order

この場合、Customerクラスが淹れ方を知っているパターンで、セルフで作っている場合です。

これをfacadeパターンを適用すると、

brew_cappuccino_facade.rb
class Customer
  def order
    barista = Barista.new
    barista.brew
  end
end

class Barista
  def brew
    espresso = Espresso.new
    milk = Milk.new
    cup = Cup.new
    cup.receive(espresso.brew, milk.steam)
  end
end

customer = Customer.new
puts customer.order

Strategy(ストラテジ)パターン

Strategyパターンとは、アルゴリズムを取替え可能にするパターン。

新しくアイスコーヒーを取り扱いという要求があったとします。

brew_iced_coffee.rb
class Customer
  def order(type)
    barista = Barista.new
    barista.brew(type)
  end
end

class Barista
  def brew(type)
    case type
    when :cappuccino
      espresso = Espresso,new
      milk = Milk.new
      cup = Cup.new
      cup.recieve(espresso.brew, milk.steam
    when :iced_coffee
      coffee_bean = BitterCoffeeBean.new
      ice = Ice.new
      cup = Cup.new
      cup.recieve(coffee_bean.brew, ice.make)
    end
  end
end

(省略)

customer = Customer.new
# カプチーノを注文する
puts customer.order(:cappuccino)
# アイスコーヒーを注文する
puts customer.order(:iced_coffee)

Strategyパターン適用後

brew_iced_coffee_strategy.rb
class Customer
  def order(drink)
    barista = Barista.new
    barista.brew(drink)
  end
end

class Cappccino
   def brew
     espresso = Espresso,new
     milk = Milk.new
     cup = Cup.new
     cup.recieve(espresso.brew, milk.steam
   end
end

class IcedCoffee
  def brew
    coffee_bean = BitterCoffeeBean.new
    ice = Ice.new
    cup = Cup.new
    cup.recieve(coffee_bean.brew, ice.make)
  end
end

class Barista
  def brew(drink)
    drink.brew
  end
end

(省略)

customer = Customer.new
# カプチーノを注文する
puts customer.order(Cappuccino.new)
# アイスコーヒーを注文する
puts customer.order(IcedCoffee.new)

Strategyパターンは、brewのような統一したメソッドを実装する必要があります。

第4章 モジュール

モジュールとは何か?

単一継承と多重継承

再利用可能ないろいろな機能をクラスにもたせたいのであれば、クラスの継承という機能を使ってできます。例えば、あるクラスAに、クラスBとクラスCを継承させれば、クラスAは、クラスBとクラスCが持つ機能を得ることができる。

多重継承:複数のクラスを継承すること

単一継承:一つのクラスのみ継承すること

ruby, PHP, Javaなどは単一継承のみをサポート

インターフェース機能

コンセントの穴もインターフェイス
コンセントの穴の向こう側がどうなっているか知らなくても、電源コードを挿せば電気を得ることができる。

境界の向こう側の詳細を知らずとも、こちら側に見えているものだけを使って目的を果たせるという考え方が、OOP言語にそのまま適用されている。

モジュール

モジュールの制限:クラスを継承できず、インスタンス化することもできない。
この制限により、モジュールは多重継承がもたらしうる複雑さからうまく距離をおき、単一継承のようなシンプルさを維持している。

MIX-IN:クラスに複数のモジュールの機能を付加すること

オープンクロズド原則

「クラスは拡張に対して開いており、修正に対して閉じていなければならない」という原則
ビジネス要件の変更があった際に、既存コードを修正するのではなく、拡張のためのコードを追加するだけで対応できるようにする。

第5章 チーム開発でのテクニック

コードレビュー

コードの説明を人に伝えられるか

自分が書いたコードが人に説明できるまで塾考できているか自分に問うこと。

見通しの良いコードを書く

  • 一つのメソッドのステップ数を100行程度に抑える。
  • 深いネストを避ける
  • 重複している処理をまとめる

システム開発

機能要件と非機能要件

一つの要求を実現する上で、システムが保障しなければならないサービスの信頼性は、エンジニアが担保しなければならない。要求を実現できたとしても、応答速度が実用に堪えなかったり、何分かに一回、応答しない機能を実装してしまっては、要求を実現したとは言えない。
このようなシステムの信頼性を現す指標をRASIS(Reliability Availability Serviceablity Integrity Security)という。
RASIS:実現したい要求である機能要件以外の機能が正しく動作する仕組みを定義した非機能要件。

name mean
Reliability 信頼性
Availabylity 可用性
Serviceability 保守性
Integrity 安全性
Security 機密性

モノシリックな既存プロジェクトに立ち向かおう

DRY原則を守り、3アウトチェンジを行う。

3アウトチェンジ:3回同じ処理を見たらメソッドやクラスなどに抽出すること

参考

本気でコードレビューしてみた
WEB+DB PRESS Vol.99