Memento memo.

Today I Learned.

Ruby Style Guide読んだ && 一部抜粋

github.com

普段はRubocopのおかげで概ねStyle準拠できているはず、と思ってたのですが、 改めてStyle Guideを読むと知見がたくさんありました。

以下、個人的に把握できてなかった箇所を抜粋しつつ、今後意識していきます。

andとorの使用は禁止

使うべき理由がないです。 常に、代わりに&&と||を使いましょう。

メソッドチェーンでのdo...endは避ける

# 悪い例
names.select do |name|
  name.start_with?('S')
end.map { |name| name.upcase }

# 良い例
names.select { |name| name.start_with?('S') }.map(&:upcase)

do...end.map やりたくなる場面がたまにあるんですが、我慢します。

代入部分を括弧で囲まずに、=の返り値を条件式に用いてはいけない

# 悪い例 (+ 警告が出ます)
if v = array.grep(/foo/)
  do_something(v)
  ...
end

# 良い例 (MRIはこれでも文句を言いますが、RuboCopでは問題ありません)
if (v = array.grep(/foo/))
  do_something(v)
  ...
end

# 良い例
v = array.grep(/foo/)
if v
  do_something(v)
  ...
end

変数がまだ初期化されていないときにだけ初期化したいのであれば、||=を使う

# 悪い例
name = name ? name : 'Bozhidar'

# 悪い例
name = 'Bozhidar' unless name

# 良い例 - nameがnilかfalseの場合のみ、Bozhidarで初期化します
name ||= 'Bozhidar'

値が入っているかわからない変数の前処理のは&&=を用いる

# 悪い例
if something
  something = something.downcase
end

# 悪い例
something = something ? something.downcase : nil

# ok
something = something.downcase if something

# 良い例
something = something && something.downcase

# より良い例
something &&= something.downcase

Proc.newよりprocを使う

# 悪い例
p = Proc.new { |n| puts n }

# 良い例
p = proc { |n| puts n }

STDOUT/STDERR/STDINの代わりに$stdout/$stderr/$stdinを用いる

STDOUT/STDERR/STDINは定数であり、 Rubyでの定数は、実際は再代入できます(つまりリダイレクトに使えます)が、 もし実行するとインタープリタからの警告が出ます。

ロジックを使って複雑な比較を行うよりも、 可能な限りRangeやComparable#between?を用いる

# 悪い例
do_something if x >= 1000 && x <= 2000

# 良い例
do_something if (1000..2000).include?(x)

# 良い例
do_something if x.between?(1000, 2000)

collectよりmap、detectよりfind、find_allよりselect injectよりreduce、lengthよりsizeを使う

これは絶対のルールではないです。 別名のほうが可読性に優れているなら、 そちらを使っていただいて構いません。 韻を踏んでいるほうのメソッド名はSmalltalkから引き継いできたもので、 他のプログラミング言語でそこまで一般的ではないです。 find_allよりもselectが推奨されるのは、 rejectとの相性がよいことと、 メソッド名から挙動を推察することも容易だからです。

シンボル、メソッド、変数にはsnake_caseを用いましょう。

シンボルはcamelCaseでも怒られない気がしたんですが、こちらもsnake_caseが良いみたいです。

危険 な可能性のあるメソッド (引数やselfを変更するようなメソッドや、 exit!(exitと違ってファイナライザが走らない)のようなもの) は、その安全なバージョンがある場合には、 危険 であることを明示する意味で感嘆符で終わる

# 悪い例 - 対応する「安全」なメソッドが存在しません
class Person
  def update!
  end
end

# 良い例
class Person
  def update
  end
end

# 良い例
class Person
  def update!
  end

  def update
  end
end

対応する安全なメソッドが存在しない場合は ! 付けない方が正しいみたいですね。副作用が大きいメソッドは ! つけるようにしてたんですが、Style Guilde的には微妙みたいです。

コメント

  • パフォーマンスに問題を及ぼすかもしれない遅い、または非効率なコードの注釈にはOPTIMIZEを使いましょう。
  • 疑問の残るコードの書き方でコードの臭いを感じた箇所の注釈にはHACKを使いましょう。
  • 意図したとおりに動くか確認する必要がある箇所の注釈にはREVIEWを使いましょう。

