いま読んでいる本が「自分にとってどのぐらい大切な一冊」なのか、「見える化」するアプリを作りながら考えたこと

はじめに

いま読んでいる本が「自分にとってどのぐらい大切な一冊」なのか、「見える化」するyondeco アプリをリリースしました。
今回は使い方や技術の概要を伝えるためのリリース記事ではなく、タイトルの通り、初学者の私が開発を通じて、さまざまな技術的な選択や学習のアプローチを経験してきたので、その経験と学びを共有し、今後の展望についても触れたいと思います。

 

このブログは4部作になっています。
詳細は自己紹介の後の目次を見て気になった箇所から読んでいたければ幸いです。

  1. サービスの概要コンセプトと問題
  2. アプリを作る上で決めたこと アプリの仕様
  3. 技術詳細上記問題を解決する技術的工夫
  4. 所感学習の気づき、リリースの判断、今後の展望

 

自己紹介

デザイナーからエンジニアを目指して一年半、プログラミングを学習してきました。デザイナーとしてはグラフィック、エディトリアル、事業などのデザインやアートディレクションを担当しており、時にはクリエイティブディレクションも担当しました。デザインの際、わたしは表層部分だけでなく、背後にある構造そのものをデザインすることを心がけていました。この姿勢が"構造家展"で見た建築分野のエンジニア、構造家の取り組みに共感を感じさせました。彼らの姿勢や、コーディングの経験、エンジニアとの出会いを通じて、ソフトウェア分野で"構造家エンジニア"としての道を追求したいと考えるようになりました。

【目次】

 

サービス概要

yondecoアプリは、いま読んでいる本が自分にとってどのぐらい大切な本なのか、見える化できる読書アプリです。 アプリの機能に沿って読書メモをとることで、書評家のように自分だけの本のレビューが完成していきます。

 

yondecoアプリの対象像
  • 自分にとっての大事な本を見つけたい人
  • 自分がどういうフレーズに惹かれるのかを知りたい人
  • 読書を通して自分自身と向き合いたいと考える人
  • 自分だけの視点や感じたことをまとめて書評やレビューを書きたい人
  • 読書をしてはいるが、その読書から得た知識や感じたことを具体的なアウトプットにつなげる方法になれていない人
  • 『ゆる言語ラジオ』のような探究エンタメが好きな人
  • 特に小説やエッセイのような文学的なコンテンツを好む人

 

yondecoアプリが取り扱うサービスの範囲

yondecoアプリが取り扱うサービス範囲の図

 

開発に至った背景

yondecoアプリを作るきっかけは数人の読書会グループに参加していたこと。

読書会ではアウトプットの質を高めるための話し合いで、以下の課題があがりました。

読書会で出た課題

  • 読書の手順がわからない
  • 合わない本を読み続けてしまう
  • 読書メーターamazonレビュー、twitter で発散して満足してしまう
  • 読書を通して自分自信と向き合うことができない。そのため自分だけの本のレビューができない

アウトプットの質を高めるための施策で良かったもの

  • 読書メモの数が多いほど自分にとって大事な本とする。
  • 上記で見つけた大事な本を精読しながらアウトプットに必要なメモを取る
  • アウトプットに必要なメモ:要約、著者の意見、自分の意見、引用された事実
  • 自分が好きなフレーズや気になった部分を前後の文脈含めてストックして読み返せるようにする

 

アプリを作る上で決めたこと

yondecoアプリの特徴は次の通りです。

  • yondecoアプリでは2つの読書体験を以下のように名づけました。

大切な本と出合うための私的な読書 👉 さらさら読書

