0
0

More than 5 years have passed since last update.

演算子はfreezeしてテストしよう

Last updated at Posted at 2016-05-01

Rubyで演算子の再定義を駆使するようなコードを書く場合、テスト時にはオブジェクトを.freezeさせたほうがいいな、と思いました。

演算子の再定義について

「C++、Ruby、Python、Swiftにあって、Java、JavaScript、PHP1にない」と、あるなしクイズみたいに有無が分かれる機能が「演算子の再定義」です。

Rubyの場合、「(複合代入を含めた)代入」と「? :&.||のようにショートサーキットになる演算子」「スコープの::」を除いて、ほとんどの演算子を再定義できます。

演算子をグループ分けしてみる

定義可能な演算子をグループ分けしてみます。

# 算術演算系(+@と-@は単項のプラス・マイナスです)
+ - * / % ** +@ -@

# 論理演算系
| ^ & ~

# 比較系
< <= == >= > <=> === =~

# シフト系
<< >>

# 添字系
[] []=

# 特殊用途
! !~ != ` 

よほど狙って特殊なものを作る2のでないかぎり、何かしらのクラスを作る上で、「算術演算系」「論理演算系」「比較系」の演算で両辺を変えてしまうのはやめたほうがいいです。「a + b」でabが変化してしまうとなると、おそらく大混乱となるでしょうし、比較演算で値が変わってしまうのは(通常の構文中では)もはや悪夢です。

一方で、[]=は破壊的操作を前提とした演算子ですし、Array#<<に見られるように、シフト演算子も「破壊的な用途に使う」文化のある演算子です。

なお、「特殊用途」に挙げた否定形の演算子や、バッククオート演算子3はかなり癖がありますので、基本的には上書きしないほうがいいでしょう。

演算子のSpecを書く際に

演算子を実装するときに、「うっかり元の内容を破壊する形で実装してしまう」というトラブルもやらかしがちかもしれません。ということで、Specを書く際に、本来破壊的操作するはずでない演算子については、テストするレシーバや引数に渡すオブジェクトを事前に.freezeするのが楽です。

.freezeしたオブジェクトに対して(故意・過失を問わず)破壊的操作をかけようとすればRuntimeErrorとなって、そして(.raise_errorで例外発生自体をチェックする場合は別として)例外が飛べばRSpecもFAILになってくれるので、わざわざ「元のオブジェクトが変化していないか」のチェックを書く必要もありません。


  1. 「PHP自体を拡張する」という力技はおいておきます(言語内だけからはできません)。 

  2. それこそRSpecのように、「Ruby文法を使ってDSLを構築する」とかならまた別かも知れません。 

  3. バッククオートでくくった文字列の動作も演算子として入れ替えられる…のはいいのですが、Qiitaのマークアップと被ってしまってインラインで書けないです。 

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0