ためすう

Ruby で MessagePack を使ってみる

2019-06-21

やったこと

MessagePack を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ gem list | grep msg
msgpack (1.2.10)

調査

gem インストール(もし必要だったら)

Gemfile

gem 'msgpack'
$ bundle install

pack と unpack してみる

MessagePack is an efficient binary serialization format

圧縮効率が良いらしいです。

$ rails c
Running via Spring preloader in process 56887
Loading development environment (Rails 5.2.3)
irb(main):001:0> msg = [1, 2, 3, "漢字"].to_msgpack
=> "\x94\x01\x02\x03\xA6\xE6\xBC\xA2\xE5\xAD\x97"
irb(main):002:0> MessagePack.unpack(msg)
=> [1, 2, 3, "漢字"]

メモリのサイズを見てみる

$ rails c
Running via Spring preloader in process 57354
Loading development environment (Rails 5.2.3)
irb(main):001:0> v = [1, 2, 3, "漢字"]
=> [1, 2, 3, "漢字"]
irb(main):002:0> require 'objspace'
=> true
irb(main):003:0> p ObjectSpace.memsize_of(v)
72
=> 72
irb(main):004:0> msg = [1, 2, 3, "漢字"].to_msgpack
=> "\x94\x01\x02\x03\xA6\xE6\xBC\xA2\xE5\xAD\x97"
irb(main):005:0> p ObjectSpace.memsize_of(msg)
40
=> 40

オブジェクトは圧縮されているようです。 (memsize_of で返される値は完全ではないらしい)

参考

第4章 アルゴリズムを交換する: Strategy part2

2019-06-20

「Rubyによるデザインパターン」の読書メモです。

時々、個人の見解入りです。

クラスベースの Strategy ではなく、コードブロックベースの Strategy パターンで書いてみます。

Strategy を適用する (簡易版)

class Report

  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(&formatter)
    @title = '月次報告'
    @text = ['順調', '最高']
    @formatter = formatter
  end

  def output_report
    @formatter.call(self)
  end
end

HTML_FORMATTER = lambda do |context|
  puts '<html>'
  puts "<head><title>#{context.title}</title></head>"
  puts('<body>')
  context.text.each do |line|
    puts("<p>#{line}</p>")
  end
  puts('</body>')
  puts '</html>'
end

PLAIN_TEXT_FORMATTER = lambda do |context|
  puts("*****#{context.title}*****")
  context.text.each do |line|
    puts(line)
  end
end

report = Report.new &HTML_FORMATTER
report.output_report

report = Report.new &PLAIN_TEXT_FORMATTER
report.output_report

コードブロックのストラテジは、そのインターフェースが単純で、1つのメソッドで事足りるようなときにのみ有効に働きます。

sidekiq で ログ出力する

2019-06-20

やったこと

Rails5 で利用している sidekiq のログを出力します。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep sidekiq
sidekiq (5.2.7)

前提

sidekiq がインストール済みとします。

Rails5 で sidekiq を使う – ためすう

調査

ログ出力の設定

config/initializers/sidekiq.rb

Sidekiq::Logging.logger.level = Logger::DEBUG

sidekiq を再起動

$ bundle exec sidekiq -C config/sidekiq.yml

ログを確認

queue を登録して、worker を実行すると下記のログが流れます。

$ less log/sidekiq.log

2019-06-15T14:17:59.230Z 61557 TID-ovrdi8s0t TaskWorker JID-fe2e63bbd5eb7d0221a72f55 INFO: start
2019-06-15T14:17:59.233Z 61557 TID-ovrdi8s0t TaskWorker JID-fe2e63bbd5eb7d0221a72f55 INFO: id 出力
2019-06-15T14:17:59.233Z 61557 TID-ovrdi8s0t TaskWorker JID-fe2e63bbd5eb7d0221a72f55 INFO: 333
2019-06-15T14:17:59.233Z 61557 TID-ovrdi8s0t TaskWorker JID-fe2e63bbd5eb7d0221a72f55 INFO: done: 0.003 sec

参考

第4章 アルゴリズムを交換する: Strategy part1

