やらなイカ?

たぶん、iOS/Androidアプリの開発・テスト関係。

メソッド分割の意義とTips

この記事は DeNA Advent Calendar 2021 16日目の記事です。

ユニットテストを書こうとしたとき、テスト対象のクラスやメソッドが大きく複雑なため断念した経験は誰しもあるのではないでしょうか。

ひとつのメソッドや関数に様々な処理・責務を持たせてしまうとテスタビリティ*1だけでなく、保守性や可読性も損なわれてしまいます。 これは「ロボット掃除機が掃除するためには部屋が片付いていなければならない」こと*2と似ています。

本記事では、メソッドを適切な単位に分割することの意義と、そのためのTipsを紹介します。

メソッド分割のPros and Cons

はじめに、メソッド分割の代表的なPros/ Consを整理してみます。

Pros

  • 再利用性の向上
    • 同一もしくは類似するロジックや計算式が複数箇所で必要になるケースは珍しくありません。メソッドを細かいロジック単位に分割することで、再利用が進むことはイメージしやすいはずです
    • 逆に言うと、再利用が少なく複数のメソッドに同じロジックが重複して存在している状態では、そのロジックに変更が入るときに修正箇所が多くなり修正漏れの恐れも生じます
  • テスタビリティの向上
    • 責務の大きなメソッドは、必然的に入力(引数のほかインスタンスフィールドの参照も含みます)が多くなり、その組み合わせすべてをテストすることが困難になります
  • 可読性の向上
    • 大きなメソッドより小さなメソッドのほうが読みやすい
    • メソッドの名前と責務を明確に定義しやすい

Cons

  • メソッド呼び出し回数が増え、オーバーヘッドがかかる
    • メソッド呼び出しには、処理ステップの増加とスタック操作を伴います。呼び出し回数が増えれば無視できない性能劣化に繋がります

メソッド呼び出しの増加に対しては、コンパイラによる「インライン化(inlining)」と呼ばれる最適化によって緩和できます。 インライン化は必ず行われるとは限らず、またバイナリサイズが大きくなるという副作用*3もありますが、本記事では考えないことにします。

Unityにおけるインライン化については、次の記事で検証していますので参考にしてください。

www.nowsprinting.com

メソッド分割Tips

続いて、メソッドや関数の分割(抽出)をスムーズに行なうためのTipsを紹介します。

Divide and Conquer, Name and Conquer

クラスやメソッドを分割することで、その関心や責務をシンプルに理解しやすく、扱いやすくする手法を、征服・統治になぞらえて「分割統治(Divide and Conquer)法」と呼びます。 チームのコーディング規約などで、メソッドの長さや複雑度の指標(後述)を基準に、人間が理解できる粒度に分割する、という考え方です。

一方で、必要なものにまず名前を付けることで扱いやすくする手法が「Name and Conquer」です。うまく対比した日本語訳が無いのですが、命名(定義)による統治(征服)といった意味です。

たとえば Fizz Buzz の一部を次のように切り出してみます。

private static bool IsFizz(int i)
{
    return i % 3 == 0;
}

これはやや過剰な例ですが、マジックナンバーである 3 を「Fizzとすべき割り切れる数」として定数に定義することは自然に行われているはずです。 上例のようなシンプルなメソッドは、定数と大きな差はありません。 3 を定数とするよりも、判定ロジックを切り出して名前を付けるという選択です。

複雑度を指標にした分割

メソッドの複雑さを測る指標として、サイクロマティック複雑度(cyclomatic complexity)やコグニティブ複雑度(cognitive complexity)が知られています。

例えばJetBrains IDEsには、エディタタブ上にこれらの指標を表示してくれるプラグインが提供されています。 プラグインを導入することで、コーディング中に複雑なメソッドに気づくことができます。

詳しくは次の記事で紹介していますので参考にしてください。

www.nowsprinting.com

IDEリファクタリング機能を利用する

JetBrains IDEsには、リファクタリングの補助機能があります。 別メソッドに分割したいコードを選択した状態でクイックアクションから "Refactor This..." を選択、続いて "Extract Method" で次のダイアログが表示されます。

f:id:nowsprinting:20211215084228p:plain:w400

メソッド名やアクセス修飾子などを指定して実行すると、メソッドを分割できます。

参考

C#のインライン化についての参考記事。

ufcpp.net

Name and Conquerについて。ヨシュア・トゥリーの話もおすすめです。

objectclub.jp

分割したメソッドに命名するのが面倒に感じることは多々あります。またPull Request (Merge Request) レビューで命名についての指摘を遠慮したこともあるのではないでしょうか。 その点、ペアプロやモブプロで、その場であれこれ名前の候補を出し合えるのは良い体験だと思っています。 モブプロについては次の記事で紹介していますので参考にしてください。

www.nowsprinting.com

PR 1

C99(冬コミ)でUnity Automated QAパッケージの解説本を頒布します。 2日目(金曜日)東ト-38b*4でお待ちしております!

f:id:nowsprinting:20211216041250j:plain:w300

PR 2

この記事は DeNA Advent Calendar 2021 16日目の記事です。

DeNAでは今年、2021年度新卒エンジニア・2022年度新卒内定エンジニアの Advent Calendar もあります! 本 Advent Calendar とは違った種類、違った視点での記事をぜひお楽しみください!

DeNA 2021年度新卒エンジニア・2022年度新卒内定エンジニアによる Advent Calendar 2021 https://qiita.com/advent-calendar/2021/dena-21x22

また DeNA 公式 Twitter アカウント @DeNAxTech では、 Blog記事だけでなく様々な登壇の資料や動画も発信してます。ぜひフォローして下さい!

*1:ISTQB GLOSSARY Ver3.2(日本語版)では「試験性」と称しています

*2:「ルンバビリティ」と呼ばれます

*3:これはそもそもメソッド分割前の状態に戻ることなので本記事では無視していいでしょう

*4:日本Androidの会Unity部さんのお隣です。UNIBOOKお買い求めのついでにお立ち寄りください!