blog

紫陽花

開発での小ネタ その2

開発での小ネタ その2

普段の開発で得た知見を書き留めておく

YouTubeへのリンクでクエリを付加するロジック

DBにURLが保存してありすでに存在すればそのまま。タグが付いていなければ付加する こちらを参考にしました。

def youtube_url
    uri = URI.parse("https://www.youtube.com/watch?v=WztpnvW4CHA")
    query_hash = {}
    query_hash.merge!(Hash[URI.decode_www_form(uri.query)]) if uri.query.present?
    query_hash.symbolize_keys!.merge!(tag: youtube.opening_bgm_id)
    uri.query = URI.encoded_www_form(query_hash.to_a)
    uri.to_s
end

URI.parseでURLをオブジェクトに変換し、クエリが存在するか判定する if query.present?

存在するならば、URI.decode_www_formを用いてクエリをkeyとvalueのhash形式にしquery_hashに格納する。

文字列(“hoge”)とシンボル(:hoge)が同一な存在とするため、symbolize_keys!を用いて文字列をシンボルに変換します。

.merge!(v: youtube.opening_bgm_id)で付加したいidをmergeする。

mergeメソッドはすでに同じkeyが存在する場合に上書きします。

URI.encoded_www_form(query_hash.to_a)でクエリをuriのqueryに格納して完了!

URI周りを厳密に処理を行いたい場合は以上のようなやり方で操作すると良さげです。

Railsでhttpリクエストをhttpsにリダイレクトさせる

gem "rack-ssl-enforcer"