ハッシュから連続して複数の値が必要になる時は、Hash#values_atを用いる

# 悪い例
email = data['email']
username = data['nickname']

# 良い例
email, username = data.values_at('email', 'nickname')

利用するケースにより特化した速い代替手段がある場合、String#gsubは使わないようにする

url = 'http://example.com'
str = 'lisp-case-rules'

# 悪い例
url.gsub('http://', 'https://')
str.gsub('-', '_')

# 良い例
url.sub('http://', 'https://')
str.tr('-', '_')

文字列の添字に直接正規表現を渡すことで、文字列の構築をシンプルにできる

match = string[/regexp/]             # マッチした内容が得られる
first_group = string[/text(grp)/, 1] # キャプチャグループの内容が得られる
string[/text (grp)/, 1] = 'replace'  # string => 'text replace'

sub/gsubでの複雑な置換は、ブロックやハッシュを用いることで実現できる

words = 'foo bar'
words.sub(/f/, 'f' => 'F') # => 'Foo bar'
words.gsub(/\w+/) { |word| word.capitalize } # => 'Foo Bar'

Effective Ruby 第六章まとめ テスティング

Effective Ruby 第五章 メタプログラミング まとめ その2 - Memento memo. の続き。

Effective Rubyのテスティング章をまとめていきます。

MiniTestはRuby標準のテスティングライブラリで、 主要なコンポーネントは以下の3つです。

require('minitest/autorun') でライブラリ全体をロードすると、上記のコンポーネントも含まれます。

テストのファイル名は tests/xxx_test.rb といった名前で格納するとrailsの作法に乗れて良いみたいです。

ユニットテストとスペックテスト(ビヘイビアスペック)はどっちでもいいらしいですが、 RubyだとRSpecデファクト感あるのでスペックテストの方が馴染みがありそうです。

Effective Ruby

Effective Ruby

MiniTestユニットテストに慣れる

  • テストクラスを定義し、スーパークラスMiniTest::Unit::TestCase にする
  • 個々のテストケースはインスタンスメソッドとして記述し、"test_"プレフィクスをつける
  • ユニットテスト時はアサーションを使う
  • テストメソッドは短くする
  • アサーションメソッドは assert メソッドだけでなく、 assert_equal 等の適切なものを使う
  • assert_xxx に対応する refute_xxx で逆の動作を扱える
  • テストをまとめて実行するRake taskを使う(または作る)
class HogeTest < MiniTest::Unit::TestCase
  def test_hoge
    hoge = Hoge.new
    assert_equal(0, hoge.xxx)
  end
end

MiniTestスペックテストに慣れる

  • 基本的にはユニットテストをラップしてるだけ
  • describeメソッド呼び出しでクラスが自動的に定義されるため、自前でクラス定義を書く必要がない
  • アサート系メソッドの代わりにオブジェクトに注入されたエクスペクテーションメソッド( must_equal, wont_equal )を使う。
describe(Hoge) do
  describe('xxx') do
    before do
      @hoge = Hoge.new
    end
    it('returns initial value') do
      @hoge.xxx.must_equal(0)
    end
  end
end

モックオブジェクトで決定論をシミュレートする

  • 非決定的な処理(HTTPリクエスト等)からテストを切り離したいときはモックを使う
  • モックで交換するメソッドは外部ライブラリが提供している部分にすべき
  • テストメソッドを終える前に必ず verify を呼んで、モックメソッドが実行されていることを確認する。
  • MiniTest::Mockでモックを作成できるが、Mocha等の別ライブラリ使った方が高機能なのでおすすめ

インタフェースをテストする、という原則があるのですが、実装詳細に立ち入ってモックを使わざるを得ないケースもあると思います。その場合 verify でモックメソッドが確実に実行されていることをテストする必要があります。 すると、実装詳細がしれっと変わった場合でもテストで検知できるようになります。

効果的なテストを追求する

  • ハッピーパスと例外パスの両方を試すためにファズテスト、プロパティテストツールを使う
  • コードカバレッジを見て安心しない
  • 機能とテストは同時に書く(テストを後回しにしない)
  • テストは自動化する

用語等

ハッピーパステスト