アウトプットにつながる学びの読書 👉 じっくり読書

  • 「さらさら読書フェーズ」の読書メモは気になったページの写真を撮るだけ。
  • 「保存したメモの数」をもとに 自分にとって大切な一冊かどうかがわかる。
  • 現在の読書フェーズと「 じっくり読書」に進むために何をすべきかがわかる
  • 読書のアドバイスを受けることができる
  • じっくり読書フェーズでは「著者の意見」や「自分の意見」を引用・要約したメモをとれる
  • 「著者の意見の数」に対する「自分の意見数」が見える化される
    • 自分の意見数が増えるとアウトプットの基盤になります
  • 横断的にメモの絞り込み検索ができる。「気持ちが上がったメモ」や「要約」など

コンセプトや使い方の詳細は yondeco アプリの「Topページ」を参照してください

 

本の状態を判定する基準値を決めた

アプリを作るにあたり、自分のとって大事な本かどうか、アウトプットに進めるフェーズかどうかを判定するために以下の基準値を設定しました。

  • メモの数が足りない「さらさら読書」の基準
    • メモの数が、読書ページ数に対して1/8未満
  • メモの数が一定数あってアウトプットのための読書「じっくり読書」に進める基準
    • メモの数が、読書ページ数に対して1/8以上、未読の数が1/4未満
  • メモの数が一定数あるが、内容を咀嚼できていないために通読したほうがいい「さらさら読書」の基準
    • メモの数が、未読の数が1/4以上(未読の数を減らせばアウトプットに進める)

 

アプリにとっての憲法「ユーザー体験設計書」を作成

アプリを作るにあたり、目的をしっかりとさせるため、ユーザー体験設計書を作りました。

ユーザー体験設計書はアプリにとって憲法のようなものです。今後、定期的に見直しますが、ここから外れるものはyondecoアプリ内では作りません。

ユーザー体験設計書では、ソーシャルゴール、読書する人のライフゴール、感情的ゴールを設定しました。


詳細はユーザー体験設計書にまとめています

 

ユーザー体験設計書の作成後はユーザーストーリーにそって、目的を阻害する要因を洗い出し「問題解決のための機能一覧」(リンク先のスプレッドシートの詳細体験設計タブ)を作成しました。

 

Topページのライティングを編集さんに手伝ってもらった

yondecoアプリは、読書に対する新しい定義をしているため、「Topページ の文章」は丁寧に取り組みました。

仕事仲間の編集さんにリライト依頼し、アプリに興味をもっていただいた方にアプリの特徴やコンセプト、使い方が伝わりやすい文章を心がけました。

 

利用者が迷わず使えるように工夫した「画面設計」を作成

yondeco.siteの画面遷移説明図

  • 読書メモの専用スライド:利用者が読書メモを楽しんで読み返すことを目的として、余計な情報を排除したシンプルなモーダル上でスライドを表示します。
  • 直感的なタブ機能: どの読書フェーズにいるのかを簡単に切り替えられるよう「さらさら読書」「じっくり読書」というタブを採用しました。
  • yondecoアプリの2つの軸。
    • 横軸: 読書中の本やメモの一覧表示。
    • 縦軸: 本の登録から、メモの撮影、そして読み返しまでのフロー。
    • この2つの軸が交差する点、つまりメモの一覧ページは、このアプリの中心的存在です。
  • UI:UIに関しては利用者が迷わず使えるよう工夫をしましたが、これに関しては今回は省きます。

 

見た目のデザイン

このアプリの対象者は『ゆる言語ラジオ』のような探究エンタメを聞いている人なので、真面目すぎず、知的に遊ぶような精神をデザインに取り入れました。

具体的なデザインは yondecoアプリを参照 してください  

「読書で遊ぶ」とか「知的にふざける」といった精神を大事にしたかったため、POPなデザインやシュールで遊び心のあるイラストで体現しました。

 

アプリのキャラクターデザインを作成

キャラクターは猫です。夏目漱石とはじめとする文豪は猫を飼っていることが多いこと・私自身が猫を好きなことから猫をモチーフに選びました。 キャラクターのタッチはイラストレータ「しろくじらぷらすし」さんのポートフォリオから選びました。

各キャラクターの説明

りす:

