アプリ制作に至った経緯
まず、筆者自身が「学習したことをタイミングよく繰り返し復習すること」が苦手であるという課題を抱えていました。
筆者はかなり好奇心が旺盛でいろんなことを並行して勉強してしまう傾向にあります。特に一度勉強したことを復習するよりかは自分にとって新しい知見を得ることが楽しく優先してしまいがちです。「これではよほど記憶力がよくない限り勉強したことがなかなか定着しない」ということは日々感じていました。特に試験などという強制的に復習を促すシステムがない独学ではなかなか復習をする時間をとりづらいということがありました。
そこでこのような復習をすることに関する自身の課題感から『独学大全』という書籍を購入し、なにか参考になる方法はないか探しました。
そこで出会ったのが「ラーニングログ」という方法でした。ラーニングログとは「手帳などに勉強範囲に対応するブロックを書き、勉強したらそのブロックを塗りつぶしていく」という方法で学習の進捗を記録し、その道のりを振り返るのに活用するというものでした。
この方法に出会ったとき「この方法で学習することをアプリでサポートできないか」と思い至りました。アプリで「ラーニングログ」を作れば学習を記録すると同時に「記録から復習のタイミングを自動で計算し知らせる」ということもできより強力に学習をサポートできるのではないかと考えました。
以上が LLog を作成するに至った経緯です。
主な機能と使い方
現在 LLog には主に次のような機能が実装されています。
- 学習の進捗を管理する機能
- 学習したことをマークダウン形式で記録するための機能
- 自分が学んだことをいつ復習するべきなのかを知らせる機能
- コンテンツ、ノート、ログを複数条件で検索できる機能
以下では LLog を使用するための一連のワークフローと共に用語の解説をしていきます。
1. コンテンツを作成する
「コンテンツ」は LLog 内で学習の進捗を管理するための最も大きな単位です。基本的には一つの書籍を一つのコンテンツに対応させることで書籍の学習状況を管理するという使い方が想定されています。(必ずしも書籍である必要はありません。ネット記事などをコンテンツとして管理しても問題ありません。)
例えば筆者は最近"Computer Systems: A Programmer's Perspective"という書籍を購入したので、LLog を使ってこの書籍で学んだことを記録していきたいと考えました。このように考えたタイミングがコンテンツ作成のタイミングです。

上の画像のようにコンテンツを作成する際には「コンテンツのタイトル」と「作成するブロック数」を入力することが求められます。
ここで「ブロック」とは LLog において学習の進捗を管理するための最も小さな単位であり、コンテンツを書籍に対応させた場合はページに対応させる使い方ができます。
例えば"Computer Systems: A Programmer's Perspective"は合計で 2568 ページありますので 2568 ブロック作成します。ページとブロックを一対一で対応させることで 1 から 10 ページ学習したときには「1 から 10 ブロック学習した」ということを LLog に記録することができます。もちろん対応関係をページにする必要はありません。ページに対応させる他にも「章を 1 ブロックに対応させる」という使い方もできます。
以下の画像はブロックを俯瞰で見るための画面のスクリーンショットです。後で詳しく説明しますが、ブロックの色の濃淡を見ることで優先して復習すべきブロックがわかるようになっています。

2. ノートを作成する
コンテンツを作成すると下の画像のような UI を使用してノートを取ることができるようになります。このノートはマークダウン形式で記述できます。ノートはブロックと結びつけることができ「どの範囲を勉強したのか」という情報を持たせることができます。以下のスクリーンショットのノートでは 247 番目のブロックが登録されています。


3. ログを作成する
ノートを取るとそのノートの内容をもとに「ログ」を作成することができるようになります。ログを作成することで LLog の学習進捗管理機能を最大限活かすことができます。ログは「学習の記録」であり「学習した範囲・内容と共にその学習をいつ行ったかを記録するもの」です。
ログを作成すると元となったノートに登録されているブロックの「レベル」が最大の 5 に変更されます。ブロックのレベルは 0 から 5 の 6 段階ありレベルがあがるほど「そのブロックに対応する内容を記憶している可能性が高いこと」を示しています。
ログの作成はノートを取った直後に行うものなのでその内容を記憶している可能性は高いはずですからログの作成直後にはブロックのレベルが最大になるということです。
4. ログを使って復習をする
Logs ページでは今まで作成した全てのログを見ることができます。このページではログとそのログのブロックのレベルを俯瞰して見ることができるようになっています。ログ作成時にはレベルが 5 だったブロックは時間経過とともにレベルが下がるようになっており、「レベルが低いブロックを持つログの内容は復習を優先的に行うべきである」ということを意味しています。
このページでどのログを復習すべきかを確認したらそのログの内容を復習します。ここでポイントなのはログから新しいログを作成することができるということです。ログの内容を復習してログ作成ボタンを押すと同じ内容でログが作成されブロックのレベルは最大になります。

