著作一覧 |
時期
元のプログラムでJSONなどの外部データから得られたHashをそのまま利用していたら、そのデータが複数のモジュール間を行き来するようになった。
モティベーション
Hashキーのリテラルが複数モジュールで記述されるためスペルミスがあってもわかりにくい。外部データで値としてnilが含まれることを前提している場合、キー名の書き間違いによるnilなのか、値がnilなのかの区別ができないためである。キー名の書き間違いはプログラムエラーとして検出したい。
解決方法:HashからStructへ変える
Structは、属性名をキーとした[]記法もできるため、仮に修正漏れがある、あるいはモジュール修正の時期によってStructへの記述変更したモジュールとHash記法のままのモジュールの混在が可能。
例
data = JSON.parse(responseText) if data['result'] == 'OK' other_module.received(data) ... end class OtherModule def received(data) if data['foo'] == 'bar' ...⇒
ResponseData = Struct.new(:result, :foo...) ... data = JSON.parse(responseText).inject(ResponseData.new) {|s, (k, v)| s[k] = v; s} if data.result == 'OK' othermodule.received(data) ... ... def received(data) if data['foo'] == 'bar' # 後刻、他の修正と共に、data.fooに修正する ...
ボヘミアンスタイルでJSONを利用している場合
# 未知のプロパティは無視する data = JSON.parse(responseText).inject(ResponseData.new) do |s, (k, v)| if s.respond_to?(k.to_sym) s[k] = v end s end
代替
リテラルの代わりに定数を利用する
module ResponseKeys RESULT = 'result'.freeze ... end ... if data[ResponseKeys::RESULT] == 'OK'
定数を使う場合のStructに対する
pros: 特にない。
cons: 冗長。
2019/3/6 追記: とみたさんにStructのコンストラクタ呼出し時にkeyword_initを使うと勝手に属性が設定されるからより簡潔と教わった。ありがとうございます。
Struct の keyword_init を使えばもっと完結に書けそう。
— とみたまさひろ🍣🍺 (@tmtms) 2019年3月6日
ResponseData = Struct. new(:result, :foo, keyword_init: true)
data = ResponseData(JSON.parse(responseText))
ジェズイットを見習え |