テストしているコードのすべての前提条件を丁寧に準備して有効な入力しか与えないテスト。バグ発見の効果が薄い。

例外パステスト

さまざまな入力を送り込んでコードの全ての分岐先を確実に実行するテスト。一般に複雑になりすぎるが、ファズテストとプロパティテストで対処できる。

ファズテスト

プログラムや特定のメソッドにランダムデータを大量に送り込むことで、 クラッシュさせたり予想外の例外を発生させることができるかをチェックするテスト。

FuzzBert gem等で実行可能。基本的にほぼ無限にテストを行うため時間がかかる。

プロパティテスト

ファズテスト同様にランダムなデータを大量に送り込むが、コードが満たすべきプロパティ(性質)を満足するかをチェックするテスト。

MrProper gem等で実行可能。

参考: ソフトウェアの品質を学びまくる:Property-based Testing、そしてExample-based testing、とは


Rubyとは別でテスティングの理論的なところをあまり深く理解できていないので、もうちょっと勉強していきたいです。 あとは実践的なところでRSpecを使いこなせるようになりたいです。(適当にしか使えてないので。。。)

The RSpec Book (Professional Ruby Series)

The RSpec Book (Professional Ruby Series)

Elixir & Phoenix のLT資料

先日、Elixir & Phoenix布教をすべく、社内でLTをしてきました。社内LT会自体は毎週やっているので、個人的に最低月1回は発表するよう心がけています。

内容は超薄いのですが、とりあえず公開することが大事だと思うので資料upしました。SpeakerDeckデビュー。

プログラミングElixir

プログラミングElixir

GitHubのリポジトリを直接指定してgemをinstallする

自作gemとかOSSのgemのリポジトリとかブランチとかを指定して使いたい場合の解決策です。

リポジトリ.gemspec ファイル等は設定済み前提で。

Gemfile使う方法とGemfile使わない方法の2通りあります。

Gemfileを使う場合

普通に指定できます。以下のようにGemfileに記述

gem 'hogehoge', git: 'git@github.com:foo/bar.git', branch: 'develop'

Gemfileを使わない場合

specific_install gemを使います。

specific_installのInstall

$ gem install specific_install

リポジトリからgem install

$ gem specific_install git@github.com:foo/bar.git develop

内部的にはcloneしてbuild & installしてローカルリポジトリ破棄してるっぽいです。

Effective Ruby 第五章 メタプログラミング まとめ その2

Effective Ruby 第五章 メタプログラミング まとめ その1 - Memento memo. の続きです。

モンキーパッチの代わりとなるものを検討する

モンキーパッチとは

  • モンキーパッチとは、実行時にコアクラスを拡張したり挙動を書き換える(パッチを当てる)こと。Active Supportが有名。
  • 複数パッチが衝突すると大事故になるのでできるだけモンキーパッチは避けたい

方法1 module関数を使う

愚直にmoduleを定義&オブジェクトをextendする方法です。

module OnlySpace
  ONLY_SPACE_UNICODE_RE = %r/\A[[:space]]*\z/
  def self.only_space? (str)
    if str.ascii_only?
      !str.bytes.any? do |b|
        b != 32 && !b.between?(9, 13)
      end
    else
      ONLY_SPACE_UNICODE_RE === str
    end
  end

  def only_space?
    OnlySpace.only_space?(self)
  end
end

str1 = "   \r\n"
puts OnlySpace.only_space?(str1)
# OOPっぽくなくなる

str2 = "hello"
str2.extend(OnlySpace)
puts str2.only_space?
# extendしないと使えない

方法2 新しい別のクラスを作成する

Stringクラスの代わりにStringExtraクラスを作成します。 継承ではなく委譲を使ってStringライクなStringExtraを実装します。(実装省略)

方法3 Refinements機能を使う

Ruby 2.1から入ったRefinements機能を使います。 Refinementsでは、パッチの適用範囲がレキシカルスコープの中に限定されます。以下の例で String#loudFoo の外で呼ぶことはできません。

使い方はこんな感じになります。

module Loud
  refine String do
    def loud
      "#{self}!!!"
    end
  end
end

class Foo
  using(Loud)
  def initialize(str)
    puts str.loud
  end
end