rack-ssl-enforcerを用います。 Gemをbundle installし、config/environments/** に以下を記述します。

config.middleware.use Rack::SslEnforcer

これでSSL通信に限定ます。 ロードバランサーなどを用いておりヘルスチェックのhttpリクエストは通したいとい場合は以下の記述を追加。

# except: ["/ping"]を追加してsslの制限をexceptすることで解決
config.middleware.use Rack::SslEnforcer, except: ["/ping"]

enumerizeでscope: trueにするとscopeとして使える

class User < ApplicationRecord
  extend Enumerize
  enumerize :sex, :in => [:male, :female], scope: true
  enumerize :status, :in => { active: 1, blocked: 2 }, scope: :having_status
end

User.with_sex(:female)
# SELECT "users".* FROM "users" WHERE "users"."sex" IN ('female')

User.without_sex(:male)
# SELECT "users".* FROM "users" WHERE "users"."sex" NOT IN ('male')

User.having_status(:blocked).with_sex(:male, :female)
# SELECT "users".* FROM "users" WHERE "users"."status" IN (2) AND "users"."sex" IN ('male', 'female')

slackへのアクション通知はslack-notifierがおすすめ

gem "slack-notifier"
notifier = Slack::Notifier.new(Rails.application.config.slack_webhook_url)
notifier.ping(message)

上記で実行するとslack_webhook_urlに対してmessageが送られる。 リッチな通知を実現したい時には以下のようにattachementsを利用すると良い。

message = {
 title: 'タイトル',
 title_link: 'タイトルのリンク',
 text: 'テキスト文',
 image_url: '表示させるサムネイルのURL',
}
SlackNotifier.ping(attachments: [message])

こちらを参考にしました。

バッチなどで大量のデータを操作する際にはfind_eachと良い

分割してレコードを取得して処理をする。 デフォルトで1000件ずつ処理をする。

Railsには find_each というメソッドが用意されています。通常の each メソッドを使用すると、全データをまとめてメモリに展開してから処理を開始します。そのため、十分にメモリに載るデータ量であれば何も問題ないですが、数百万、数千万というデータ量になってくるとメモリに載りきらずに溢れてしまって大変なことになります。 find: 全データをメモリに展開してから処理 find_each: 少しずつデータをメモリに展開しつつ処理 そういうときには find_each メソッドを使いましょう。

参考にしました。 Railsで大量のデータをまとめて更新するならfind_each使うよね

Page.where(:category_id => 1).find_each do |page|
    # hogehoge
end

引数の管理にはハッシュとfetchを使おう

クラス設計で依存関係の管理を行う時のテクニックです。 引数が必要なメソッドを定義した時に値を渡す順番が元から決まっておりそれに沿った形で値を渡さなければいけない状況が多々あります。 もしくは、引数が必要がない場合とある場合を想定したメソッドがあったりします。そんな問題を解決する際には引数のHash化とfetchを用いると良い感じになります。

導入前

class Gear
  attr_reader :chainring, :cog, :wheel 
  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog       = cog
    @wheel     = wheel
  end
end

Gear.new(52, 11, Wheel.new(26, 1.5)).gear_inches

導入後

class Gear
  attr_reader :chainring, :cog, :wheel 
  # fetchを使ってデフォルト値を指定している
  def initialize(args={})
    @chainring = args.fetch(:chainring, 40)
    @cog       = args.fetch(:cog, 18)
    @wheel     = args[:wheel]
  end
end

Gear.new(
    chainring: 52, 
    cog:       11,
    wheel:     Wheel.new(26, 1.5)).gear_inches

引数をHashで渡すことにより引数を渡す順番の依存がなくなり、 またfetchメソッドを用いることでデフォルト値を設定することも可能になるのでより柔軟性のあるコードになる。良い!!!

saveメソッドを用いて実行するvalidationを指定する

validatesメソッドのonオプションを用いることで走らせるメソッドを指定できる

validates :title, presence: true, on: :create

こうすれば、createメソッドの時にのみvalidationが走ることになる。 saveメソッドのcontextオプションと一緒に用いることで自由に実行するバリデーションを指定できるようになる。

validates :category, presence: true, on: :publish

@obj.attributes = { title: "hoge", content: "こんてんつ" }
@obj.save(context: :publish)

すごく良い

複数のvalitetionをかける場合は、

with_options on: :publish do |publish|
  publish.validates :title, presence: true
  publish.validates :content, presece: true
end

みたいな感じで定義できるっぽい。

状況によってsave時に実行するバリデーションを切り替える

こちらの記事を参考にしました。素晴らしい知見に感謝

メソッドキャッシュを用いて少しでも高速化を図る

class Hoge
  attr_accessor :obj
  def huga
    @obj ||= begin
      sleep 3
      "aaa"
    end
  end
end

一回目にhogeを実行する時よりも2回目の方が実行が早くなる。@objインスタンス変数に値が存在すればそれを使うという書き方をすることで不必要な処理を省く。 すごく細かいところだかこう小さな気遣いが後々効いてくる。なお、使いどころには注意すること

RSpecでスタブを定義しテストをする

スタブ

テストを行うためにメソッドにダミーの振る舞いを定義すること

  • allowでクラス名を指定
  • receiveでメソッドを指定
  • and_returnでメソッド実行の戻り値を指定
describe "" do
  before do
    allow_any_instance_of(Twitter).to receive(:post).and_return(true)
   end
end

テストを実行する前に 振る舞いを定義しておく

describe "" do
  before do
    allow_any_instance_of(Twitter).to receive(:post).and_raise(ArgumentError)
  end
end

and_raiseで例外エラーを発生させることも可能

コレクションクロージャメソッドのすゝめ

collect (map)、select (filter)、inject (reduce) メソッドを用いて処理を簡潔で分かりやすいものに書き換える

ループを取り除いてコレクションクロージャメソッドを使うと、コードをたどりやすくなる。コレクションクロージャメソッドは、コレクションの中を行き来したり、派生コレクションを作ったりするためのインフラストラクチャコードを隠し、ビジネスロジックに集中できるようにしてくれる。

引用: リファクタリング Ruby エディション

.select フィルタ処理

# before
managers = []
employees.each do |e|
  managers << e if e.manager?
end

# after
managers = employees.select{ |e| e.manager? }

.collect 収集処理

# before
offices = []
employees.each{ |e| offices << e.office }

# after
offices = employees.collect{ |e| e.office }

.select & .collect 複数の処理をまとめて

# before
managerOffices = []
employees.each do |e|
  managerOffices << e.office if e.manager?
end

# after
managerOffices = employees.select{ |e| e.manager? }
                          .collect{ |e| e.office }

gsubメソッド ブロックを用いる

Rubyのgsubメソッドは置換を行うメソッド

第1引数にマッチした値を第2引数の値に置き換える

values = "apple cherry banana"
values.gsub("apple", "pear")
#=> "pear cherry banana"

ブロックを使う方法があることを知った。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/) {"<p>#{$1}</p>"}
# => "<p>ruby</p> <p>python</p>"

$1から$9が特殊変数として定義されているそうな。

Ruby .mapの返り値は必ず配列が返ってくる

.mapというメソッドがあるがこれをハッシュに対して実行した際に、返り値がハッシュではなく配列が返ってきた。 ハッシュの返り値を期待していたもののmapメソッドは必ず配列が返る仕様になっているようで詰まった。 .mapには別名で.collectメソッドも存在する

each_slice each_with_object()

Rubyの配列操作ではメソッドが豊富に用意されている。

.each_sliceメソッドは引数に渡した数ごとに分割した値を返してくれる

.each_with_objectメソッドはinjectメソッドに似ている配列操作内で使用したいオブジェクトをブロック内で使えるようにする LINK

Observerパターン Ruby

Observerパターン

オブザーバーパターンとは

Observerとは「観測者」 あるオブジェクトの状態が変化した際に、そのオブジェクト自信が「観測者」に「通知」をする仕組み。 オブザーバーは3つのオブジェクトで構成される。

  • サブジェクト(subject):変化する側のオブジェクト
  • オブザーバ(Observer):変化を関連するオブジェクトに通知するインターフェース
  • 具象オブザーバ(ConcreateObserver):状態の変化に関連して具体的な処理を行う

状態とは、クラスが持つ変数の値のこと。

用いるメリット

  • オブジェクト間の依存度を下げることができる
  • 通知先の管理をオブザーバーが行うことで、サブジェクトは通知側を意識しなくていい

実装

  • Employee(サブジェクト):従業員を表す
  • Observable(オブザーバ):従業員のニュースを監視する仕組み(observer/Observable)
  • Payroll(具体オブザーバ1):給与の小切手の発行を行う
  • TaxMan(具体オブザーバ2):税金の請求書の発行を行う
require 'observer'
class Employee
  include Observable # Observerとして働く

  attr_reader :name, :title, :salary

  def initialize(name, title, salary)
    @name = name
    @title = title
    @salary = salary
    # 監視するインスタンスを追加
    add_observer(Payroll.new)
    add_observer(TaxMan.new)
  end

  # 給与をセットして、ConcreteObserverに通知する
  def salary=(new_salary)
    @salary = new_salary
    # changedで変化があったかを確認する
    changed
    # 変化があった場合に以下実行される
    notify_observers(self)
  end
end

#----------------具体オブザーバ---------------

# 給与の小切手の発行を行う
class Payroll
  # 変更通知用にupdateメソッドを持っている
  def update(changed_employee)
    puts "彼の給料は#{changed_employee.salary}になりました!#{changed_employee.title}のために新しい小切手を切ります。"
  end
end

# 税金の請求書の発行を行う
class TaxMan
  # 変更通知用にupdateメソッドを持っている
  def update(changed_employee)
    puts "#{changed_employee.name}に新しい税金の請求書を送ります"
  end
end

john = Employee.new('John', 'Senior Vice President', 5000)
john.salary = 6000
#=> 彼の給料は6000になりました!Senior Vice Presidentのために新しい小切手を切ります。
#=> Johnに新しい税金の請求書を送ります
john.salary = 7000
#=> 彼の給料は7000になりました!Senior Vice Presidentのために新しい小切手を切ります。
#=> Johnに新しい税金の請求書を送ります

ストラテジーとの違い

  • オブザーバー:発生しているオブジェクトに対してイベントを通知している
  • ストラテジー:何らかの処理を行うためにオブジェクトを取得している

Strategyパターン Ruby

Strategyパターン

ストラテジーパターンとは

抽象的な処理と具象的な処理を分離することで、 変化に強い構造を実現する。

委譲を用いてアルゴリズムを変更可能にする。

目的の部分としてはテンプレートメソッドパターンと似ているが、

問題に対する解決のアプローチが異なる。

ストラテジは「戦略」という意味。

条件によってアルゴリズムを切り替えるところは、まるで戦略を練っているようだからその名がついたそうな。

ストラテジーパターンの最終的な目的は、 アルゴリズムの交換」を行うこと。

テンプレートメソッドパターンが抱える問題

テンプレートメソッドパターンは 継承をベースにしているといこと。

これによりよろしくないつながりを生んでしまう。

良い部分を作り出すと同時にそこで生まれたつながりが複雑にしてしまう。

よろしくないつながりを生み出さずに変化しやすい形をとりたい。

そんなときに使うのがStrategyパターン

変化しやすいコードの塊を抽出し、 全く別のクラスに閉じ込める。

Strategyパターンでは、アルゴリズムの部分を他の部分と意識的に分離する。

そしてアルゴリズムとインターフェースを規定し、アルゴリズムの部分は委譲を用いて処理を他クラスの使って解決する。

これによりアルゴリズムは代替可能な状態になるため変更が容易になる。

アルゴリズムのクラス化」

構成

  • コンテキスト(Context):ストラテジの利用者
  • 抽象戦略(Strategy):同じ目的をもった一連のオブジェクトを抽象化したもの
  • 具象戦略(ConcreateStrategy):具体的なアルゴリズム

用いるメリット

  • 使用するアルゴリズムに多様性を持たせることができる
  • コンテキストと戦略を分離することでデータも分離することができる
  • 継承よりもストラテジを切り替える方が楽

実装

  • Report(コンテキスト):レポートを表す
  • Formatter(抽象戦略):レポート
  • HTMLFormatter(具象戦略):HTMLフォーマットでレポートを出力
  • PlaneTextFormatter(具象戦略):PlaneTextフォーマットでレポートを出力

f:id:Yamakichi:20161121000756p:plain

処理をstrategy(戦略)のclassとして定義する。 Formatterクラスを抽象戦略として定義しているが、これはインターフェースを定義するだけのクラスになるので、 Rubyらしくはありません。不要なら定義しなくても問題はない(ダックタイピングの哲学)

# レポートの出力を抽象化したクラス(抽象戦略)
class Formatter
  def output_report(title, text)
    raise 'Called abstract method !!'
  end
end

# HTML形式に整形して出力(具体戦略)
class HTMLFormatter < Formatter
  def output_report(report)
    puts "<html><head><title>#{report.title}</title></head><body>"
    report.text.each { |line| puts "<p>#{line}</p>" }
    puts '</body></html>'
  end
end

# PlaneText形式(*****で囲う)に整形して出力(具体戦略)
class PlaneTextFormatter < Formatter
  def output_report(report)
    puts "***** #{report.title} *****"
    report.text.each { |line| puts(line) }
  end
end

レポート側の実装

# レポートを表す(コンテキスト)
class Report
  attr_reader :title, :text
  attr_accessor :formatter

    # 引数にformatterオブジェクトを渡す(依存オブジェクトの注入)
  def initialize(formatter)
    @title = 'report title'
    @text = %w(text1 text2 text3)
    @formatter = formatter
  end

    # @formatterのoutput_reportに処理を委譲する
  def output_report
    @formatter.output_report(self)
  end
end

report = Report.new(HTMLFormatter.new)
report.output_report
#<html><head><title>report title</title></head><body>
#<p>text1</p>
#<p>text2</p>
#<p>text3</p>
#</body></html>

report.formatter = PlaneTextFormatter.new
report.output_report
#***** report title *****
#text1
#text2
#text3

注意すべきこと

  • コンテキストとストラテジ間のインターフェースがストラテジの種類の増加を妨げないようにすること
  • コンテキストの変更がストラテジに影響を与えないようにすること

TemplateMethod パターン Ruby

TemplateMethodパターン

テンプレートメソッドパターンとは

抽象的な処理と具体的な処理を分離することで、 変化に強い構造を実現する。

2つのオブジェクトによって構成される。

  • 骨格としての「抽象的なベースクラス」
  • 実際の処理を行う「サブクラス」

用いるメリット

  • 抽象的なベースクラス側に、「変わらない基本的なアルゴリズム」を置ける
  • 抽象的なベースクラスは「高レベルの処理」を制御することに集中出来る
  • サブクラス側に、「変化するロジック」を置ける
  • サブクラスは「詳細を埋めること」に集中出来る

f:id:Yamakichi:20161119155738p:plain

実装

  • Report(抽象的なベースのクラス): レポートを出力する
  • HTMLReport(サブクラス): HTMLフォーマットでレポートを出力
  • PlaneTextReport(サブクラス): PlanTextフォーマットでレポートを出力
ベースとなるクラス
class Report
  def initialize
    @title = "html report title"
    @text = ["report line 1", "report line 2", "report line 3"]
  end

  # レポートの出力手順を規定
  def output_report
    output_start
    output_body
    output_end
  end

  # レポートの先頭に出力
  def output_start
    raise NotImplementedError
  end

  # レポートの本文の管理
  def output_body
    @text.each do |line|
      output_line(line)
    end
  end
具体的な処理を実装したサブクラス
HTMLReport
class HTMLReport < Report
  # レポートの先頭に出力
  def output_start
    puts "<html><head><title>#{@title}</title></head><body>"
  end

  # 本文内のLINE出力部分
  def output_line(line)
    puts "<p>#{line}</p>"
  end

  # レポートの末尾に出力
  def output_end
    puts '</body></html>'
  end
end
PlaneTextReport
class PlaneTextReport < Report
  # レポートの先頭に出力
  def output_start
    puts "**** #{@title} ****"
  end

  # 本文内のLINE出力部分
  def output_line(line)
    puts line
  end
end
実行
html_report = HTMLReport.new
html_report.output_report
#<html><head><title>html report title</title></head><body>
#<p>report line 1</p>
#<p>report line 2</p>
#<p>report line 3</p>
#</body></html>

plane_text_report = PlaneTextReport.new
plane_text_report.output_report
#**** html report title ****
#report line 1
#report line 2
#report line 3
変わるもの(具象サブクラス)と変わらないもの(テンプレート)を分離して、変化に強い構造へ。

具象クラスによってオーバーライド出来る(不要ならしなくてもよい)メソッドのことを、 フックメソッドと呼ぶ

凡化 = 抽象化

特化 = 具象化

Special Thanks

Rubyを用いてデザインパターンを学ぶ

オブジェクト指向には設計原則とデザインパターンが存在する。

デザインパターンとは

Gang Of Four(GoF)が定義したもの Javaで書かれた本がある。 「オブジェクト指向における再利用のためのデザインパターン」 1999/10 出版

  • プログラミングにおいて繰り返し現れる問題に対する、適切解のパターン
  • 無駄なく設計されたオブジェクト指向プログラムの実現をサポート
  • 諸刃の剣

パターンとしてカタログ化されていることで 車輪の再発明を防ぐ

デザインパターンに重要な5つの考え

  • 変わるものを変わらないものから分離する
  • プログラムはインターフェースに対して行う (実装に対しては行わない)
  • 継承より集約
  • 委譲、委譲、委譲
  • 必要になるまで作るな (YAGNI)

変わるものを変わらないものから分離する

理想的なシステムは、すべての変更が局所的であるべき。 ソフトウェアに完璧は存在しない。 変わるものを変わらないものから分離することによって、 将来起こりうる「新たな変更」に対して柔軟に対応できるようにしておく。

プログラムはインターフェースに対して行う (実装に対しては行わない)

可能な限り「一般的・抽象的なもの」に対してプログラミングすること。 (ここで言うインターフェイスは、Javaの組み込み構文としてのインターフェイスではなく、 より広いレベルで、「抽象度を高めたもの」を意味する。)

Bad

具象性が高く、密結合なコード

if is_car
    my_car = Car.new
    my_car.drive(200)
else
    my_car = get_vehicle
    my_vehicle.travel(200)
end

Good

抽象度が高く、疎結合なコード

my_vehicle = get_vehicle
my_vehicle.travel(200)

抽象度を高くすることによりコードにより変更に強くなる

継承より集約

継承はよろしくない繋がりをつくってしまう

class Vehicle
  def start_engine
    # エンジンスタートの処理
  end

  def stop_engine
    # エンジンストップの処理
  end
end

class Car < Vehicle
  def drive(distance)
    start_engine()
    # driving....
    stop_engine()
  end
end
  • Carからエンジンの実装が丸見え
  • エンジンを使用しない乗り物を作りたい場合には大きな変更を加える必要が出てくる

-> 変わりやすい部分(Engine)を変わりにくい部分(Vehicle)から分離できていない

じゃあどうするのか?

集約を使い解決する。 オブジェクトに「他のオブジェクトに対する参照」を持たせる。 オブジェクトが何かの一種である(is-a-kind-of)関係(継承)を避け、 何かを持っている(has-a)関係(集約)にする

依存オブジェクトの注入

class Engine
  def start
    # エンジンスタートの処理
  end

  def stop
    # エンジンストップの処理
  end
end

class Car
  def initialize
    @engine = Engine.new() # 依存オブジェクトの注入
  end

  def drive(distance)
    @engine.start()
    # distanceだけ走る処理
    @engine.stop()
  end
end

これでEngineをVehicleから分離し、カプセル化された 依存オブジェクトの取り扱いには注意する必要がある。

委譲、委譲、委譲

委譲(delegation)
class Car
  def initialize
    @engine = GasolineEngine.new
  end

  def drive
    @engine.start
    # driving..
    @engine.stop
  end

  def switch_to_diesel
    @engine = DieselEngine.new
  end

  def start_engine
    @eigine.start  # Engineクラスに任せる
  end

  def stop_engine
    @eigine.stop  # Engineクラスに任せる
  end
end
集約と委譲の組み合わせは、強力かつ柔軟な継承代替手段となる。

必要になるまで作るな (YAGNI)

You Ain't Gonna Need It
「将来使うかも」は、大抵は使わない

これはデザインパターンに限った話ではなくアプリケーション開発全般においても言えること。 不要な処理は書かない。 必要になった時に必要な分だけの実装を行い無駄のないシステムを作る。

Special Thanks

オブジェクト指向設計 実践ガイド 読んだ

オブジェクト指向設計 実践ガイド 」を一通り読み終えました。

結構読むのに時間がかかってしまったのですがとても良い内容でした! オブジェクト指向についてはなんとなく知っているけれども、しっかり実践で活用しきれていない中級プログラマーの方が読むといいかもしれません。

よくオブジェクト指向関連の本に出てくるワード、「ポリモーフィズム」「ダックタイピング」「単一責任原則」「デザインパターン」など。

この本では実際にそれらのワードについてソースコードリファクタリングのような形(before|after)で分かりやすく説明してくれています。

なので、読んでいると「あぁ、これってこういう風に使うと便利なのか!」という気づきが多くありました。

実際の現場でオブジェクト指向で組まれたプログラムを見たことがあるのですが、実践ガイドを読む前と、読んだ後ではコードの見方が大きく変わりました。

他の方も書評で書かれていたのですが、文章が少し抽象的な表現が多かったかなと。 読んでてこれってどういうこと?みたいなところが所々ありました。 読み手の理解力の無さが原因でもあるのですが(笑)、自分は読み解くために2、3回時間を空けて読み直すことで理解を深めていきました。

今までオブジェクト指向関連は、ネットの記事や先輩の説明などでなんとくなく理解していたつもりでいました。 今回本を用いて体系的に学ぶことで型を学ぶのは大事だなと改めて思いました。

特に印象に残ったところを簡単にメモ書きしておきます。

メモ

  • 実用的な設計とは、未来を予測するものではなく、動くための余地を設計者に残すものです。
  • 彫刻家がノミとやすりを持つように、オブジェクト指向の設計者も道具を持つ。それが、原則とパターン
  • 設計者の目標は、機能あたりのコストが最も低い方法でソフトウェアを書くこと
  • 実践とは配られたカードでベストを尽くすことの積み重ねなのです
  • 設計とはアプリケーションの可変性を保つために技巧を凝らすことであり、完璧を目指す行為ではありません
  • 見通しが良い(Transparent)・合理的(Reaspnable)・利用性が高い(Usable)・模範的(Examplary) TRUEなコードを書く
  • メソッドも単一責任にし、なるべくコードを単純化する
  • オブジェクト指向アプリケーションは「クラスから成り立つ」が、メッセージによって「定義される」
  • パブリックインターフェースとプライベートインターフェースの使い分けで不要な関連づけを防ぐ
  • 依存オブジェクトの注入・ダックタイピング・テンプレートメソッドパターン・ファクトリーパターン・コンポジション・共通モジュール

12/16(金) 追記

Qiitaでjoker1007さんが「オブジェクト思考設計実践ガイド」を読め。と言及していました。

俺が悪かった。素直に間違いを認めるから、もうサービスクラスとか作るのは止めてくれ

Railsに用いるserviceクラスの問題点について述べられています。 後半にはオブジェクト指向設計に関する重要な要点を完結にまとめてくださっています。感謝 学習のメモとして引用させてもらいます。

  • オープン・クローズドの原則
  • 単一責任の原則
  • デメテルの法則
  • クラス同士の依存関係の管理
  • 実装ではなくインターフェースに依存する
  • ダックタイプとクラスの中に内在するインターフェース
  • 集約クラス

オープンクローズドの原則

先に述べた様に、変更が発生した場合に自分以外の外に影響がもれずに閉じている状態が望ましい。 でないと、ある処理を変えた時に連鎖的に調査範囲が拡大して変更をコントロールできず死ぬ。 もしサービスクラスが、モデルレイヤーのメソッドをいくつも直接呼び出しているとすると、モデルにちょっとした変更が入ったらぶっ壊れるかもしれない。 そんなのがいくつもあると辛みしかない。

単一責任の原則

一つのクラスが担う責務は一つにすること。 ActiveRecordを元にしたクラスはRDBという根幹になるデータ構造と紐付いているため、複数の役割を担うことになる可能性が高い。しかし、それを別のクラスに分割することはできる。 責任範囲でクラスを分けることを意識しないと半端に元のクラスと結合した扱いづらいクラスが生まれることになる。 モジュールで分けても本質的には抱え込んでいる責務を分けたことにはならない。 どういった責任をどういった名前を持ったクラスに与えるべきなのかを考えて、クラスを作っていくことが大事だ。

デメテルの法則

あるクラスが触っていいのは、自分が直接知ってるクラスだけという状態を維持しなければならない。 これを守ろうとしないと、あるクラスがあちこちのクラスに依存し、処理の詳細まで抱え込んだ結果、変更が発生した時の影響範囲が拡大し過ぎて爆死することになる。 また、デメテルの法則に反したコードは、隣人クラスに問い合わせして取得した結果を元に自分で判断する様なコードになることが多い。 こういったコードは前述した様にテスタビリティが悪い。

クラス間の依存関係

自分んおクラスが直接持っている情報以外を触るということは、外部の何かの知識に依存するということであり、それが存在していないと処理が完結しないことを意味する。 自分以外のクラス名や、それが持つメソッド名、メソッド名、メソッドの引数、メソッドの返り値、外部クラスの処理結果を知れば知る程依存関係が強くなり、変更が辛くなる。 依存が少なくなる様な構造を考える必要がある。 また、依存関係は一方向に保つこと。相互依存するような関係はオブジェクトの生成プロセスや処理の流れを激しく複雑にする

メソッド名の抽象化、引数の取り扱い、依存オブジェクトの抽出 etc... 少し意識を変えて書くだけで依存関係を適切な形に変更できる。

インターフェースに対して依存する

あるクラスが知っておくべき知識をできるだけ抽象化しておいた方が良い。 具体的な処理内容はとても変わりやすく、その内容に依存しているとすぐ壊れる。 そして単純に読みにくさが増す(実装を行ったり来たりしないと何が起きてるか把握できない)。 ポリモーフィスムの力を活かす上でもインターフェースを意識したコードを書くことは重要。

例: 抽象化 carやbikeではなくvehicleとする

ダックタイプ

Rubyという言語は何も記述しなくても、メソッドの定義だけを元に対象が何者であるかを判断する。 しかし、それはインターフェースが消えて無くなったわけではない プログラマが逐一書かなくてもいいようになっているだけで、その処理に利用される各クラスはインターフェースの実装としての姿を持っている必要がある。 どの様にインターフェースを見出してクラス間の依存関係をどう表現するかは、プログラマの脳内にある業務領域とクラス構造のマッピングにかかっている。 プログラマが楽できる分、その知識に信頼性が求められている。

集約クラス

クラスが持つ責任範囲には境界線がありグルーピングできることが多い。 ここから先の詳細は全部こいつに任せる、後は知らなくていい、そういうクラス同士の関わり方の地図を作っておかなければいけない。 そうでないと、複雑さが組み合わせによって無限に増えて行く。 Ruby/Railsでは、それを言語レベルで強制する手段は無いので、プログラマの責任として守らなければいけない。 V字型により詳細を知っているクラスにブレイクダウンしていく動きを想像して欲しい。 モデル層の中にも階層構造は存在するし、処理の大枠だけを管理するコントローラーの様な存在はある。

MySQL5.7 JSON型の使い方メモ

MySQL5.7から実装されたJSON型 使い方メモ

JSON型のテーブルを作成する

CREATE TABLE `blocks` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(120) NOT NULL,
  `content` varchar(200) NOT NULL,
  `meta` json DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

JSON型のデータを追加する

INSERT INTO `blocks` (`title`, `content`, `meta`)
VALUES (
  'qiita',
  'ruby',
  '{"ruby":2.3,"rails":5,}'
);

引数のデータを確認する

SELECT JSON_TYPE('["ruby", "rails", "jquery"]');
return ARRAY


オブジェクトに変換し返す

SELECT JSON_OBJECT('ruby', 2.3, 'rails', 5);
return {"ruby": 2.3, "rails": 5}

データの結合を行う

SELECT JSON_MERGE('["ruby", 2.3]', '{"rails": "5"}');
return ["ruby", 2.3, {"rails": "5"}]

値を検索する
$.key名で値を探し、値が存在しない場合にはNULLが返ってきます。

SELECT JSON_EXTRACT('{"ruby": 2.3, "rails": "5"}', '$.rails');
return "5"

SELECT JSON_EXTRACT('{"ruby": 2.3, "rails": "5"}', '$.php');
return NULL

WHERE句でJSON_EXTRACTを使うことでJSON型の中身で一致たした結果のレコード返す
LIKE文も使えるので便利
metaカラム全体にたいしてLIKE検索をかけることももちろんできる

SELECT * FROM blocks WHERE JSON_EXTRACT(meta, '$.ruby') = "2.3";

SELECT COUNT(id) FROM blocks WHERE JSON_EXTRACT(meta, '$.key') LIKE "%value%";

SELECT id, meta, created_at FROM blocks WHERE meta LIKE "%rai%" ORDER BY created_at DESC;


JSON型は使えるようになれば便利ではあるなと思いました。