オタクのファンレターはなぜ読むのが大変なのか。RubocopのABC Sizeで解説してみた


💡 この記事の結論:オタクのファンレターは長くて難解なので読んでもらえない
*ABC SizeのTipsをおまけとして書いています。たいていの人はおまけだけ読めば問題ないかと思います。

導入

ここに読むのに根気と努力と忍耐を要するファンレターがあります。
ちなみに本筋とは関係ないので読み飛ばして大丈夫です。

ファンレター.otk
親愛なる青山吉能さんへ。

私の人生はあなたにお会いするまでは深い闇に包まれていました。
しかし、5年前の冬、UDXであなた達を一目見かけた時から闇に一筋の光が差し込み、毎日が楽しい日々に変わりました。
寒空の下で舞い踊るあなた達はまだ芽吹いたばかりのわかばでしたが、何か心を惹かれるものを感じました。
その時の思い出の写真を添付資料(*出逢いの記録写真)として添付します。

そして月日が経ち、あなた達は新芽のようにすくすく成長して行き、あっという間に立派な青葉に成長しました。
WUGももう成長しきっちゃったし、次の世代でも探そうかなと薄々考えていた矢先に始まる恋愛暴君。
初めて見て、声を聴いたときは添付資料(*恋愛暴君での衝撃)のような衝撃を受けました。
そして恋?で愛?で暴君です!のあのPV、誰だこのかわいい女の子は。
これが青山吉能だとしたら、今までみて来た人は何者だったんだ。
そして暴れん坊SHOWで本当に楽しそうに朗読するあなたの姿を見て、私は完全にあなたの虜になりました。
その時の詳しい感想は添付資料(*最前連番してくれてありがとう)にてお送りします。

もともとは、添付資料(*芋山吉能)のように思っていたあなたの印象が、今ではもうすっかり変わり、
・普段 楽しそうに遊んで楽しく生きてる人
・イベントの時 お茶目に場を盛り上げてくれる人
・歌っているとき 美しい歌声で会場をわかせてくれる人
・声優の時 誰よりも楽しそうに演じ、キャラになりきる人
という印象になりました。

もし5年前UDXに行っていなかったら、あなたに出会うことはなかったかもしれない。
またもし暴れん坊SHOWに行っていなかったら、こんなに好きになることはなかったかもしれない。
解散しても、ずっと応援しています。

寿司食人より。

アイドルは日々の業務で疲れているので当然読みません。

今日は、なぜオタクはこんなに読みにくい文章を書いてしまうのか、ABC Sizeで解説していこうと思います。

ABC Sizeとは

コードの行数などではなく、ソフトウェアのサイズを測る尺度です。
https://www.softwarerenovation.com/ABCMetric.pdf
簡単に言うと、そのコードがどれだけデカくて複雑で読みにくいかを表す尺度です。

ABC SizeはAssignment(代入式)、Branch(関数呼び出し)、Condition(条件分岐)の個数をそれぞれa,b,cとして

ABC Size = \sqrt{a^2+b^2+c^2} \\

として計算されます。
https://github.com/rubocop-hq/rubocop/blob/v0.28.0/lib/rubocop/cop/metrics/abc_size.rb#L22

Rubocopではこのサイズが規定値(デフォルトでは15)を超えるとエラーになります。
actress/wug/member/yoshino.rb:5:15: C: Assignment Branch Condition size for _read_letter is too high. [81/15]
読む気起きないから直せってことです。ファンレターと一緒ですね。

準備

さて解説していこうと思うのですが、日本語だとさすがにわかりにくいので、ある程度オタクの皆さんにも読みやすい文章に翻訳します。
当然実行できません。

NotExecutable.rb
def letter
  you = {
    name: 'yoshino',
    age: 22
  }
  me = new Otaku('Sushi')

  life = nil unless you
  life += light
  everything = colorful
  you = young_leaves
  me.attracted(you)
  memory = DOCUMENT[0]

  you.grow_to(green_leaves)
  me.watch_tv('恋愛暴君')
  me.shocked_by(DOCUMENT[1])
  you = unknown if you[:name] == yoshino
  me.thought(DOCUMENT[2])

  impression = DOCUMENT[3]
  impression =
    case situation
    when event then 'mood maker'
    when sing then 'beautiful'
    when actor then 'very good'
    else 'happy'
    end

  if UDX.blank?
    me.attracted(nil)
  elsif SHOW.blank?
    me.attracted(you, 0.5)
  end

  me.cheer(you)
end

幾分かましになりました。