Foo.new("wei")
#=> wei!!!

参考

エイリアスチェイニングで書き換えたメソッドを呼び出す

  • 既存のメソッドに新しい名前を与え、元のメソッド名でメソッドを再定義して最終的に元のメソッドを呼び出す。
  • エイリアス作成時にメソッド名がユニークになるよう注意
  • エイリアスチェイニングを取り消すメソッドも作成する

alias_methodについて

alias_method(new_name, original_name) でmethodにエイリアスを貼ることができます。

参考: ref.xaio.jp

sample

module LogMethod
  def log_method(method)
    orig = "#{method}_without_logging".to_sym
    
    if instance_methods.include?(orig)
      raise(NameError, "#{orig} isn't a unique name")
    end

    alias_method(orig, method)

    define_method(method) do |*args, &block|
      $stdout.puts("calling method '#{method}'")
      result = send(orig, *args, &block)
      $stdout.puts("'#{method}' returned #{result.inspect}")
      result
    end
  end

  #エイリアスチェイニングを取り消すメソッドも作成する
  def unlog_method(method)
    orig = "#{method}_without_logging".to_sym
    
    if !instance_methods.include?(orig)
      raise(NameError, "was #{orig} already removed?")
    end

    remove_method(method)
    alias_method(method, orig)
    remove_method(orig)
  end
end

Array.extend(LogMethod)
#=> Array
Array.log_method(:first)
#=> :first
[1,2,3].first
# calling method 'first'
'# first' returned 1
#=> 1
Array.unlog_method(:first)
#=> Array
irb(main):008:0> [1,2,3].first
#=> 1

Procの引数の個数の違いに対応できるようにする

  • Procオブジェクト生成には"強いProc"と"弱いProc"がある。 lambda? メソッドで識別可能。
  • 弱い(Weak)Proc: 引数の扱いが緩く、間違った個数の引数を与えてもエラーにならない。e.g. block
  • 強い(Strong)Proc: 通常メソッド呼び出しと同じで、引数の個数が違うとArgumentError例外が発生する。e.g. lambda
  • Proc#arityメソッドを使うと、Procオブジェクトが期待する引数の数がわかる。
  • Proc#arityメソッドで引数の個数の違いをうまく吸収させる。

モジュールのprependを使うときは慎重に考える

  • 継承階層は ancestors メソッドで確認可能
  • includeは継承階層においてレシーバの後にモジュールを挿入する。
  • prependは継承階層においてレシーバのにモジュールを挿入する。
  • prependよりもalias_methodを使った方が柔軟になる

メタプログラミング面白いですね。Refinementsで局所的にクラス拡張するのは結構気に入りました。

まだ メタプログラミングRuby 第2版 も読めていないのですが、健全なメタプロパワーを高めていきたいです。

Effective Ruby

Effective Ruby

denite.nvimとag使い始めた

かの有名な暗黒美夢王ことShougoさん開発中のdenite.nvimを使い始めました。いつもありがとうございます。超速くていい感じです。

install方法とか詳細な説明は以下のエントリが詳しいので割愛。

qiita.com

とりあえずdeniteの設定自体は :h denite に載ってたのをとりあえず8割くらいコピペしました。 fast grepツールとしてagを使ってます。

chaika.hatenablog.com

設定はこんな感じになりました。

call denite#custom#var('file_rec', 'command',
      \ ['ag', '--follow', '--nocolor', '--nogroup', '-g', ''])

call denite#custom#var('grep', 'command', ['ag'])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#var('grep', 'final_opts', [])
call denite#custom#var('grep', 'separator', [])
call denite#custom#var('grep', 'default_opts',
      \ ['--nocolor', '--nogroup'])

nnoremap <silent> <C-k><C-f> :<C-u>Denite file_rec<CR>
nnoremap <silent> <C-k><C-g> :<C-u>Denite grep<CR>
nnoremap <silent> <C-k><C-l> :<C-u>Denite line<CR>
nnoremap <silent> <C-k><C-u> :<C-u>Denite file_mru<CR>
nnoremap <silent> <C-k><C-y> :<C-u>Denite neoyank<CR>

とりあえず :Denite grep :Denite file_rec あたりが快適なのでいろいろ捗ります。

