年末なので、過去のPRを振り返って、自分なりに学びがあったElmのモデル設計を記事にしてみようと思います。
同じような仕様や、今後設計を考える上で参考になれば嬉しいです!
当時まだまだエンジニアになりたての時に書いていたコードなので、至らないところも多いと思いますがご容赦ください。
作った機能
チュートリアル機能
簡単な仕様
- 各項目がどれくらい完了しているかによってviewが異なる。(順不同)
- 全ての項目が完了しているかどうかによってviewが異なる。
- チュートリアルをそもそも表示しない対象が存在する。
- 各項目のステータスの状態によってviewが異なる。
- 「完了・たった今完了・未完了」が存在。
初めに作成したModel
{isDoneHoge : Bool, isDoneFuga : Bool, ...}
のように、各項目が終了しているのかどうかの状態をそれぞれ、フィールドとして持つのではなく、カスタムタイプをうまく活用したいという思いで設計を目指しました。
type alias Model =
{ tutorialStatus : TutorialStatus
}
type TutorialStatus
= NoTutorial
| Tutorials Profile Post Edit Save
type Profile
= ProfileCompleted
| ProfileJustCompleted
| ProfileNotCompleted
type Post
= PostCompleted
| PostJustCompleted
| PostNotCompleted
type Save
= SaveCompleted
| SaveJustCompleted
| SaveNotCompleted
(今改めて見るとほんとにいけてないですね。。。)
実装中に無理やりなパターンマッチが生じてしまう箇所もあり、違和感を感じていたものの、Modelを改善するというところまで自身で辿り着けなかったのは反省です。
よくない設計だと思うポイント
***「カスタムタイプを使う方が良い!」
***という思考が先行しすぎてて本質的には良い設計になってませんでした。
- チュートリアル項目の追加・削除といった変更に弱い。
- modelに応じたviewを生成する際等にパターンマッチが必要になるが、変更コストが高い。
- 各項目ごとにカスタムタイプが記載されているため、共通の操作関数やview関数が作成しづらい。
レビューを受けて改善したModel
チュートリアルの**「各項目名」と「(各項目の)状態」**を別のカスタムタイプで管理するように変更しました。またそうして、作成されたレコード(TutorialStatus)のリストにすることで各レコードの追加や削除への対応を簡単にし、また操作関数を適用しやすい設計にしました。
最終的にはこのサンプルのようなModelを採用しました。
やはり旧バージョンよりすっきりしていて、読みやすいし扱いやすいです。
type alias Model =
{ tutorialStatus : TutorialStatus
}
type TutorialStatus
= NoTutorial
| ExistTutorials (List Tutorial)
type alias Tutorial =
{ name : TutorialName
, status : TutorialStatus
}
type TutorialName
= Profile
| Post
| Save
type TutorialStatus
= Completed
| JustNowCompleted
| NotCompleted
今思えば改善できそうなポイント
-
NoTutorial
とExistTutorials []
が同じ意味を表してしまう。- tutorialsを
List Tutorial
に変更する。 -
[]
はチュートリアルが存在しない状態を表現する。
- tutorialsを
(※当時、これでは実現できない仕様があったかもしれないです)
type alias Model =
{ tutorials : List TutorialStatus
}
type alias Tutorial =
{ name : TutorialName
, status : TutorialStatus
}
type TutorialName
= Profile
| Post
| Save
type TutorialStatus
= Completed
| JustNowCompleted
| NotCompleted
view : Model -> Html msg
view model =
case model.tutorials of
[] ->
text "チュートリアルが存在しない。"
missions ->
Debug.todo "チュートリアルが存在している際のviewを記載"
まとめ
- 手段の目的化を避ける。目的思考を大切に。
- Elmのカスタムタイプは便利!ただし、何でもかんでも使えばいいというものではない。
- 一度設計したModelを崩して、再構築する決断をいとわない!
- 違和感や、扱いにくさを感じた時点で一度Modelの設計を振り返る。