2019-06-19

「Rubyによるデザインパターン」の読書メモです。

時々、個人の見解入りです。

継承に基づいた設計は、いくつかの深刻な不利益をもたらします。たとえどんなに注意深くコードを設計しても、サブクラスはそのスーパークラスに依存してしまいます。

そこで、「委譲」を使ってコードを書き直してみます。

Strategy を適用する (version1)

class Formatter
  def output_report(title, text)
    raise 'Abstract method called'
  end
end

class HTMLFormatter < Formatter
  def output_report(title, text)
    puts '<html>'
    puts "<head><title>#{@title}</title></head>"
    puts('<body>')
    text.each do |line|
      puts("<p>#{line}</p>")
    end
    puts('</body>')
    puts '</html>'
  end
end

class PlainTextFormatter < Formatter

  def output_report(title, text)
    puts("*****#{title}*****")
    text.each do |line|
      puts(line)
    end
  end
end

class Report

  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(formatter)
    @title = '月次報告'
    @text = ['順調', '最高']
    @formatter = formatter
  end

  def output_report
    @formatter.output_report(@title, @text)
  end
end

report = Report.new(HTMLFormatter.new)
report.output_report

report.formatter = PlainTextFormatter.new
report.output_report

formatter を外から注入しているので、依存性の注入と言えそうです。

コンテキストとストラテジの間でのデータを共有する方法

  • コンテキストがストラテジオブジェクトのメソッドを呼び出すときに引数で渡す
  • ストラテジへと渡されたコンテキスト自身の参照を使う
    • データの流れが単純化されますが、コンテキストとストラテジ間の結合度を上げてしまうことにもなります。

Strategy を適用する (version2)

Formatter クラスはインターフェースを規定するためにしか、存在しません。

Ruby のダックタイピングの哲学に反するため、削除します。

class HTMLFormatter
  def output_report(title, text)
    puts '<html>'
    puts "<head><title>#{@title}</title></head>"
    puts('<body>')
    text.each do |line|
      puts("<p>#{line}</p>")
    end
    puts('</body>')
    puts '</html>'
  end
end

class PlainTextFormatter

  def output_report(title, text)
    puts("*****#{title}*****")
    text.each do |line|
      puts(line)
    end
  end
end

class Report

  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(formatter)
    @title = '月次報告'
    @text = ['順調', '最高']
    @formatter = formatter
  end

  def output_report
    @formatter.output_report(@title, @text)
  end
end

report = Report.new(HTMLFormatter.new)
report.output_report

report.formatter = PlainTextFormatter.new
report.output_report

sidekiq で namespace を使えるようにする

2019-06-19

やったこと

Rails5 で sidekiq を利用しているのですが、queue に namespace を指定するとエラーが発生しました。

今回はこのエラーを解消を目指します。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep sidekiq
sidekiq (5.2.7)

前提

sidekiq がインストール済みとします。

Rails5 で sidekiq を使う – ためすう

調査

発生したエラー

config/initializers/sidekiq.rb

Sidekiq.configure_server do |config|
  config.redis = { url: 'redis://localhost:6379', namespace: 'sidekiq' }
end

namespace を指定しました。

エラー

2019-06-15T07:14:53.315Z 47524 TID-ov7xxfh88 INFO: Booting Sidekiq 5.2.7 with redis options {:url=>"redis://localhost:6379", :namespace=>"sidekiq", :id=>"Sidekiq-server-PID-47524"}
2019-06-15T07:14:53.426Z 47524 TID-ov7xxfh88 INFO: Running in ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
2019-06-15T07:14:53.426Z 47524 TID-ov7xxfh88 INFO: See LICENSE and the LGPL-3.0 for licensing details.
2019-06-15T07:14:53.426Z 47524 TID-ov7xxfh88 INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
2019-06-15T07:14:53.429Z 47524 TID-ov7xxfh88 ERROR: Your Redis configuration uses the namespace 'sidekiq' but the redis-namespace gem is not included in the Gemfile.Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.

どうやら、redis-namespace をインストールする必要があるようです。

redis-namespace をインストール

Gemfile