どこが読みにくいのか

ここからが本題です。先のファンレターはどこがなんで読みにくいのかを解説して行きます

代入の多い文章は読みにくい

「私はオタクだ」などの代入式が多いと、結局何が何やらわからなくなります。
Assignment(代入式)では = の数をそれぞれ1とカウントします

例としては
・ローカル変数への代入
・インスタンス変数への代入
・グローバル変数への代入
・クラス変数への代入
・定数への代入
・自己代入
・多重代入
です。

今回のファンレターではAssignmentは 10 になります。

添付資料が多すぎて読んでられない

あっちこっち色んなものを読んで戻って読んで。
結局何の話だったのかよくわかんなくなります。
Branchでは関数呼び出しの数をそれぞれ1とカウントします。
チェインしたらチェインしただけ数えていきます。

例としては
・クラス内外の関数呼び出し
・インスタンスの生成
・HashやArrayへのアクセス
などです

今回のファンレターではBranchは 16 になります。

こうだったらこう、みたいなのが多すぎる

結局どうなんだ。
Conditionでは条件分岐の数をそれぞれ1とカウントします。
elsifやwhenも一個一個数えていきます。

例としては
・if
・elsif
・unless
・when
・三項演算子
などです

今回のファンレターではConditionは 8 になります。

まとめ

今回のファンレターのABC Sizeを計算すると

\sqrt{10^2+16^2+8^2}=20.49 \\ 

となり見事に15を超えています。当然読まれることはありません。
そもそもうだうだ中身のない文章を書いても想いは伝わりません。
言いたいことだけを簡潔に編集したのが以下の文章になります。

ファンレター.otk
よしのへ

好き!優勝!

寿司食人より

これならどんなにPUBG Mobileで忙しくても読んでもらえるのではないでしょうか。
おわり。

おまけ ABC Sizeの減らし方

概要は分かったけど結局どうやって減らすのか!?
そもそもサイズの計算が合わねぇ!
そんな人のためのおまけです。

それぞれで代替してみる

ABC Size = \sqrt{a^2+b^2+c^2} \\

上記計算式、なんか見たことあるなって思う方も多いと思います。
そう、直方体のそれぞれの辺の長さから対角線の長さをだす計算式です。

対角線を固定して各辺の長さの合計値を最大にするには、a=b=c、つまり立方体にする必要があるということが一般的に知られています。

ABC Sizeの話に戻すと、代入、関数呼び出し、条件分岐の個数をできるだけ同じ数に近づけると、個数の合計値に対するABC Sizeの数は減っていきます。

デフォルトだとABC Sizeを15以下にする必要があるので、それぞれ8以下にすると問題なく通るということになります。

8^2 + 8^2 + 8^2 < 15^2 < 9^2 + 9^2 + 9^2

多重代入を使う

本文でも説明しましたが、Assignmentは = の数を数えます。
多重代入は = を一つしか使わないため、Assignmentを1しか増加させません。
可読性はさておき、Assignmentを減らすことが目的であれば、
「あなたと私はアイドルとオタクです」のように多重代入を用いることでサイズを抑えることが可能です。

you, me = idol, otaku # Assignmentは1しか増えない

HashやArrayへのアクセスは関数呼び出し

Branchが大きすぎる!そう感じたあなた!

memory = DOCUMENT[0]
you[:name] == yoshino

などはBranch(関数呼び出し)としてカウントされます。
見た目ではわかりにくいですが、内部的には [] という関数です。

返り値の多重代入

me, friend = _become('WUGner')

上記、Branchはいくつになるでしょうか。
関数1個だから1?
それとも2回呼んでそうだから2?

正解は3です。
関数にてreturnで複数の戻り値を返す場合、実態としてはArrayとして返されます。
(Assignmentの数は置いておいて)実際は以下のような処理が行われています

otaku = _become('WUGner')
me, friend = otaku[0], otaku[1]

上記でも書きましたが、ArrayへのアクセスはBranchにカウントされます。
よってBranchは3とカウントされます。

じゃあAssignmentはどうなるのでしょうか。
本文にも書きましたが、Assignmentは = の数を数えるものなので、多重代入しようが何しようが1個しかカウントされません。
なので最初の例だとAssignmentが1とカウントされます。

偉大なる参考文献

いらすとや
https://www.irasutoya.com/

Rubocop
https://github.com/rubocop-hq/rubocop

AbcMetric
http://wiki.c2.com/?AbcMetric

Wake Up, Girls!
https://wug-portal.jp/