「さらさら読書」のイメージキャラクター。未読の読書メモを口いっぱいに溜め込んでしまう。ちゃんと読み返しましょう。

たこ:

「さらさら読書」のイメージキャラクター。沢山の本を読んで自分にとって大事な一冊を見つけようとしている。

ふくろう:

「じっくり読書」のイメージキャラクター。アウトプットが得意で知的。

詳細:イラストレータさんへの依頼内容 

 

 

技術詳細

使用した技術・言語・ツール

使用した技術・言語・ツールの図

その他お世話になったツール・サービスなど

Swiper、Panzoom、ImageMagickMakefile

 

バックエンド

Ruby

リース直前、本の状態名を利用者に親しみやすい名前に変更したことでクラス名を変更する必要が出た

アプリのリリース直前、利用者にとって親しみやすい本の状態名への変更が必要となりました。具体的には、「乱読」「通読」を「さらさら読み」、そして「精読」を「じっくり読み」へと変更しました。この「さらさら読み」には「乱読」と「通読」の両状態が含まれるため、クラス名の変更だけでなく、名前空間の導入をすることで整理・構造化をする予定です。

クラス名変更の図

 

フロントでは将来的に React を使いやすいように実装した
  • Helper メソッドの使用を最小限にし、表示のためのロジックを書く module 「ViewModel」を自前で実装しました。これにより Controller から View にローカル変数を渡し、View には表示のみを任せることができました。これらの実装方法は将来的に React への移行を見越してのものです。
  • View にローカル変数を渡している理由は View で @を使わないようにするためのものです。 View で@を使うことによる難しさを Ruby コミュニティで聞いていたので、@を使わないで実装できるように考えました。

 

表示のためのロジックは ViewModel に、DB に近いロジックは Model に置いた
  • 現在、 Rails の Controller 内にロジックを記述している箇所がいくつか存在しますが、これは最適ではないと思っています。 DB に直結するロジックは Model に、それ以外のロジックは適切なクラスに分割して実装し、 Controller からはそれらを呼び出す形をとっています。
  • また、 ViewModel 内にもいくつか DB に近いロジックがあるため、これを Modelに移動させる計画です。そうすることで、 ViewModel は状態の操作や管理を中心に担当させたいと思っています。

 

ActiveRecord 型をあまり長く持たないようにした
  • ActiveRecordとDBが密接に結びついていることによる難しさをコミュニティのエンジニアから聞くことがあります。このため、データ取得時にはなるべくRubyで使える基本的な型、例えばArrayに変更して扱うようにしています。

 

コードのリファクタリング単体テスト
  • 今まではテストを実装の後に書いていましたが、今後、リファクタする時は先にテストを書いて修正していきたいと考えています。
  • 各メソッドには用途と期待する戻り値をコメントアウトで残しているため、それらをもとに足りない単体テストを追加していく予定です。せっかく学んだテスト技法もここで活かしたいと思います。

 

自前でアップロード機能を作成

よく使用される Carrierwave や Active Storage を使わず、独自のアップロード機能を実装しました。

画像処理には、主流の Gem  「mini-magick」や「r-magick」の代わりに直接 ImageMagick を活用しました。

 

画像名のサニタライズ
  • system コマンドを使う必要があるため、ファイル名に関してはActiveSupport::Inflector.transliterateを使用して非 ASCII 文字を ASCII の近似値に変換し、さらに正規表現を使ってスペースやワード文字以外の文字を除去することで、ファイル名を安全な形式にサニタイズしました。
  • シェルのメタ文字や特殊文字によるインジェクションのリスクを回避するためsystem コマンドではカンマで区切りました。

     

ファイルの内容自体のチェック
  • アップロードファイルの初期の検証は正規表現を使用していましたが、実際のファイル内容も検証する必要が生じました。
  • ファイルの Mime-Type を検証するために「Marcel」という Gem を採用しました。

 

