🧩

ViewModelのメソッド名はイベントを表す命名にしたいという話

2022/10/18に公開8

Viewから呼び出すViewModelのメソッド名は、例えば save のようなアクションを表す命名ではなく onSaveButtonClick のようなイベントを表す命名にしたい、という趣旨のお話です。

これには、「ViewはViewModelを操作する」のではなく、「ViewはViewModelへイベントを伝搬する」という責務を明確にしたいという意図があります。

具体例

例えば、プロフィール編集画面を例に考えてみましょう。
この画面ではユーザーのアイコン, 名前などを編集でき、保存ボタンを押下すると編集されたプロフィールが保存されます。

この保存ボタン押下時にViewModelの save メソッドを呼び出して、編集されたプロフィールを保存するとします。

ここで、入力されたプロフィール情報のバリデーション結果に応じてエラーのアラートを表示することを考えます。(ボタン押下前にバリデーション結果を表示できた方がUX上は良いですが...)

ViewModelの save メソッドはプロフィールを「保存する」というアクションを表す命名になっており、「バリデーションが成功したら save メソッドを呼び出してプロフィールを保存するようにしよう」という思考へ派生しやすく、そうするとView側でバリデーションの処理を書きたくなってしまいます。

本来バリデーションのようなロジックは責務の観点からViewで書くべきではなく、ViewModel(あるいはより上のModel層)で実装されるべきです。

このように、「アクションを表す命名」でViewModelにメソッドを追加してしまうと、View側でViewModelを操作する余地が生まれやすく、ロジックがViewへ漏れ出てしまう可能性が高まると思っています。

これを防ぐために、ViewModelのメソッドは イベントを表す命名 にしたいと考えています。

今回の例だと、 save ではなく onSaveButtonClick のような命名に変更できれば、Viewの責務は ViewModelへイベントを伝搬すること のみであるということを明確にすることができ、バリデーションのロジックも自然とViewModelの onSaveButtonClick 内で書く流れが生まれやすいと思います。

また、ViewModelのメソッド名をイベントを表す命名にすることで、ViewModelをみた時に、Viewでどのような操作が行われたらViewModelはどういった状態になるのかを把握しやすくなるというメリットもあるかと思っています。

一方で、多少の冗長性が発生することも事実です。
例えば初回の画面表示時にコンテンツの読み込んで表示する画面があるとします。
読み込みに失敗した場合はリトライ導線を表示して再読み込みできるようにします。
この場合、ViewModelのメソッド名がアクションを表す命名であれば、画面表示時とリトライ時に同一の load メソッドをViewから呼び出してあげれば良いですが、イベントを表す命名の場合は画面表示とリトライという二つのイベントに対応するメソッドをViewModelへ追加する必要があるでしょう。

トレードオフにはなりますが、自分はそれでもイベントを表す命名の方がメリットは大きいかなと感じていて、好んでそのように命名しています。


今回の主張は絶対的な強い意志があるわけではなく、自分はこのような考えを持ってViewModelのメソッドにはイベント名を冠する命名をしている、というフワッとした意見です。

(実際、 Androidのアプリアーキテクチャガイド ではViewModelのメソッド名はアクションを表す命名がなされており、自分の意見が完全に正しいという自信は持てていません。)

みなさんの意見についても何らかフィードバックいただけると幸いです。

Discussion

rizumitarizumita
  • ViewはBindingでViewModelと接続されており、Viewは状態を持っていないので本来的にはValidationは不可能なはず
  • ViewはViewのイベントからViewModelのコマンドを起動することしかできないはず
  • ViewModelはViewのイベント知識は持たないはず(もし持つとloadの例のように二つメソッドが必要になり抽象化が崩れる)
  • saveのためにvalidationが前提となるという知識はViewは持ち得ないはず

なので私はsaveという名前を支持します。

rocknamerockname

コメントありがとうございます!

ViewはBindingでViewModelと接続

ここ、自分はBindingではなく Androidのアプリアーキテクチャガイド にあるような単方向データフローによる状態の接続を想定していましたmm
今考えると前提として書くべき重要な部分でしたね... 🙇

Viewは状態を持っていないので本来的にはValidationは不可能なはず

これも、ViewModelから公開されている状態を見てViewからValidationを書くということが可能という想定で書いていました✍️

ViewはViewのイベントからViewModelのコマンドを起動することしかできないはず

↑の話から、Viewにロジックを書く余地が生まれると考えています

saveのためにvalidationが前提となるという知識はViewは持ち得ないはず

自分もそうあるべきだと思います!
しかしViewに書けてしまうというのが問題であって、命名によってそのような可能性を低くすることはできないか?というのがこの記事の出発点でした🙏

rizumitarizumita

V→VM→Mという依存構造でVの知識をVMに移動させるのは悪手ですので、VMの知識をVに公開するメソッド名にすれば良いです。

例えばsaveWithValidationやvalidateAndSave、他にはtryToSaveなど考えられます。
フォーム入力完了時点でイベントからvalidateメソッドを呼ぶ可能性を考えるとvalidateという知識を公開することは問題ないと考えます。

rizumitarizumita

それから、いくらVMのon系メソッドをVから呼んでも、イベントを直接onメソッドにバインドできなければ他の処理を書けてしまうのは変わらないので、結局規約で守るしかなくなります。

rocknamerockname

おっしゃる通りですね😇
命名はコードに規約を課すところまではしてくれないので、あくまで書き手の判断に委ねられるのは事実です...

rocknamerockname

validateAndSaveのような命名にしてしまうのは非常にスマートで良い解決策ですね👍

V→VM→Mという依存構造でVの知識をVMに移動させるのは悪手

一方で自分はあまりここが理解できていません😢

自分の中でViewとViewModelは同じ括りにあると捉えていて、 (V→VM)→M のようにイメージしています。
Modelの中でView側を意識させないことによるコードの拡張性の担保については理解できるのですが、ViewModelがViewを意識することによるデメリットはあまり思い当たりません。 (複数画面からViewModelを汎用的に扱いたいというケースくらい🤔)

例えばリクエスト中はボタンをdisabledにするために isButtonDisabled のようなフィールドをViewModelに定義してそれをViewから監視させる、というようなことも自分はよくやります。
これを isLoading にしてView側で「リクエスト中はボタンをdisabledにする」と解釈して扱うことも可能ですが、自分はView側でそれらの判断をさせたくなく、極力ViewModelへViewの状態操作は寄せたいと思っています。 (このようなフラグが増えて行った際に、View側で isA && isB のようなロジックを書いてしまうことを危惧しています)

これらのことからもViewModelはViewを強く意識して書くことは前提にあるのかなと思っていました🙏

rizumitarizumita

例えばMVVMのVMの抽象化は再利用性も含まれています。

isA && isBをisButtonDisabledと書いてしまいがちですが、私は可能な限りisA && isBはisCとして抽象化して利用する様にしています。

VとVMが一体となったような実装は、例えばUIKitではDelegateやDataSourceとして表現されているので、MVVMのニュアンスとは違ったものであることが基本だと思います。それを前提とした上でViewModelを論じるのであればいいのですが、一般論として述べるのは違うかなと。

rocknamerockname

なるほどです!
おっしゃる通りで、ViewModelという命名がよろしくないように感じてきました...
自分が論じているViewModelは「Viewの状態管理でSingle Source of Truthを達成しやすく切り出されたStateHolder」にすぎないので、MVVMの思想と混同させてしまうような表現になっているのが問題ありそうですね💦