pluginばっかりに頼ってると素のvim, grep, findあたりの使い方が怪しくなってくる。。。

Ruby2.3系での"Missing frozen string literal comment"への対処

Missing frozen string literal comment

Ruby2.3系でRubocopを実行したら Missing frozen string literal comment と怒られました。

ファイルの先頭に以下のmagic commentが必要とのこと。

# frozen_string_literal: true

これによってRubyのStringリテラルが勝手にfreezeされてimmutableになるらしいです。 Ruby3系では基本的にimmutableになる予定なので、その互換のためのようです。便利な上に移行も楽そうですね。

参考:

qiita.com

対処

Rubocopで Missing frozen string literal comment が出てるファイルの先頭行にmagic commentを追記すればよいので、適当なワンライナーで一括対処できます。

rubocop | grep 'Missing frozen string' | cut -d: -f1 | xargs gsed -i -e '1i\# frozen_string_literal: true\n'

BSDsedのオプション苦手なのでgsed使ってます。

Effective Ruby

Effective Ruby

Git Large File Storage (LFS)を使う

会社のSlackでGit Large File Storage (LFS)なるものを知ったので適当に使ってみました。

git-lfs.github.com

Git LFSとは?

Gitの拡張です。 画像とか音声みたいなLarge File(BLOB)をGitで管理しようとめちゃくちゃ重くなってきます。 直接BLOBをいじらずにポインタをテキストファイルとしてgitで管理し、BLOBの本体は別サーバに保存してるみたいです。 本体のリポジトリがすっきりする&fetch, cloneが高速になるのがメリットです。

https://git-lfs.github.com/images/graphic.gif

Install

$ brew install git-lfs
$ git lfs install

使い方

https://media.githubusercontent.com/media/shotat/lfs-sandbox/master/space_cat.jpg

とりあえず適当な画像(space_cat.jpg)をリポジトリに配置して試しました。 DocのGetting Startedを参考に。

# track対象のパス確認
$ git lfs track
Listing tracked paths

# track対象に"*.jpg"追加
$ git lfs track "*.jpg"
Tracking *.jpg

# track対象のパス確認
$ git lfs track
Listing tracked paths
    *.jpg (.gitattributes)

# .gitattributesに設定が追記されている
$ cat .gitattributes
*.jpg filter=lfs diff=lfs merge=lfs -text

# あとは普通
$ git add .
$ git ci -m 'aaa'
$ git push

これだけなので超簡単です。

試したやつ

space_catを回転させてPR出してます。プレビューが楽しい。

github.com

仕様

このあたりに書いてあったのであとで読みたいやつです。

github.com

エンジニアのためのGitの教科書 実践で使える! バージョン管理とチーム開発手法 (WEB Engineer’s Books)

エンジニアのためのGitの教科書 実践で使える! バージョン管理とチーム開発手法 (WEB Engineer’s Books)

Effective Ruby 第五章 メタプログラミング まとめ その1

Effective Ruby 第四章 例外 まとめ - Memento memo. の続きです。

第五章 メタプログラミングについての前半部です。

Effective Ruby

Effective Ruby

モジュール、クラスフックを使いこなす

  • Rubyのイベント通知 -> フック関数の実行は、適切な名前のメソッドを書くだけで実行可能
  • 定義できるフックは10種類
hookメソッド タイミング
included moduleがincludeされる
extended moduleがextendされる
prepended moduleがprependされる
inherited classが継承される
method_added method追加
method_removed method削除
method_undefined method定義解除
singleton_method_added 特異method追加
singleton_method_removed 特異method削除
singleton_method_undefined 特異method定義解除
  • 全てのフックメソッドは特異メソッドとして定義する
  • method追加、削除、定義解除系のフックは引数としてメソッド名を受け取る。クラス名を知りたい場合はselfを使う。
  • 全てのフックメソッドは自動的にprivateになる
  • フックメソッドに関連する以下のメソッドはオーバーライドしてはならない。フックを使うこと。
    • extend_object
    • append_features
    • prepend_features
module PreventInheritance
  class InheritanceError < StandardError; end

  def inherited(child_klass)
    raise(InheritanceError, "#{child_klass} cannot inherit from #{self}")
  end
end