ユーザビリティの向上
  • 古いスマートフォンでも yondecoアプリでの画像投稿をサポート。特定の「HEIC」形式の画像も「jpg」に変換し、必要に応じてリサイズ処理を施しました。

 

複数枚の画像投稿の際の仕様
  • yondeco アプリでは、ユーザーは複数の画像を一度に投稿可能です。
  • もし投稿された画像の中に保存不可のファイルが含まれていても、他の問題ないファイルは正常に保存されます。
  • すべての投稿処理終了後、エラーメッセージは改行で区切られ、 JavaScript を使用して flash メッセージとしてユーザーに表示されます。

 

ビルドツールに「Vite Ruby」を使用

「Vite Ruby」は RubyRails のウェブプロジェクトで、 webpack の代わりとして使える、より速いページ更新とビルドを実現するツールです。

  • webpacker のメンテナンスが終了したことを知り、生の webpack を使おうと思いましたが、少し使っただけでも管理の大変さを感じたため、いくつかのビルドツールの候補を出した上で Vite Ruby を選択。アプリから webpacker を剥がし、代わりに Vite Ruby を使用しました。
  • 現在の yondeco アプリは Rails の中でフロントを作成しています。 Vite Ruby は特定の Js ファイルから特定の HTML.erb ファイルだけを読み込むことができず、 Js ファイルがすべての HTML.erb を読み込んでしまう問題が生じました。これを解決するために、 If 分岐を利用して、特定の条件下でのみ必要な HTML.erb ファイルを読み込むように回避策を施しています。
  • このとき、私はソフトウェアの問題点を「ISSUE」として報告するだけのスキルしか持っていませんでした。今後は OSS にパッチを提供できるようにしていきたいと考えています。

 

フロントエンド

Vanilla JSを選定して基礎理解

Vanilla JS と Railsでの状態管理の理解

当初、私は React を導入することを検討していましたが Vanilla JS を選定しました。React では state の変化時に component が更新され、Reducer を通して状態管理が行われます。この仕組みは MVC モデルに似ていると感じました。しかし、 React を直接導入する前に、 Rails 内での View や Controller の操作を Vanilla JS が担当することで、その動作の理解を深めたいと考えたためです。

MMVCでのJsの役割の図

 

ユーザー体験の最適化

ユーザーにストレスフリーな体験を提供するため、部分的な表示の更新やフロントエンドでの入力バリデーションを導入しました。

 

非同期通信のアプローチ

RailsAjax 機能を利用せず、より深く非同期通信を理解するために、remote: true .js.erbファイルではなく、fetch 関数を使用して非同期処理を実装しました。

 

JSライブラリの統合時の課題

モーダルのスライドページにおいては、複数の JavaScript ライブラリを使用しています。これらのライブラリ間での機能の競合が発生し、一時的な困難に直面しましたが、ドキュメントを丁寧に読み解き、デバッグを行いながら問題を解決していきました。

 

バケツリレーとの向き合い方

JS 単体での todo リスト作成時には、「バケツリレー」という状態管理の課題を体験しました。しかし、今回のアプリケーション開発では、JSから直接 html.erb の子要素へデータを渡す方式を採用したため、この課題を特に感じることはありませんでした。次回の React の利用時には、 状態管理ツールを使用せずに開発し「バケツリレー」を経験してみたいと考えています。

 

まずはピュアCSSを使ってCSSを書けるようにした

CSSツールの技術選定で迷う
  • 特に「TailwindCSS」とピュアな CSS の間で迷っていました。 TailwindCSS は、「Bootstrap」のように JS が組み込まれていない UI コンポーネントが多いことから CSS が苦手な私にも魅力的に感じました。また、クラス名が長くなるという懸念については設定によって短縮できることもわかりました。
  • しかし、最終的な技術選定は、業務での経験者の意見を聞きたいと考え、 MENTA というサービスを利用して複数のメンターと相談しました。
  • 自分のスキルと制限時間、期待値を明確に伝えた上で、6 名のメンター全員からピュアな CSS の使用をお薦めされた。これによって、基礎を積む意味でも、まずはピュアな CSS を採用することに決めました。

 