gem 'redis-namespace'
$ bundle install

設定ファイルの修正

キューを入れるときの設定も追記します。

※ namespace も myapp に変えました。

config/initializers/sidekiq.rb

Sidekiq.configure_server do |config|
  config.redis = { url: 'redis://localhost:6379', namespace: 'myapp' }
end

Sidekiq.configure_client do |config|
  config.redis = { url: 'redis://localhost:6379', namespace: 'myapp' }
end

rails と redis をそれぞれ再起動したら、OKです。

参考

第3章 アルゴリズムを変更する: Template Method

2019-06-18

「Rubyによるデザインパターン」の読書メモです。

時々、個人の見解入りです。

修正前

例えば下記のようなコードがあったとします。

ここから、htmlではなくプレーンテキスト形式でもレポートを表示する要件が加わったとき、どうすれば良いでしょうか。

class Report
  def initialize
    @title = '月次報告'
    @text = ['順調', '最高']
  end

  # 簡略化
  def output_report
    puts('<html>')
    puts("<title>#{@title}</title>")
    @text.each do |line|
      puts("<p>#{line}</p>")
    end
    puts('</html>')
  end
end

Report.new.output_report

修正後

class Report
  def initialize
    @title = '月次報告'
    @text = ['順調', '最高']
  end

  def output_report
    output_start
    output_head
    output_body_start
    output_body
    output_body_end
    output_end
  end

private
  def output_body
    @text.each do |line|
      output_line(line)
    end
  end

  def output_start
  end

  def output_head
    output_line(@title)
    puts
  end

  def output_body_start
  end

  def output_line(line)
    raise "Called abstract method: #{__method__}"
  end

  def output_body_end
  end

  def output_end
  end
end

class HTMLReport < Report

private
  def output_start
    puts '<html>'
  end

  def output_head
    puts "<head><title>#{@title}</title></head>"
  end

  def output_body_start
    puts('<body>')
  end

  def output_line(line)
    puts("<p>#{line}</p>")
  end

  def output_body_end
    puts('</body>')
  end

  def output_end
    puts '</html>'
  end
end

class PlainTextReport < Report

private
  def output_line(line)
    puts(line)
  end
end


p '-- HTMLReport --'
HTMLReport.new.output_report

p '-- PlainTextReport --'
PlainTextReport.new.output_report

これで新しいフォーマットが必要になった場合も、サブクラス追加で出来ますね。

※ 本のサンプルコードに少し手を加えています。

おまけ

ダックタイピング

「もしアヒルのように見えて、アヒルのように鳴くのなら、それはアヒルである」

注意点

すべての起こりうる事態に対処するために、サブクラスに大量のメソッドをオーバーライドさせるようなテンプレートクラスを作成するのは避けましょう。

sidekiq で管理画面を表示する

2019-06-18

やったこと

sidekiq で管理画面を表示できるようにします。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep sidekiq
sidekiq (5.2.7)

調査

config/routes.rb

require 'sidekiq/web'
mount Sidekiq::Web, at: '/sidekiq'

http://localhost:3000/sidekiq/ にアクセスすると管理画面が見られます。

参考

序章,第1章 よいプログラムとパターン

2019-06-17

「Rubyによるデザインパターン」の読書メモです

時々、個人の見解入りです。

序章

デザインパターンは、ソフトウェアを構築する際に繰り返し発生する一般的な問題を認識し、解決する手助けをするという意味があります。

第1章 よいプログラムとパターン

パターンのためのパターン

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

理想的なシステムでは、すべての変更は局所的です。すべてのコードをくまなく調べる必要がないようにすべきです。

コードの流れを追うという意味では、そうだと思います。

同じプロダクトだけど、管理しているソースコードが違ったりすると、これは難しくなりそうです。

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

この記事に似たようなことを書いた気がするので置いておきます。

オブジェクト指向について – ためすう

継承より集約

継承

継承は、まさにその性質から、サブクラスをスーパークラスに結びつける傾向があります。スーパークラスの振る舞いを変更すれば、それはサブクラスの振る舞いを変えてしまうことになります。

