最近、リーダブルなコードを書くためにはどんなことを意識すれば良いのだろう?と常々考えるようになり、本屋巡りをしていたところ、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などどんなものを保持しているかわからない変数
-> 開発者の理解を妨げる問題
いい変数はスコープが狭い
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
変数のスコープは、広いほど変更を加えられる可能性が上がります。
定数
一般に、何らかの設定値を書いたりエラーハンドリングだったりと手堅い部分でお世話になることが多い
マジックナンバーの意味を共有しよう
class Tweet
def initialize(content)
@content = content
end
def post!
if @content.length <= 140
# 投稿
else
# エラー処理
end
end
end
おもむろに出て来た140という数字は何なんでしょうか?
マジックナンバー:何かしらの意味を持つが、書いた人にしかわからなくなるような数値
このような場合に定数を使うべき。
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という数値に明確な意味づけができるようになった。
メソッド
いいメソッドと悪いメソッドとは
メソッドに切り出す理由:
- 関心ごとを集約させるため
- 再利用性を高めるため
コンテキストが違うメソッドの共通化は、かえって影響範囲を広げてしまうデメリットがあるので注意が必要
クラス
クラスとは何か
責務という考え方
日頃から問いかけを忘れない
- このクラスは、この処理を担当すべきなのか?
- このクラスは、多くのことをやりすぎてないだろうか?
クラスを活用してコードのアウトラインを作る
名詞と動詞を洗い出していく
(例)動画投稿サイトの動画を再生する機能
「動画を再生する」の「動画」は名詞で、「再生する」は動詞です。名詞はクラスの、動詞はメソッドのヒントになってくれる。
class Video
def play
# 動画を再生する
end
end
class Playlist
def add(video)
# プレイリストに動画を追加する
end
def play_all
# プレイリストに含まれている動画を全て再生する
end
end
クラスを活用しよう
デザインパターンの中からFacadeパターン、Strategyパターンを使う
Facade(ファサード)パターン
複雑な手続きを誰かに代行してもらおうというパターン
(例)コーヒーショップでカプチーノを注文するシーン
客はカプチーノをコーヒーショップの店員さんに注文します。お金を払ってしばらく待つとカプチーノが出て来ます。
客がやったことは、「カプチーノをください」と店員さんに伝えてお金を払い、カプチーノを受け取るだけです。しかし、豆やミルクなどの材料は提供するから、後は自由に作ってくださいというコーヒーショップの場合はどうか?カプチーノを飲むためにカプチーノの入れ方を覚える必要があります。Facadeパターンはこのような煩わしい手続きを代行するパターンです。
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パターンを適用すると、
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パターンとは、アルゴリズムを取替え可能にするパターン。
新しくアイスコーヒーを取り扱いという要求があったとします。
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パターン適用後
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アウトチェンジを行う。