::Array.extend(PreventInheritance)

class ChildArray < ::Array; end
#=> ChildArray cannot inherit from Array (PreventInheritance::InheritanceError)

inherited フックを使うとこんな感じの使い方になります。

クラスフックからはsuperを呼び出す

  • hookは他のモジュールで定義されたhookの制御を完全に奪ってしまう場合がある。
  • クラスフックメソッド内では必ず super を呼ぶのが行儀が良い。

method_missingではなくdefine_methodを使う

  • method_missingを使ってはいけない理由
    • パフォーマンス面でコストがかかる
    • エラーメッセージが分かりにくくなる
    • respond_to? 等のイントロスペクションメソッドが使えなくなる。
  • method_missingで本来やりたいことの大半はproxy, decorator patternの実装だが、これらはdefine_methodで代替可能
  • proxyの実装
class HashProxy
  Hash.public_instance_methods(false).each do |name|
    define_method(name) do |*args, &block|
      @hash.send(name, *args, &block)
    end
  end

  def initialize
    @hash = {}
  end
end
  • decoratorの実装
require('Logger')

class AuditDecorator
  def initialize(object)
    @object = object
    @logger = Logger.new($stdout)

    @object.public_methods.each do |name|
      define_singleton_method(name) do |*args, &block|
        @logger.info("calling '#{name}' on #{@object.inspect}")
        @object.send(name, *args, &block)
      end
    end
  end
end

上記例の用に、オブジェクトの場合は define_singleton_method で特異メソッドを定義できます。

  • どうしても method_missing を避けられない場合は respond_to_missing? を併用する。
  • respond_to_missing?repond_to? がメソッドを見つけられなかった場合の最後のチャンスをプログラマに委ねる。

evalの多様な変種間の違いを把握する

  • eval, instance_eval, class_eval で直接文字列を評価するのは避ける。
  • 代わりに class_exec, instance_exec とブロックを使うと変数のvalidation等が行えるようになる。

参考

secret-garden.hatenablog.com


細かいところはメタプログラミングRubyを読む必要がありそうですね。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

続きます。

翔泳社の技術書(電子版)が40%オフセールだったので色々買った

技術書は分厚くて持ち歩くのがしんどいので最近は全部電子版を買って読んでます。 翔泳社の技術書はKindle対応してるので最高です。

翔泳社の技術書のセールが始まったらしいので色々買いました。 個人的にメインはDDD本です。

www.amazon.co.jp

買った / すでに持ってた本

エリック・エヴァンスドメイン駆動設計

DDD本です。結構難しいらしいので頑張って読みたいです。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

実践ドメイン駆動設計

DDDと合わせて読みたいやつです。途中まで読んで力尽きたのでリトライしたいです。

実践ドメイン駆動設計

実践ドメイン駆動設計

Spring徹底入門

DIとかAOPの解説が分かりやすい。大判固定レイアウトなので電子版だと読むのがつらかったです。

Effective Ruby

読むだけでRubyのコーディングの質が上がります。 最近読み終わりました。めっちゃ良かったです。

Effective Ruby

Effective Ruby

実用Common Lisp

200pくらい読んで挫折しましたが良書です。 全部で1000pくらいあります。

実用Common Lisp

実用Common Lisp

スターティングGo言語

Golangの入門書です。 "みんなのGo言語"より初心者向けのはずです。

スターティングGo言語

スターティングGo言語

エッセンシャル スクラム

スクラム本はいろいろあるんですが、会社の先輩におすすめされたのでこれ買いました。 アジャイルサムライとかもおすすめらしいです。

エッセンシャル スクラム

エッセンシャル スクラム

今回買ってないけど読んでみたい本

ストラウストラップのプログラミング入門

鈍器&名著らしいですが、多分読めないので今回はスルーしました。

ストラウストラップのプログラミング入門

ストラウストラップのプログラミング入門

プログラマのためのSQL

結構人気が高いみたいです。 今回はキャパオーバーしそうなのでパスしましたがいつか読みたいやつです。

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

プログラマのためのDocker教科書

Dockerは日本語良書が少ない上、結構頻繁にアップデートされてる印象です。 公式ガイド読んだりKubernetesの勉強した方がいいっぽいですが一応