test1.rb

class Vehicle
  def start_engine
    p "#{self.class.name}:#{__method__}"
  end

  def stop_engine
    p "#{self.class.name}:#{__method__}"
  end
end

class Car < Vehicle
  def sunday_drive
    start_engine
    # ... something
    stop_engine
  end
end

Car.new.sunday_drive
集約

test2.rb

class Engine
  def start
    p "#{self.class.name}:#{__method__}"
  end

  def stop
    p "#{self.class.name}:#{__method__}"
  end
end

class Car
  def initialize
    @engine = Engine.new
  end

  def sunday_drive
    @engine.start
    # something...
    @engine.stop
  end
end

Car.new.sunday_drive

Engine クラスを切り出しました。

Engine の差し替えもやりやすくなります。

委譲、委譲、委譲

委譲先のオブジェクトに責任転嫁する際に、追加のメソッド呼び出しが必要になります。

=> Ruby ではそのようなメソッドは書かなくてもOK (詳しくは、第10章、第11章)

必要になるまで作るな

YAGNI 原則

「You Ain’t Gonna Need It (必要になるまで作るな)」

将来のことは誰にも分からないので、いつか必要になる可能性もあると同時に

必要にならない可能性もあります。

本書で扱うGoFの14パターン

  • Template Method
  • Strategy
  • Observer
  • Composite
  • Iterator
  • Command
  • Adapter
  • Proxy
  • Decorator
  • Singleton
  • Factory Method
  • Abstract Factory
  • Builder
  • Interpreter

さらに

  • 内部ドメイン特化言語
    • 専門分野に特化した小さな言語を作るとても動的な仕組み
  • メタプログラミング
    • 必要なクラスやオブジェクトを実行時に動的に作るテクニック
  • Convention over Configuration (CoC)
    • 構成ファイル (たいていはXML)の憂鬱への治療薬

Ruby で alias を使う

2019-06-17

やったこと

Ruby で alias を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

app/controllers/tasks_controller.rb

class TasksController < ApplicationController
  def new
    logger.debug(__method__)
  end

  def new222
    logger.debug(__method__)
  end

  alias :new :new222
end

ログの確認

Started GET "/tasks/new" for ::1 at 2019-06-15 18:18:07 +0900
   (0.3ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
  ↳ /Users/xxxx/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
path is /tasks/new
Processing by TasksController#new as HTML
new222
  Rendering tasks/new.html.erb within layouts/tasks
  Rendered tasks/new.html.erb within layouts/tasks (264.0ms)
Completed 200 OK in 290ms (Views: 282.6ms | ActiveRecord: 0.0ms)

new222 が呼ばれます。

参考

アジャイルソフトウェア開発の奥義 第7章 リファクタリング

2019-06-16

「アジャイルソフトウェア開発の奥義」の読書メモです。

時々、個人の見解入りです。

腐敗するソフトウェアの兆候

硬さ

変更しにくいシステム

たった1つの変更で、それと依存関係のあるモジュールを芋づる式に変更しなければならないような場合、設計が「硬い」という。

もろさ

「もろさ」とは、たった1つの変更によって他の多くの部分が壊れてしまうような傾向があるとうことだ。

移植性のなさ

再利用が困難

扱いにくさ

  • ソフトウェアの扱いにくさ
  • 開発環境の扱いにくさ

不必要な複雑さ

設計している時点で不必要な要素が含まれている設計である。

必要になったタイミングで、最小限の修正をする方が良いと思います。

不必要な繰り返し

同じような構造を繰り返し含み、抽象化してまとめられる部分がまとまっていないソフトウェア

個人的に抽象化してあるとは、ポリモーフィズムを使っていることなのかなと考えています。

不透明さ

読みにくく、わかりにくい。その意図がうまく伝わってこないソフトウェア


ソフトウェアのプロジェクトにおいて最も不確実なものは仕様要求であることを忘れてはならない。仕様要求は常に不安定な状態にある。

設計にパッチを当て新しい要求に対応するような一時しのぎをするのではなく、将来同じような変更があった場合に対応できるように設計を改善するチャンスだととらえる。