お部屋掃除官僚

日々の掃除タスクを管理するため、お部屋掃除官僚というアプリケーションを書いて半年くらい使っている。名前の由来はちゃーしゅーねこさんが一時期週末の部屋掃除をするたびに「お部屋掃除官僚」というポストをしていたから。最近はIMEが賢くなったらしく普通に「お部屋掃除完了」というようになってしまった。

リポジトリ: https://github.com/osak/room-cleaning-bureaucrat

スクリーンショット:

動機 & 目的

わざわざアプリケーションを書いた理由は、掃除場所によって適切な間隔が異なるから。例えば床はすぐホコリが溜まるので頻繁に掃除したほうがいいが、トイレや流しの掃除はそんなに頻繁にやらなくてもよい。とはいえ目に見えるほど汚くなってから掃除を始めると大変なので、きれいな状態を保ちつつ面倒ではないくらいのちょうどいい間隔で掃除をしたい。掃除箇所が多くなってくるといちいち覚えておくのが面倒なので、そこの管理をプログラムでやることにした。

アプリケーションの挙動としては、作りながら試行錯誤して以下の仕様に落ち着いた。

  • インターフェースはNotionにする。
  • 前回掃除をした日付から一定の日数が経ったら、その掃除をやるようにリマインドする。
  • リストを「期日を過ぎたのでやる」と「まだ期日に達していない」の2つの状態で管理する。
  • 期日を過ぎてからは好きなタイミングで消化できる。

Notionで作ることにしたのは、日常的なメモをNotionで書いているから。Kanban board的な機能があってAPIも公開されているのでまあ行けるでしょうということで決めた。最後のポイントは、タスクの消化に期日を設定してしまうと一度オーバーしたときに一気にやる気を失いがちなので、好きなタイミングで掃除すればいいという意図で設定した。実際に期日を過ぎてから数日経ってやる気が出たときに掃除するという運用をよくしている。

設計方針

お部屋掃除官僚の設計をする上で、以下の方針を定めた。

  • バグやイレギュラーな対応のため、タスクの状態は人間がいじれるようにする。
  • 内部状態を持たない。全ての状態は表示されていて、状態遷移も目に見える情報のみで行われる。
  • 次の状態変化を引き起こすトリガーもフィールドとして管理する。

この手の日常使いソフトウェアは要件定義があいまいなので例外的な処理を必要とすることが往々にしてあるし、バグって微妙にうまく動かなかったときにすぐコードを修正できるほど時間と気力があるとも限らないので、人力でworkaroundできる仕組みがあるに越したことはない。するとほぼ必然的に、人間がUIから触ることのできない内部状態(Notion外に置かれたDBなど)は排除されることになる。また、全ての情報を見ようと思えば見えるという設計にすることで、初期設計の段階では見せる必要がないと思っていたけど実は見えたほうが便利だったというケースをすくい上げることができる。実際にこの手のシステムをどう内面化するかはやってみないと分からないところがあるので、全ての情報を見せておいて必要なければ隠すという方針にすると、使っていて気持ちいいシステムになることが多いと思う。

最後の項目は、期日を過ぎたタスクを「やる」リストに移すときの条件として、最後にやった日付からの差分を毎回計算するのではなく、最後に掃除を終わらせた時点で次にやるべき日付を計算してフィールドとして保存したものを参照するということ。スクリーンショットでは「次にやる日」として見えているフィールドが相当する。「次にやる日」がプログラムの実行時にしか存在しない揮発性のデータでなく、Notionに保存されているという点で「内部状態を持たない」の一環とみなすこともできる。実際に手動で「次にやる日」を編集する必要に迫られたことはないが、プログラムに依存せず管理できることには安心感があるし、UI上で表示することによって未来のタスクが予測しやすくなるという嬉しい副作用もあった。

実装

実装はDenoを使った。HTTP通信でJSONをやり取りするプログラムで、規模もあまり大きくならないが書き捨てのスクリプトよりは複雑、という条件なのでTypeScriptを使いたいという選択が前提にあり、たまたまいくつかのプロジェクトでDenoを目にしたので試しに使ってみることにした。package.jsonを自力で書く必要がないこと以外は総じてnpm経由で使うのと同じだった。オプションで明示的に許可しないとI/O等に制限がかかるセキュリティは目玉機能なんだろうけど、依存ライブラリ数も少ない個人開発だとあんまりメリットを感じなかった。

時刻を扱うので素のJavaScriptではきついと思い、最初はdate-fnsを使うつもりだったけど、調べたらTemporal APIという、JavaのJodaやjava.timeに似たAPIがStage 3で実装もあるようなので使ってみた。今回の用途では「日本のタイムゾーンでN日後」を計算するための、タイムゾーンを意識したZonedDateLocalDateの変換が主な用途で、この用途では特に苦労することなく使えた。しかし後で機会があって別に調べたところ、フォーマットのAPIが貧弱なのでRFC 3339でない特定の書式を要求するような用途には使いづらいっぽい。こうやって見るとJodaやjava.timeがいかにうまく設計されているかがよく分かる。

掃除を終わらせた後は日付を記録しておく必要があるので、中間状態として「掃除を終わらせた」に対応する状態を導入した。この状態にあるタスクは日付を記録して「まだ期日に達していない」状態に遷移する。ちょっと頑張ればこの状態を消せる気もするが、実際に動くことが重要なので簡単な方針を選んだ。

運用

一度走らせると現在の日付を参照してNotionの状態を更新する、という形のアプリケーションにしたので定期実行する必要がある。実行環境としてはAWS Lambdaを使った。Lambda単体ではcronのように定期実行させることはできないが、EventBridgeと組み合わせると簡単に定期実行できるようになる。

Lambdaで走らせること自体はdeno-lambdaというプロジェクトを使って簡単にできた。このプロジェクトでLambda用のLayerも公開されている。しかし公開されているLayerはlockファイルの処理にバグがあり、実行のたびにnpmから依存ライブラリをダウンロードしてくるようになっていた。修正のPRは存在しているもののまったく取り込まれる気配がないので、このPRを取り込んだLayerを自作して使うことにした。

今回は踏まなかったものの、別件でDenoからS3に繋ごうとするとSSLのバージョンが低くて失敗するという問題を踏んだので、DenoをLambdaで動かす設計は可能なら避けたほうが良いと思う。

実際に使ってみて

目に見える形でやるべきタスクが示されると、あまり考えなくてもタスクに取り組み始めることができるので掃除に取り掛かるのが楽になった。気が乗らないタスクは割と数日とか数週間とか放置していることもあるが、そういうことをしても何も怒られないし、気が向いたときにやればまた定期的にリマインドしてくれるので、繰り返し発生する作業をゆるく継続するモチベーション源としても役に立っていると思う。

まとめ

定期的に掃除タスクをリマインドするために「お部屋掃除官僚」というアプリケーションを作って、AWS Lambdaで定期実行する環境を整えた。タスク管理のプラットフォームにはNotion、アプリの実装にはDenoを使った。なるべく隠れた内部状態を排除することでメンテしやすくした。押し付けがましくなく掃除することを促してくれるので、結構気に入っている。