なおブロックのレベルの下がり方は「そのブロックを何回学習したか(そのブロックを含むログを何回作成したか)」に依存しています。1 回だけしか取り組んでいないブロックはレベル 5 から 1 に下がるのに 1 日しかかかりませんが、2 回取り組んだブロックはレベル 1 まで下がるのに 3 日かかります。取り組み回数が増えるほどレベルが下がるまでの時間が長くなるという仕組みです。(レベル 0 は「まだ取り組んでいない」という意味なのでレベルが下がるのは 1 までになります。)
基本的にログを中心に使うことで学んだことを復習していくような使い方になるかと思います。
以上が LLog の大まかな使い方の説明です。
使用技術とその選定理由
LLog のプロジェクトは 「React と TypeScript を中心に昨今の Web フロントエンド開発で使用されているライブラリ、言語、ツールなどの使い方を学ぶ」という目的がありました。また、以前から React を勉強しており、より実践的、本格的な形式で使い方を学ぼうというのも目的の一つでした。 したがって、まず第一に利用しようと考えていたのは React と TypeScript でした。他の技術に関しては React と TypeScript を使用することを念頭に、React と TypeScript と相性がよく初学者でもサポートが受けやすい技術かどうかという視点から選定を行いました。
-
Electron
LLog は基本的にローカルでオフラインで使用することが多いと考えられるので Web アプリである必要がなく、むしろデスクトップアプリの方が適していると考えました。ネットワーク上のやり取りが発生せずスムースな体験を実現できるのではないかと考えました。
-
Emotion
styled components を使うか迷いましたが css props の使い勝手が良いと感じたため使用しました。styled components のいわゆる styled API による抽象化ではコンポーネントの内部で何の要素使われているのか分かりにくくなることがありましたが、css props では要素が見えやすくコードが理解しやすいと感じました。
-
ChakraUI
簡単に統一感のあるスタイル実現できそうだったため使用しました。
-
Framer Motion
宣言的にアニメーションのコードを書けるのが React の書き方と相性がよいと感じ導入しました。いざとなれば命令的にもアニメーションのコードが書け、自分でカスタマイズもしやすいと考えました。
-
React Ace
出来合いのテキストエディタとして十分な機能を持ち、少しのカスタマイズで LLog にあったエディタを実現できそうだったため。
-
Prisma
SQL 自体書いたことがあるもののほとんど経験はありませんでした。今回データベースには sqlite3 を使用しましたが直接操作するには学習コストが高すぎると感じたため Prisma を導入しました。 スキーマを書くと sqlite を操作する関数群と TypeScript の型情報を生成してくれる機能が便利だったので使用しました。
こだわり
UI のこだわり
『誰のためのデザイン? 増補・改訂版 認知科学者のデザイン原論』で学んだことを念頭に UI の設計を行いました。具体的にこだわった部分やその背景にある思考などは拙著『個人開発アプリとその UI デザイン』をご覧ください。
特に自分的にこれは面白い設計だと考えているのは『ログ作成のときにログのタイトルを必ず要求する』という設計です。先述した通り LLog は「ノートを取りログを作成する」という流れで使用されます。ログ作成時にログのタイトルを要求することで強制的に学んだ内容を抽象化させることで記憶に残りやすくなると考えこのような設計にしました。
ソフトウェア設計と実装のこだわり
LLog 開発前には Atomic Design を試しうまくいかなかったということがありました。Atomic Design では UI コンポーネントの小さい順に atoms、molecules、organisms、templates、pages などと分類してコンポーネントを整理しようという方法でした。しかしながら「コンポーネントの大きさでの分類する」というのは分類のための分類であり、ドメインの関心とはほとんど関係のない分類になっているように思いました。
このような背景から LLog では Atomic Design を採用せず、自分でディレクトリ設計を手探りで試すことにしました。ここで試したのは以下のようなディレクトリ設計です。(後に『反省点・改善すべき点』言及するようにこの設計がベストだとは考えていません。)
components/
Component1/
index.tsx
styles.ts
hooks/
...
# Components1を構成するコンポーネントをcomponentsに格納する
components/
Components1-1/
index.ts
styles.ts
...
Component2/
...
layout/
Layout1/
pages/
Page1
Page2
上述のように筆者は「分類のための分類」に違和感を持っていたため LLog では「分類のための分類」を極力排し責任ごとに分類することにしました。また 「コンポーネントはさらに複数のコンポーネントから構成される」という構造をそのままディレクトリ構造に反映させました。これにより「このコンポーネントはどの分類か」を考えることなくコンポーネントを作成できるようになり、また、どのコンポーネントがどこにあるか予測しやすくなると考えています。
反省点・改善すべき点など
ディレクトリの設計
React のコンポーネントの整理の仕方に迷いました。特に renderer ディレクトリ直下の components ディレクトリには雑多なコンポーネントが特段整理されずに入り乱れる巨大なディレクトリになってしまいました。Icons があるのに兄弟に CloseIcon があったり、ルールが決められておらずどこに何があるのか予測しづらい設計になっているように感じます。「関心の分離」を徹底することで、機能のまとまりごとにコードを整理していくのが良いのではないかと考えています。
例えば、Notifier コンポーネントと NotifierContextProvider はユーザーがログの作成やノートの保存などの操作を行ったことを通知する機能を提供するものですが、二つのコードは components ディレクトリ直下に他のコード(ContentMenu や Forms など)と一緒に置かれています。Notifier と NotifierContextProvider は互いに強く関係するコードなので、それを示すためにも二つのコードはさらに細く分類された notifer ディレクトリに格納するのがよいかもしれません。また、機能と純粋な UI パーツを分ける意味で features ディレクトリ、ui ディレクトリを作ると良さそうだと感じました。
// 現状のディレクトリ構成
renderer
// componentsという括りにあらゆるコンポーネントが直接置いてあるのは大雑把すぎる
components
CloseIcon
Forms
Header
Notifier
NotifierContextProvider
// 改善案
renderer
// 純粋なUIとアプリの機能でディレクトリを分ける
features
notifier
Notifier
NotifierContextProvider
Forms
ui
icons
CloseIcon
Header
コメント、issue の書き方
プロジェクト初期の頃に書き出した issue について、情報量が足りず後から見返して何を言っているのかわからないということがありました。改善案を以下に書き出します。
- UI に関する提案は現状の問題となっている UI を画像付きで説明する。アニメーションに関する提案は動画をつける。
- 現状と理想の状態を明確に分けて書き、解決すべき問題を示す。
- コードに関する改善には必ず現状のコードを示し、コメントによって具体的に何が悪いのかを書く。さらに理想のコードを示し現状のコードとの違いを明確に示し何が良いのかを具体的に示す。
- 参考文献や根拠となる資料を示す。
- 調べたこと、考えたことを全て書く。
- 最も表現力が高く分かりやすい言語で書く。(初めは英語の練習のために英語で書いていましたが途中からは分かりやすさのためにネイティブ言語である日本語を使うことにしました)
コードフォーマッターの導入と設定
このプロジェクトの後にグローバル環境に Prettier を導入しました。その結果、LLog のコードに変更を加え保存するごとにフォーマットされてしまい。コードが中途半端にフォーマットされてしまうことになりました。今後のプロジェクトでは初めから Prettier などのコードフォーマッターを導入するのがよいと感じました。
テスト
今回のプロジェクトではテストを書いていません。現状大きな仕様変更やリファクタリングを行っておらず、ソフトウェアが壊れるといったことはありませんが(テストを書いていないので壊れていることが検知できていない可能性はあります)、今後行う予定のリファクタリング作業で「テストを書けばよかった」と実感するのではないかと考えています。今後、リファクタリング作業と合わせてテストコードを追加していきたいと考えています。
追記:2024/3/5 に ESLint および Prettier を導入し、すべてのコードにフォーマットを適用しました。
今後やりたいこと
今後はまず開発環境のアップデートを行いたいと考えています。現在、モジュールバンドラーとして Webpack を使用していますが、これをより高速であるとされている Vite に置き換える作業を行うつもりです。実際にどれくらいビルド時間が改善したのかパフォーマンスの計測も同時に行いたいと考えています。
追記:2024/3/26 Webpack から Vite へのリプレイスが完了しました。今後はリファクタリングを行う予定です。また、ページング機能を新たに実装したりノート検索機能を強化していきたいと考えています。
主なアップデートの記録
- 2024/3/26 Webpack から Vite へのリプレイスが完了しました。
以下は Webpack と Vite でそれぞれ 5 回ビルドを行ったときビルドにかかった時間の平均値です。
| Webpack | Vite(electron-vite) |
|---|---|
| 16430 ms | 4028 ms |
ソースや最終的なビルド結果が異なるため単純に比較することはできませんが約 4 倍の改善となりました。