🎨

RMagickのImageListの癖が強い

2022/12/17に公開約1,000字

リファクタリング中に ImageList で管理している画像を一括解放[1]する目的で次のように書いてあるプロダクションコードがあった。

おそらく意図とは異なるコード
list = Magick::ImageList.new
list << Magick::Image.read("logo:").first
list << Magick::Image.read("logo:").first
list.destroy!

ひと目配列に対して destroy! で不吉な匂いがするが、エラーにならないことからおそらく ImageList と Image を混同していたか、ActiveRecord でいうところの destroy_all 相当をやってくれていると当時の自分は勘違いしたのかもしれない。

だがこれは list.scene が指す要素を解放するだけなので1つ目は解放されないのだった。

list = Magick::ImageList.new
list << Magick::Image.read("logo:").first
list << Magick::Image.read("logo:").first
list.scene                      # => 1
list.to_a.collect(&:destroyed?) # => [false, false]
list.destroy!                   # => #<Magick::Image: (destroyed)>
list.to_a.collect(&:destroyed?) # => [false, true]

method_missing を悪用活用してレシーバが反応しないメソッドは scene が指す方に流すロジックになっている。

ちなみに ImageList#collect があるにもかかわらずいったん to_a としているのは collect(&:destroyed?) だと謎のエラーになるから。コードを読むと collect ブロック内では Image のインスタンスを返す前提となっていた。もっと言えば collect は破壊的メソッドだった。他にも参照に見えて破壊的なメソッドが多くある。

脚注
  1. 昔のRMagickはメモリリークが心配でとにかく自分で解放するのが慣習だった ↩︎

Discussion

ログインするとコメントできます