毎日1時間 CSSペアプロ・質問時間を1ヶ月間設けた
  • デフォルトでペアプロ・質問時間を 1 ヶ月の間、毎日 1 時間設ける設定にしました。
  • 何をどう進めたいのかの計画をメンターさんに伝えた上で実装を進め、困った時に質問をしました
  • 月の後半では、 CSS の整理方法への興味が自然と湧き、マージンやフォントの大きさをクラス化するなど自分で工夫しつつ、メンターさんのやり方を見せてもらいました。メンターさんはSASSを使っていたが、最近の CSS は変数機能を持っているので、その機能を活用して色を管理したいと思います。

 

必要を感じて Linter / Formatter を使った
  • CSS でスタイリングをする場合、HTML構造の修正が頻繁に発生するため、自分でインデントを修正するのは非効率でした。 学習初期は Linter があることを不便に感じることがあったが、スタイリング段階ではインデントの自動化が重要と感じ、保存のたびに Lint を通して自動コード整形をデフォルト有効にしました。
  • 自分で作成した以下の全てのファイルに対して、自動整形をデフォルトにしました。
     対象ファイル:Ruby(.rb)、JavaScript(.js)、JSON、ERB(.html.erb)およびCSS(.css

 

インフラ

インフラ周りのステップアップ

インフラ構成図

 

アプリ作成初期から手動でデプロイしていたが Github Actions で自動化
  • Github Actions を使うまでは手動でデプロイしていました。
  • その後は Makefile を使ったデプロイに変更したが、デリバリーはできるだけ早い方が良いと感じ、 Github Actions を使って CI/CD を自動化しました。
  • ローカル環境での開発には、 Docker を使用する前は Vagrant を活用していました。その際、コンテナや仮想マシンを作成し直すたびに、 postgreSQLruby などのツールやソフトウェアを毎回インストールし直す必要がありました。 また、自動化する前までは、何度も手動デプロイをしていましたし、あらゆる場所からEC2 や VagrantSSH でのデプロイも経験していました。その経験が、 Github Actions の CI/CD を書く上で非常に役立ちました。

 

Dockerを活用したリモート・ローカル環境の自動構築

以前は Vagrant を使用してローカル環境を構築していました。その主な理由は、リモート環境と同じ Ubuntu を使用したかったためです。しかし、コンテナや仮想マシンを新しく作るたびに必要なツールやソフトウェアをインストールする必要がありました。その際、一部のインストール作業は Makefile を活用して行っていました。この経験が、後に Docker を使って環境構築を自動化する際の基盤となりました。

 

AWS の Elastic Load Balancing (ELB) か Nginx か
  • Nginx を利用した理由は、無料であり、小規模なアプリケーションでロードバランサーの利用が不要だったからです。
  • SSL 証明の取得には無料の Let's Encrypt を使用しています。ローカル環境ではDNS チャレンジで、デプロイ先の EC2 では HTTP チャレンジで証明書を取得しています。
  • Route 53 でドメインを登録し、ローカル環境ではサブドメインを作成して使用しています。

 

デプロイ先はAWSのEC2
  • EC2 を選択した理由はIaaSの利用ができるためです。仮想マシンインスタンスを立ち上げ、 OS やアプリケーションを自由にインストールできる柔軟性に魅力を感じました。
  • 最初は EC2 上に postgreSQL サーバーをインストールして利用していましたが、学習の一環として RDS の使用に切り替えました。

 

認証・認可

自分のサーバーにログイン情報を持たせたくなかったため SaaS 系を利用しています。近いうちにパスワードレスの設定をしたいと考えています。

 

リポジトリはこちらをご覧ください

github.com

 

所感

開発を進める中での学習に関する気づきと改善

初期の課題

アプリを作り始めた際、取り組めるタスクが限られていました。これまでの学習方法は本を網羅的に読むことやコードの写経が中心で、実際のアプリ開発とは異なるアプローチが必要でした。

 

課題解決のアプローチ

アプリ制作を進めるために、以下のステップを実践しました:

  1. ユーザー目線で解決したい問題を明確化
  2. その問題を解決する機能を設計
  3. 何の技術を習得すべきかを調べる
  4. 技術の習得から機能作成までのステップ式のカリキュラム※ を自分で作成しレビューを受ける
  5. それに基づき機能を実装

※ ステップ式のカリキュラムでは、最低限の基礎知識だけで作成し、段階的に理想に近い機能を作成した。

 

新たな知識が必要になった時の全体像を掴む手順
  1. 信頼できそうな記事のハンズオン
  2. 関係性やコマンドの意味を図解して全体像を理解
  3. 自分のアプリに適応して実践
  4. 用語がわからない時はいくつかの記事を見て、共通の言葉をピックアップし理解する

 

わからない時に対象課題を図解化して質問

学習が後半に及ぶに従い、質問をする際は自分の理解を図解化したり、取り組んだ/取り組んでいないタスクを一覧化してから質問をするようになった。

 

現在の学習課題

仮説の的外れな点が多いのが現在の課題です。多方向からの仮説をすべて書き出してから少しずつ調べたり質問することを意識しています。

現在はリファクタ段階にいてプロセスを重要視している。 そのため以下のアプローチも取り入れています。

 

理想のプロセスに近づける方法を自分の能力でできる方法に落とす
  1. 現状の自分の方法と理想のプロセスとの間のギャップを特定する。
  2. ギャップを埋めるための方法を、自分の能力に合わせて調整し、ステップバイステップで進める。

詳細:私の学習の工夫

 

どこまで作ったらリリースするべきかの判断

アプリのリリースの判断基準として、「アプリを使用する際のコアな機能がすべて動作するか?」を主な基準としました。ユーザーへの価値を明確に伝えるためのチラシを作成し、その中で挙げられたコアな価値を実現する機能がすべて動作する段階を MVP ( Minimum Viable Product )と定義しました。 その結果、「削除」などのサブ機能はMVPの段階では使えない場合があります。 デザインのお仕事では、まだないベータ版機能のボタンを配置することもありました。ユーザーの反応を見て、需要が高い場合に具体的なUI設計や実装を検討するためです。 このアプリでも利用者のニーズを取り入れつつ、優先度が高いタスクから着手していきたいと思っています。

 

今後の展望

サービスとして、本と利用者の関係性を強化していきたいと考えています。一方、コードの面では、テストの追加やクラス名の変更、コードの整理など、多くの改善点が見られるため、リファクタリングを進めていきたいと思っています。

サービスの向上(興味の順番)
  • 自己理解の強化: 読書メモを解析し、利用者がどのフレーズに興味を持っているかを示す機能の追加。
  • 音読機能:読書中に撮影したメモを二次元キャラが音読する機能。APIのリクエスト方法を工夫し、ワード数の制限を回避できることは実験により確認済みです。
  • 利用者のカスタマイズ:利用者が「さらさら読み」「じっくり読み」のような本の状態の判定基準をカスタマイズできるようにする。
コードの向上
  • 状態名のリファクタリング状態名の変更を反映させ、適切なクラス名に変更。
  • ドメイン名の使用状態をフラグで示している部分をドメイン名に変更。
  • クラスの導入Hashで表現されているクラスを正式なクラスに変更。
  • 新しい技術の探求Rust製の全文検索『MeiliSearch』の導入や、フロントエンドのReactとTypeScriptへの移行、画像の保存先をS3にしたり、terraformを使うことに興味があります。

 

***

 

よければこちらも

このアプリを作るきっかけになった読書会での私のアウトプットの一つです。
架空雑誌を作り、以前からの研究テーマ「旅と音」で企画を立ててみました。

maki-ooo.hatenablog.com