我々は何から手を付けるべきか

TL;DR

  • 須田の自慰ポエム
  • プロダクトやプロジェクトの意義を忘れないようにしたいよね
  • たとえコストが高くても、コア機能から実装していきたいよね

本文

最近、大学の主専攻実験のチームやプライベートで組んでいるチーム、バイト、あるいは個人で様々なプロダクトやプロジェクトに携わるようになってきた。それらの多くはまだこの世に存在しないものを自分たちで生み出していくようなプロダクトやプロジェクトである。そういった企画たちに携わっていく中で、「我々は何から手を付けるべきか」ということを考えるようになった。日記ついでに文章にまとめておく。

結論から言えば、何か企画を始めるにあたってまず手を付けるべきことは、「この企画はいったいどんな熱量を持っており、我々はこの企画の何に惹かれて集まったのか」をメンバー全員で確認するということである。自分たちが今まさに始めようとしている企画は自分たちや自分たちの周りをどう変えるのか、自分はこの企画が実現すれば何が嬉しいのかを確実に明確にし、忘れぬようにする必要がある。

企画が上手くいかず、志半ばで挫折せざるを得ない場面は多い。その原因は様々である。資金の不足、人員の不足、ライバル企画の存在……数多くの原因が考えられるが、僕が最も恐れる挫折の原因は「飽き」である。

人間は思いのほか飽きやすい。刺激が無くなれば一瞬で人間は飽きていく。ほとんどの企画は、飽きられた瞬間終了する。飽きるのは企画を提供される側だけではない。企画を提供する側、すなわちここで言う「我々」も、「人間は飽きる」という運命から簡単には逃れることはできない。

人間を飽きさせないためには刺激が必要である。企画における刺激とは、企画そのものが持つ熱量に他ならない。企画を提供する我々は、企画自身が持つ熱量を常に外部に提供し続けなければならない。刺激の供給が企画の被提供者を飽きさせないために必要であるのは言うまでもないが、刺激の供給は企画の提供者である我々を飽きさせないためにも必要である。企画の持つ熱量を適切に扱っている間は、我々は決して飽きることはない。

その形式は企画の種類によって様々であるが、企画にはリリースがある。リリースとは、我々が準備してきた企画を外部に公開することである。例えば企画がサービスの開発であればまさに言葉そのままの意味のリリースであり、イベントの企画であればイベントの開催がリリースに当たる。

我々はリリースに際し、その時点で我々が提供できる「企画の最も味の濃い部分」を提供しなければならない。我々がリリースに際して考えるべきことは「今回の我々のリリースは企画の持つ熱量を全力で伝えられているのだろうか」という一点に限る。「この部分は実現が簡単だからリリースしてしまおう」などといったことは考えてはいけない。そんなことをしてしまえば、人は一瞬で飽きる。飽きられた企画の再起は容易ではない。

例えば Twitter を開発することを考えてみよう。Twitter を完成させるためには、やらなければいけないことが山のようにある。ユーザの認証が必要である。クールで素敵な UI デザインも必要だろう。ダイレクトメッセージも欲しいところだ。さて、我々は何から手を付けるべきだろう?お察しのとおり、我々は今挙げたいずれの機能の実装も初めに手を付けるべきでは無い。Twitter のコアは「誰もが気軽に思ったことを投稿できる」という部分にある。これを実現するのは非常に簡単である。web ページを 1 枚作り、ど真ん中にテキストの入力欄を配置するだけで良い。これで「企画の最も味の濃い部分」の実装は完了した。さて、次に味の濃い部分はなんだろう?せっかく投稿したのだから、投稿された文章はぜひとも見たいところだ。では、投稿された文章を全てページ上に掲出するようにしよう。これもまた、その時点での「企画の最も味の濃い部分」の実装である。この時点ではクールな UI はおろか、ユーザの概念すらない。だが不思議なことに、 Twitter の面白さを最低限理解できる企画になっている。これは確実に Twitter のコア機能、「Twitter の持つ熱量」を実装できているからである。

我々は企画の被提供者を飽きさせないため、そして我々自身が飽きないために、刺激的なリリースを出し続ける必要がある。そのためには、企画の提供者である我々が企画が持つ熱量を決して忘れないことが必要である。そして、今我々が提供できる「企画の最も味の濃い部分は何なのか」を常に考え、実装し続ける必要がある。熱量の低い部分の実装はひとまず後にしとにかく熱量を追い求め続ける姿勢こそが企画を成功させるのではないかと、僕は思う。

うんこと僕

うんこ。

今日はうんこの話をしようと思う。

うんこの話と言っても、人前で漏らしたなどという強烈な話題は無く、さらに言えばこの投稿にはオチすら無いことを始めに断っておく。

うんこと僕の付き合いは長い。かれこれ21年半もの間、僕はうんこを生み出し続け、うんこは僕から生み出され続けてきた。

長い付き合いと言っても、21年半まったく同じようにうんこと付き合ってきたわけではない。僕の人生にはうんことの付き合い方を見直さざるを得ない時期があった。

高校を卒業するまでは、僕とうんこの付き合い方は一般人のそれと何ひとつ変わらなかった。朝登校する前に5分ほどトイレに籠もり、生産する。朝の生産が上手くいかない日もある。そんな日でも、夕食を食べた後ほどには下腹部から「脳よ、そろそろ生産を」という命令が飛んだ。男子高校生らしい、極めて健康的な生産生活である。僕の高校は生徒の人間レベルが比較的高く、休み時間にトイレの個室に入ったとしても変な目で見られるようなことは皆無だった。だが、高校のトイレの個室に入った記憶はほとんど無い。朝晩のどちらかに通常生産ができているので、入る必要が無かったのである。

そんな平和な生活が終わったのは、第一志望の大学に落ちてから数カ月後だった。浪人生という社会の底辺に落ちぶれた僕は、毎日電車で2駅、15分ほどかけて予備校に通うことになった。はじめの1, 2ヶ月は特に何の変化も無かった。しかし予備校生活にも慣れ始めた初夏の頃から、だんだんと僕とうんこの関係性は崩れていった。

関係性の変化はある事件から始まった。ある日の数学の講義の時間、僕は急にうんこをしたくなった。しかし講義は佳境であり、家で解けなかった問題の解説が今まさに始まろうとしているところである。うんこに行きたい。下腹部は既に悲鳴を上げている。だが解説は聞きたい。浪人して云十万円を親に支払わせておきながら、聞くべき解説を聞かずにうんこをしているのは何と遺憾なことであろうか。僕は我慢することにした。今から思えば異常な精神状態である。全力で括約筋に力を込め、シャープペンシルの芯も折れよと言わんばかりの筆圧で板書を取る。強すぎる空調が効いている教室で、ただ一人ダラダラと嫌な汗をかいている。何とか講義が終わった。友人よ、頼む、今は話しかけないでくれ。全力疾走でトイレにダイブしたいところだが、そんなことをすれば衝撃で内容物も全力疾走してしまう。皇族のようなにこやかな笑顔でトイレに向かう。風圧で死人が出るほどの勢いでトイレのドアを叩き閉め、どうにか事なきを得た。予備校が間借りしている、遠鉄百貨店9階のトイレがオアシスと化した瞬間である。

この事件は、僕にうんこの怖さを刷り込ませるのに十分すぎた。僕はこの事件を境に、トイレに行けない状況を極度に恐れるようになった。しかし僕は浪人生である。50分の授業が、1日最大10回訪れる。大教室のど真ん中の席に50分座り続けるのは、「トイレ行けない状況過敏症」の僕にとって過酷な状況である。休み時間にはもちろんトイレに行く。スッキリした、もう大丈夫だ。これ以上出るものは無い。教室に着席する。講義が始まる。脳の奥底でうんこの悪魔が「はい、今から50分あなたはトイレに行けません」と囁いてくる。うるさいうるさい、さっきトイレに行ったばかりだ。出るものは無いんだ。でもあれ、今トイレに行ったら出る気がする。というかトイレ行きたくなってきた気がする。なんか下腹部ムズムズしてきた。トイレ、トイレに行きたい。講義終了はまだか――。

講義のたびにトイレに行きたくなる症状は、しばらく続いた。うんこを我慢するツボを研究(手の甲の薬指と中指の間を強く押すと良い)したり、足の組み方によって状況を緩和できないか試行錯誤(左足を上にして深めに組むと良い)したりもした。根本的な解決を目指し、実家至近のかかりつけ医にも掛かった。診断は予想通り「過敏性腸炎」。医者からは「ストレス要因を取り除くことが必要ですね」と言われた。しかし浪人生からストレス要因を取り除けばただのニートである。結局、処方された薬を服用するのみで、根本的な解決には至らなかった。最も酷い時期には、通学のための2駅+出発駅の合計3駅すべてでトイレに入るという快挙を達成したりもした。電車という空間も講義教室と同様に「トイレ行けない状況」の最たるものだったのである。一緒に通学していた友人も、なぜこいつは出発前にしておかないのかと訝しんだことであろう。だがしかし、僕は出発前にもトイレを訪れ、対策には万全を喫しているのだ。もはや僕にできることは無いのである。

それでも、「数学うんこギリギリ事件」から日が経つにつれ、徐々に症状は鎮静化していった。と言っても元に戻るには程遠く、うんこ行きたい欲求をどうにか忘れる方法を身に着けた程度の改善である。

「数学うんこギリギリ事件」から3年以上が経ち、僕は大学生として生活している。しかし未だにトイレに行けない状況ではトイレに行きたくなる。大学から東京へと伸びるつくばエクスプレスは、区間快速の停車駅全てでうんこをしたことがある。友人とドライブに行くときは出発前に30分トイレで準備をする。大学でもあらゆる場所のトイレ事情を把握している。生活圏内では3C棟のトイレが最高である。できれば4階まで足を伸ばしたい。

僕とうんこの深い付き合いは今後も続いていくのだろう。僕が急にトイレに立ってなかなか帰ってこなくても気にしないでほしい。心の大腸を鎮めるためにお腹をさすっているだけであるはずだ。

「明日は多分北千住あたりでうんこだな」そんなことを考えながら今日も眠りに就く。

主専攻実験enPiTで少し幸せになれるTips #1

こんにちは。

このブログが授業の中で触れられた結果、Twitterの新規フォロワー2名と授業参加者からの白い目を獲得しました。

僕は元気です。皆様いかがお過ごしでしょうか。

まえがき

主専攻実験enPiTの1日目が終了しました。enPiTではRuby on Railsを用いてアプリケーション開発を行いますが、数十名で同時に開発となると様々なハマりポイントが出てきます。

僕はRails上級者では決して無いのですが、気になったものをいくつかピックアップして解説を試みます。

目次

PostgreSQLが上手く動かない

PostgreSQLはデータベースシステムです。enPiTでは、Herokuへのデプロイを想定してローカル環境でもPostreSQLを動作させることになっています。以下では、Homebrewを用いてPostgresQLをインストールした前提で話を進めていきます。

まず、PostgreSQLの状態を確認するためには psql コマンドを実行してみると良い です。

$ psql

出てきたエラーメッセージを読んでみましょう。

パターン0:rails newでコケる

以下のようなエラーが出ることがあります。

An error occurred while installing pg (1.1.4), and Bundler cannot continue.
Make sure that `gem install pg -v '1.1.4' --source 'https://rubygems.org/'` succeeds before
bundling.

これはPostgreSQLが正常にインストールされていないことに起因します。下記コマンドでPostgreSQLをインストールします。

$ brew install postgresql

先ほど rails new コマンドは途中で終了してしまっています。続きを再開するために以下のコマンドを入力しましょう。

$ bundle install

パターン1:PostgreSQLが起動していない

PostgreSQLを使う場合、バックグラウンドで起動されている状態にしなければなりません。

psqlコマンドを実行したときに以下のようなエラーが発生した場合、PostgreSQLが起動していない可能性が高いです。

psql: could not connect to server: No such file or directory
    Is the server running locally and accepting
    connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

以下のコマンドでPostgreSQLを起動することができます。

$ brew services start postgresql

パターン2:データベースが存在しない

これはenPiTの資料でも説明されています。以下のような画面が出た場合、データベースが作成されていない可能性が高いです。

以下のコマンドを実行してデータベースを作成しましょう。

$ rails db:create

Railsが起動しない

$ rails s

がうまく動かない場合です。

パターン1:他の何かが起動している

rails sをしたときにエラーが大量に出るはずですが、その一番下に注目してください。

`initialize': Address already in use - bind(2) for "::1" port 3000 (Errno::EADDRINUSE)

というようなエラーが出ていた場合、他の何かが起動している可能性が高いです。Railslocalhost:3000というIP + ポートで起動しようと試みたのですが、他のwebアプリか何かが同じIP + ポートで起動しようとしているために起動できずエラーが返ってきます。

根本から対処するためには起動している他のアプリを停止する必要がありますが、その場しのぎ的には、以下のようにポートを変更してあげることで起動できるかもしれません。

$ rails s -p 3030

パターン2:Railsのゾンビが残っている

rails sした後、メッセージ最下部に以下のような文言が出る場合、Railsが死んでも死にきれていない可能性があります。

A server is already running. Check /<your project path>/tmp/pids/server.pid.
Exiting

エラーの本来の意味は「既に他のサーバが起動しているぞ」です。まずは既にRailsサーバを立ち上げていないか確認しましょう。

Railsが立ち上がっていないにも関わらずエラーが発生することも稀に起こります。本来、Railsはサーバを終了するときに /<your project path>/tmp/pids/server.pid を削除するはずなのですが、強制終了した際など、何らかの拍子にこれが残ってしまうことがあります。

この場合、手でこのファイル(/<your project path>/tmp/pids/server.pid)を削除すればOKです。

Railsでエラー画面が出る

rails sは問題なく実行できるものの、エラー画面が出る場合です。エラーには無数の種類があるので解説を省きますが、エラー画面の見方を説明します。

エラー画面は、上の図の番号順に見ていくと良いです。①で解決できれば儲けもの、④まで見ても解決しなければ厳しい戦いです。

①にはエラーのタイトルが書かれています。上記の例では ActiveRecord::NoDatabaseError となっていますね。どうやらデータベースが存在しないことがエラー原因だと分かります。パッと見て解決しない場合、タイトル文字列をそのままGoogleで検索してみましょう。かなりの確率で解決に繋がるはずです。

②にはエラーの概要が書かれています。タイトルを補足する程度の内容です。

③にはエラーを直接引き起こしたコードが示されます。どのソースコードかは、後ほど説明する④の最上行に示されています。注意するべきは、③に表示されるのはエラーを直接引き起こしたコードであり、エラーの原因となったコードではないということです。エラーの原因が掴めない場合、④に目を移します。

④にはエラーを引き起こしたコードから順に、「そのコードを呼出したコード」が列挙されます。エラーを直接引き起こしたコードには問題がなくても、そのコードを呼び出したコードに問題がありエラーが起こることは良くあります。

以上の①から④までを丁寧に見ればエラーは解決することになっています。エラーが解決しない場合、その場で座禅を組んで瞑想を始める他に手はありません。ご愁傷様です。

複数のGitHubのアカウントを使い分けたい

インターンやバイトでGitHubのアカウントを作って使っていたものの、今回新しく別のアカウントを作って開発したいという場合、アカウントを切り替える必要があります。

原則は基本通り、GitHubに指示されるまま下記のようなコマンドを実行すればOKなはずです(コマンド内のURLはサンプルなのでそのままコピペしても動きません)。

$ git remote add origin https://github.com/sample/sample.git
$ git push -u origin master

しかし、人によっては下記のようにエラーが返ってくることがあります。

error: The requested URL returned error: 403 Forbidden while accessing https://github.com/sudame/where2eat.git/info/refs

fatal: HTTP request failed

このエラーが出たら、GitHubの認証が通っていない可能性が高いです。

簡易的には、以下のようにすれば簡単に認証できます。your-github-usernameの部分は自分のGitHubアカウントのユーザ名にしてください。例えば僕なら sudame です。

$ git remote add origin https://your-github-username@github.com/sample/sample.git
$ git push -u origin master

これを実行すると、GitHubのパスワードを聞かれるはずなので、適切に入力してやりましょう。

しかし上記の方法ではGitHubにpushするたびにパスワードを聞かれて億劫です。余裕があればこちらの記事の通りに作業をすればOKです。

最後に

他に「ここみんな詰まってたよね」「死ねRails」などという情報がありましたら @dev_sudame までリプをください。書き足すかもしれません。

FlutterのBLoCパターンについて得た知見

FlutterのBLoCパターンについて得た知見

本稿は、FlutterのBLoCパターンについて調べていく中で得られた知見をまとめたものです。

記事メタ情報

筆者の背景・執筆の動機・対象読者について

筆者の背景

普段はwebの開発に当たることが多く、大規模アプリの開発よりは小規模アプリや準静的ページの開発が多いです。

Androidアプリは高校1年生のころにJava(当時はKotlinなどという言語は存在しなかった……)でほとんど使い物にならない書籍管理アプリを作った程度の経験値です。iOSアプリの開発は未だに未経験です。

今回、webの案件が一段落したので、Flutterを使ってネイティブアプリに手を出してみることにしました。Widgetの使い方やTransition、Animationの使い方は何となく分かったのですが、アプリ内でのデータの受け渡しがよく分かりませんでした。Flutter経験者の友人に相談したところ、「BLoCパターンというアーキテクチャGoogleは推しているっぽいよ」とのことだったので、集中して調べてみることにしました。

執筆の動機

BLoCパターンとは何か」という情報は英語記事を中心に山ほど出てくるのですが、日本語での記事や実際にどう実装すれば良いのかといった記事が非常に少なかったです。

同じことを調べる人の手間を省くためにも、自分的に参考になった記事へのリンクを交えつつ知見をまとめていきたいと思います。

対象読者

BLoCパターンについて調べている、主に初心者が対象読者です。

経験者が満足できるような情報を提供できる自信はありません……。間違っていたり改善できるような知見が書かれていたらぜひTwitter @dev_sudame までリプ or DMお願いします!

目次

前提知識編

Flutterの概要

Flutterとは、Googleが開発しているモバイルネイティブアプリのフレームワークです。Dart言語を用いて記述し、簡単なアプリであればFlutterで書くだけでAndroid/iOSの両方に対応できます。

Flutterの公式ページには次のような説明がされています。

Flutter allows you to build beautiful native apps on iOS and Android from a single codebase.

(訳) Flutterで一つのコードベースからiOSAndroidの美しいネイティブアプリを開発できます。

Flutterの大きな特徴としては、レイアウトもUIコンポーネントも、UIのほぼすべてを Widget を階層的に積み重ねて記述する点が挙げられます。

今回Futterに入門される方は、参考リンクにも挙げたFlutterの効率良い学び方を一読することをオススメします(著者のmonoさんの記事はどの記事も非常に参考になるのでありがたい限り……!)。

本稿はFlutter自体の入門解説は想定していないので、詳しい情報は参考リンクに譲ることにします。

参考リンク

  • Flutter - Beautiful native apps in record time
    • Flutterの公式サイトです。Flutterは公式ドキュメントが(英語ですが)非常に充実しています。基礎的な部分は公式サイトを見ておけば大丈夫だと思います。
  • Flutter YouTube Channel
    • FlutterはYouTubeで大量に解説動画を出してくれています。Google Developersチャンネルから分離したFlutterの公式チャンネルが用意されています。
  • Flutterの効率良い学び方
    • 入門時のロードマップが示されています。初心者向けの記事リンクも充実しているので、Flutterを学び始めたときのハブとして活用させていただきました。

Flutterのデータ/ステート管理

アプリケーション設計で不可欠かつ最も重要なのはステート管理(状態管理)です。アプリケーションが現在どのような状態にあるのかを適切に管理できなれければ、画面への情報の表示も外部との通信もできません。

Flutterには確固たる一つのステート管理法が存在するわけではなく、開発者が各々手に馴染むものを、適材適所で採用するスタイルのようです。公式ドキュメントには以下のステート管理法が紹介されています。

  • setState
  • InheritedWidget & Scoped model
  • Redux
  • BLoC / Rx
  • MobX

それぞれについて軽く説明していきます。

setState

setStateは最も素朴なステート管理法で、Flutter公式の最初のチュートリアルでも使われています。習得は簡単ですが、大規模アプリケーションの開発には向かなさそうです。

InheritedWidget & Scoped model

InheritedWidget & Scoped modelはFlutterに独特のステート管理法だと思います。

InheritedWidgetはFlutterに組み込まれている基本的なWidgetの一つで、子孫Widgetからデータにアクセスさせたいときなどに用いられます。

Scoped modelはFlutterのプラグインとして提供される機能です。ただ、Flutter公式のステート管理のチュートリアルにも用いられており、準公式的な立ち位置にいます。InheritedWidgetと同様、子孫Widgetからデータに簡単にアクセスさせる時に用いられるようです。

Redux

webアプリケーションの開発の際に用いるフレームワークReact」で広く用いられるステート管理法の一つです。こちらに関しては記事が充実しているので説明は省略します。

BLoC / Rx

本記事で詳しく触れていく管理法です。公式ドキュメントでは(なぜか)触れられていませんが、Google I/O 2018で紹介されるなど、Googleが強く推していると見られる実装方法です。

MobX

React界隈を中心にReduxと対比されるステート管理フレームワークです。Reduxよりも手数が少なくリアクティブな実装をできるようですが、僕は使ったことがなく、全然調べられていません。

参考リンク

BLoCとは

BLoCは「Business Logic of Component」の略です。ビジネスロジック(Business Logic)は、Wikipediaによると、こう定義されています。

ビジネスロジック(英: business logic)は、データベース上のデータに対する処理手順といったようなものを指す、ソフトウェア工学的な用語である。

Flutterで言うビジネスロジックは、View(Widget)とModelをつなぐ部分にあります。Viewからデータを受け取り、Modelとやりとりをしてステートの更新をし、Viewにステートの変更を通知する役割を持っています。

Google I/O 2018では以下のような画像で紹介されました。

電波塔のようなアイコンの部分でデータの変更を取得し、スピーカーのようなアイコンの部分に変更されたデータを流し込んでいるようなイメージです。具体的な実装方法については後ほど説明するので、ここでは概念的な説明に留めておきます。

参考リンク

Streamの概念について

さて、Dart言語には言語組み込みの概念として Stream が定義されています。Streamは入り口と出口があり、入り口には sink、出口には listen を用意しておきます。

Stream の役割は入り口と出口を結ぶことです。入り口と出口は別の場所にあって構いません。先に出したビジネスロジックの例で言えば、4本の Stream が使われています。水色の2本は入口がView(Widget)で出口がbusiness logic、紫(?)色の2本はbusiness logicが入口でView(Widget)が出口に設定されています。

補足 Google I/Oで紹介された画像は、business logicが主語の文脈で紹介されています。このため、business logicに入力されるデータをlistenし、business logicから出力されるデータをsinkに流し込んでいるイメージです。逆にView(Widget)を主語に考えれば、business logicへデータを出力することはsinkに流しこむことに、business logicからデータを受け取ることはデータをlistenすることに対応します。

参考リンク

RxDartについて

RxDartはDartサードパーティライブラリです。Dartには上述の通り、デフォルトで強力なStreamクラスを持っていますが、RxDartはさらに使いやすい機能を提供してくれます。

Rx の接頭辞からも分かる通り、RxJavaやRxJSで有名なReactiveXプロジェクトの一つとして整備されているライブラリです。AndroidアプリでRxJavaを使ってリアクティブ開発をしていた方にはRxDartが馴染みやすいかもしれません。

Dart純粋のStreamクラスにはキャッシュ機能が無く、例えば新規にStreamを開通させたときに初期値を設定したりするのが難しいのですが、RxDartを用いればそれが簡単に実現できます。

Rx系ライブラリではSubjectというクラスを使ってデータの受け渡しを行います。Subject.add()でデータを流し込み、Subject.stream.listen()でデータのリッスンが可能です。Subject系のクラスには用途によって様々な兄弟クラスが存在します。兄弟クラスの詳細については参考リンクの解説に詳しいので、そちらに譲ることにします。

RxDartはFlutter開発においても非同期ライブラリでは覇権を握っていると言っても過言ではなく、特に何の説明もなく解説記事に使われていたりもするので、軽く頭に入れておくと良いです。

参考リンク

  • rxdart | Dart Package
    • RxDartの配布ページです。右カラムの中にAPIリファレンスへのリンクがあるので、そちらも参考にすると良いです。
  • Rxで知っておくと便利なSubjectたち - Qiita
    • Rx系ライブラリに共通なSubject系クラスの解説がされています。DartはStreamの機能が標準で強力なため、はじめのうちはSubject系クラスのみ理解しておけば良さそうです。
  • ReactiveX
    • Rx系ライブラリの親元の公式サイトです。Observer/Observableなどの概念の解説があるので、「Subjectって結局なんだよ」と思い始めたら読むと良いと思います。
    • 残念なことに日本語対応はしていません。

実装編

ディレクトリ構成

BLoCパターンを実装した場合、どのようなディレクトリ構成にするのが良いのかは悩みどころです。一般的には、次のような構成を取っていることが多いようです。(というか、僕はこうしています。)

┣ lib
┃  ┣ blocs
┃  ┣ models
┃  ┣ views
┃  ┗ helpers
┣ android
┗ ios

見ての通りですが、blocsの中にはBLoCクラスを入れます。modelsにはModelにあたるクラスを、viewsの中にはViewとなるWidgetを組み合わせたものを入れておきます。helpersの中には、後述するBLoCProviderなどのヘルパー系クラスを入れてあります。

参考リンク

  • felangel/bloc
    • ToDoアプリの例です。このリポジトリでは、viewsscreenswidgetsに分離しています。これもよく見るパターンです。アプリ内のページの数が増えてきたら、こちらの方が管理しやすそうです。
  • vaygeth89/reactive_flutter_todo_database_app
    • こちらもToDoアプリの例です。多少ディレクトリの名前は違いますが、ほぼ同じ構成です。Firebaseやweb API、あるいはデバイスローカルのストレージなど、Flutterアプリ外と通信する場合はrepositoriesディレクトリを設けることが多いです。

BLoCパターンが守るべき条件

BLoCパターンを始めに提唱したのはAngularDartです。

その提唱者によるGithHub issueコメントでは、BLoCを用いる場合には以下の条件を守る必要があるとされています。

  1. Inputs and outputs are simple Streams/Sinks only
  2. Dependencies must be injectable and platform agnostic
  3. No platform branching allowed
  4. Implementation can be whatever you want if you follow the previous rules

簡単に訳してみると以下のようになります。

  1. BLoCの入出力はStreamとSinkのみである
  2. BLoCの依存関係は必ず注入可能であり、プラットフォームに依存してはいけない
  3. BLoC内でプラットフォームによる条件分岐をしてはいけない
  4. 以上のことを守りさえすれば、どのように実装しても構わない

「もちろん、すべてのコンポーネントBLoCを持つ必要はありません。『ある程度複雑な(complex enough)』コンポーネントBLoCを持つべきです。」とした上で、コンポーネント側(Flutterで言えばWidget側)が満たすべき条件としては、以下が挙げられています。

  1. Each "complex enough" component has a corresponding BLoC
  2. Components should send inputs "as is"
  3. Components should show outputs as close as possible to "as is"
  4. All branching should be based on simple BLoC boolean outputs
  1. 「ある程度複雑(complex enough)」なコンポーネントは、対応するBLoCを持つべきである
  2. コンポーネントは入力されたものを「そのまま」BLoCに送るべきである
  3. コンポーネントBLoCからの出力を可能な限り「そのまま」表示するべきである
  4. すべての条件分岐はBLoCからのシンプルな真偽値の出力に基づいて行われるべきである

以上の条件を満たすようにアプリケーションの状態管理を設計すれば、自然とBLoCパターンに則ることになります。

参考リンク

BLoCクラスの実装

BLoCクラスには最低限以下の要素が必要です。

  • 入力用のストリーム
    • View(Widget)などからBLoCに入ってくる方向のストリーム
  • 出力用のストリーム
    • BLoCからView(Widget)などに出ていく方向のストリーム

BLoCでは、入力用のストリームをリッスンし、適切な処理を施して出力用のストリームに流し込みます。

以下に簡単なサンプルコードを示します。ToDoアプリを作ることを考えます。

モデル

ToDoの一つのアイテム(Task)は以下のように定義しておきます。

Taskは titleisCompletedid を持っています。細かい部分はコメントを読んでください。

class Task {
  final String title;
  final bool isCompleted;
  final int id;

  Task({
    this.title = '',
    this.isCompleted = false,
    this.id,
  });

  // 新しいTaskを作るときに呼び出す
  // Taskのインスタンスを更新するのではなく、新しいインスタンスを立てる
  Task copyWith({String title, bool isCompleted, int id}) {
    return new Task(
      title: title ?? this.title,
      isCompleted: isCompleted ?? this.isCompleted,
      id: id ?? this.id,
    );
  }

  // idが同じであれば同じTaskであるとみなす
  // ==演算子の上書き
  @override
  bool operator ==(other) => this.id == other.id;

  // 同一オブジェクトであるとみなすためにはhashCodeを同一にする必要がある
  // ここではhashCodeをTaskのidにすることで解決
  @override
  int get hashCode => this.id;
}

BLoC

モデルに基づいてBLoCを組み立てます。

先に紹介したBLoCの原則の1つ「コンポーネントは入力されたものを『そのまま』BLoCに送るべきである」にやや反しますが、View(Widget)からBLoCに入力情報を送るときは TaskEvent を作って送ります。

TaskEventaction として create update delete の3つを指定でき、それと task を合わせて送出することで「どのTaskに何の処理するのか」を表現しています。

先に説明した通り、TaskBlocクラスには入力用と出力用のストリームがそれぞれ用意してあります。また、それらのストリームを制御する用のプライベートフィールドも定義しています。

なお、TaskBlocクラスはBlocクラスを継承していますが、この事情は後ほど説明します。

// dart lang
import 'dart:async';

// 3rd party packages
import 'package:bloc_provider/bloc_provider.dart';
import 'package:rxdart/subjects.dart';

// self packages
import 'package:sudame_todo_bloc/models/task.dart';

// CRUDアクションの設定(Readは無し)
enum TaskEventAction {
  create,
  update,
  delete,
}

// TaskをC(R)UDする指示を出すイベントクラス
class TaskEvent<T> {
  final TaskEventAction action;
  final Task task;

  TaskEvent({this.action, this.task});
}

// BLoC本体
class TasksBloc implements Bloc {
  // Taskのリスト
  static List<Task> _taskList = [];

  // 入力Streamの制御
  final StreamController<TaskEvent> _inputController = StreamController();

  // 出力Streamの制御
  final BehaviorSubject<List<Task>> _outputController =
      BehaviorSubject.seeded(_taskList);

  // 入力Stream
  Sink<TaskEvent> get setTask => _inputController.sink;

  // 出力Stream
  Stream<List<Task>> get getTaskList => _outputController.stream;

  // Taskを新規で作る
  void _createTask({Task task}) {
    final int _id = _taskList.length;
    _taskList.add(new Task(
      title: task.title ?? '',
      isCompleted: task.isCompleted ?? false,
      id: _id,
    ));
  }

  // Taskの更新
  void _update(Task task) {
    _taskList[task.id] = task;
  }

  // Taskの削除
  void _delete(Task task) {
    _taskList.removeWhere((t) => task == t);
  }

  // 入力Streamのリスナ
  void _taskEventListener(TaskEvent e) {
    if (e.action == TaskEventAction.create) {
      // createアクションだった場合
      _createTask(task: e.task);
    } else if (e.action == TaskEventAction.update) {
      // updateアクションだった場合
      _update(e.task);
    } else if (e.action == TaskEventAction.delete) {
      // deleteアクションだった場合
      _delete(e.task);
    }
    // Taskのリスト更新後、出力Streamに流し込む
    _outputController.add(_taskList);
  }

  // コンストラクタ
  TasksBloc() {
    // 入力Streamをリッスンしてリスナを登録
    _inputController.stream.listen(_taskEventListener);
  }

  // ステートが破棄された場合、Streamを閉じて破棄する
  @override
  void dispose() async {
    await _inputController.close();
    await _outputController.close();
  }
}

正直な話、まだ実務レベルでFlutterを書いているわけではありませんし、それほど知識のインプットもできていないので、上のコードを信じ込むことはせず、あくまで参考程度にしてください。

全体のソースはこちらにあります。

参考リンク

BLoCインスタンスの扱い方

概要

当然ですが、BLoCBLoCだけでは意味が無く、View(Widget)と相互にやり取りすることで機能が生まれます。

BLoCへ情報を送り込むWidgetBLoCから情報を受け取るWidgetは一般的に異なります(同一Widgetで済むならばBLoCパターンにする必要は無いですね)。ここで必要なのは、異なるWidgetから同一のBLoCインスタンスにアクセスするということです。それぞれのWidgetが好き勝手にBLoCインスタンスを立ち上げても、ストリームが異なってしまいますので通信はできません。

異なるWidgetから同一のBLoCインスタンスにアクセスするために考えられる方法は2つあります。

  1. BLoCクラスをシングルトンにする
  2. BLoCインスタンスを一度だけ生成し、インスタンスWidget間で受け渡す

この内、Flutterで良く採用されるのは2です。なぜ1が採用されないのか、僕の知識と調べた範囲では分かりませんでした。ご存知の方はぜひ教えていただきたいです。

2の方法では、Widgetの階層構造の上位でBLoCインスタンスを生成・保持し、下位のWidgetからアクセスすることを考えます。setStateなどでBLoCインスタンスを下位Widgetに順に伝播させていっても良いですが、階層構造が大きくなるとコードが煩雑になり、しかも伝播の流れの中にあるWidgetはその都度再ビルドされていまいます。

そんな状況を解決してくれるのが InheritedWidget です。

InheritedWidget

公式リファレンスには以下のように紹介されています。

Base class for widgets that efficiently propagate information down the tree. (訳) 階層構造の下位に効率的に情報を伝播するwidgetのベースクラスです。

この説明からも、BLoCインスタンスを効率的に階層構造の下位に伝播するのに最適なクラスだという雰囲気が伝わってきます。InheritedWidgetを用いれば、たとえ階層構造が深くなったとしても、下位のWidgetから一発で(O(1)で)情報にアクセスできます。

具体的な使い方として、公式リファレンスの例を見てみましょう。

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  }) : assert(color != null),
       assert(child != null),
       super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FrogColor) as FrogColor;
  }

  @override
  bool updateShouldNotify(FrogColor old) => color != old.color;
}

重要なのはstaticなofメソッドです。ofメソッドの中ではcontext.inheritFromWidgetOfExactType()というメソッドを呼んでいます。inheritFromWidgetOfExactType()メソッドは、ざっくり言うと「親Widgetたちの中で最も近い、指定された型を持つWidgetを返す」機能を持ったメソッドです。やや難しいですが、ここで言う「親Widget」にはofメソッドを定義したクラス(ここではForgColorクラス)も含まれます。従って、FrogColorクラスの子孫WidgetFrogColorクラスのofメソッドを呼び出した場合、FrogColor Widgetそのものが返ることになります。

この動作については参考リンクに詳しいので、そちらを参照してください。

先の例を続けるならば、FrogColor Widgetの子孫Widgetからは以下のようにしてFrogColor Widgetを呼び出すことができます。ここで contextBuildContext 型です。例えば StatelessWidgetbuild メソッド内では BuildContext にアクセスできます。

final FrogColor _frogColor = FrogColor.of(context);

ところで、先の例では ofメソッドは FrogColor Widgetそのものを返していましたが、ofメソッドはWidgetを返す必要はありません。例えば、FrogColor クラスは Color フィールドを持っていますが、これを返してもOKです。以下のような書き方になるはずです。

static Color of(BuildContext context) {
  return (context.inheritFromWidgetOfExactType(FrogColor) as FrogColor).color;
}

BLoCから話が逸れてしまいました。ここまでのInheritedWidgetの動作を踏まえれば、InheritedWidget を親としてBLoCインスタンスを子孫に効率よく伝播させることができます。簡略的には以下のように書けるでしょう(本来はもうひと手間必要です)。

class BlocProvier extends InheritedWidget {
  const BlocProvider({
    Key key,
    @required Widget child,
  }) : assert(child != null),
       super(key: key, child: child);

  // BLoCインスタンスの生成
  final Bloc bloc = new Bloc();

  static Bloc of(BuildContext context) {
    // BLoCインスタンスの提供
    return (context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bloc;
  }

  @override
  bool updateShouldNotify(BlocProvider old) => bloc != old.bloc;
}

子孫クラスからは、下記のようにしてBLoCインスタンスにアクセスできます。

final Bloc _bloc = BlocProvider.of(context);

BLoCインスタンスの適切な生成・破棄

InheritedWidgetを用いることで下位Widgetに効率的にBLoCインスタンスを伝播させることには成功しました。しかしまだ問題があります。それは BLoCインスタンスを適切に生成・破棄する ということです。

使われなくなったWidget(disposeされたWidget)に紐付いているBLoCは、ストリームを適切に閉じてあげる必要があります。ストリームを閉じない場合、Widgetは存在しないのにBLoCは生き続け(当然ストリームも生き続け)、メモリリークやバグが起こる温床となります。使わなくなったものは適切に片付けなければいけません。

Widgetが使われなくなった場合、破棄されるタイミングで State クラスの dispose メソッドが呼び出されます。従って、このdisposeメソッドの中でストリームを閉じる処理を呼び出せば良いことになります。

また同様に、Widgetが使われ始めるタイミングでBLoCインスタンスを生成する必要もあります。これは State クラスの initStateメソッド内で行えば良いです。

実際には動作しませんが、気持ちとしては以下のようなコードになります。

class BlocProviderState extends State {
  Bloc _bloc;

  // initState()内でBLoCインスタンスの生成
  @override 
  initState() {
    _bloc = Bloc();
    super.initState();
  }

  // dispose()内でBLoCインスタンスの破棄
  // Bloc.dispose()メソッドではストリームを閉じる処理などを想定
  @override
  dispose() {
    _bloc.dispose();
    super.dispose();
  }

  // InheritedWidgetを使って下位Widgetに生成したBLoCインスタンスを伝播
  // BlocProviderは前項で出した例のものを想定
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      bloc: _bloc,
      child: MyHomePage(),
    );
  }
}

まとめ

ここまでで、BLoCインスタンスを生成し、下位Widgetに伝播し、破棄することが可能になりました。長くなったのでまとめておきます。

  1. StateクラスのinitStateメソッド内でBLoCインスタンスを生成する
  2. State (StatefulWidget) の子にInheritedWidgetを配置し、子孫たちにBLoCインスタンスを伝播させる
  3. Stateクラスのdisposeメソッド内でBLoCインスタンスを破棄する

以上を実装すれば、大規模なFutterアプリでもBLoCパターンを用いたステート管理が実現できるはずです。

参考リンク

総括

かなりの大長編となってしまいました。

僕がFlutterに初めて触れてBLoCパターンについて知りたかったことをすべて詰め込むという意気込みで書きなぐりましたが、まだ不足している情報があるかもしれません。「あれも入れたほうが良い」「ここでハマった」等がありましたら追記しますのでお声掛けください。

繰り返しますが、私は完全な初心者なので、本稿の情報には誤りが多分に含まれている可能性があります。気付き次第修正を入れますので、こちらもどうぞお気軽にお声掛けください。

お疲れ様でした。

(おまけ) 全参考リンク集

記事内に登場したすべての参考リンクを集めてみました。リンク集としてご活用ください。

はじめてのかいはつがっしゅく(こうへん)

はじめてのかいはつがっしゅく(こうへん)

こんにちは。須田です。

友人が「インターンに行ってきた」という以前の投稿を読んでくれたと言ってくれました。嬉しくなって感想を求めたところ、「確かに満員の通勤電車ってうんこ行きたくなるよな!」と返されました。言われてみればそんなことも書きましたね。僕は元気です。

目次

発端〜1日目

サザエさんの中島くんに野球に誘われるようなノリで開発合宿に誘われた僕。準備やら何やらが何もかもギリギリではあるものの、何とか初日を迎えることができました。

詳しくははじめてのかいはつがっしゅく(ぜんぺん)をご覧ください。

2日目

前日・1日目の夜は「絶対に誰かしら寝坊するんだろうな〜」と思いつつ床に着いたのですが、奇跡的に全員定刻の8時に起床しました。

須田の朝食は前日の残りのカレーです。温かい白米に冷えたカレーを乗せて食べるのはなぜこうも美味しいのでしょうか。

2日目も黙々と開発に当たります。下の写真は友人Mの午前8時53分の様子。友人Mがこの時間に人間的な活動をしているのを目撃するのは数ヶ月ぶりだと思います。すごいぞ開発合宿。

ところで、この開発合宿では友人たちとの割り勘のほとんどを個人間送金アプリを使って行いました。僕、友人U、友人MはKyashというサービス、友人TはLINE Payでのやり取りです。

アプリを使えば1円単位で送金できるので、現金でやり取りするときの「15円持ってる?」「ごめん5円玉無いわ」「あー、じゃあ350円でいい?」「んー、小銭増えるから300円でいいよ」という原始時代から繰り広げられてきた時間の無駄でしかないやり取りから開放されます。

また、財布を出して小銭を探して……という現金でのやり取りよりも圧倒的に手軽に、その場でササッと送金できるため、小旅行で起こりがちな「お金の貸し借りが積み重なってわけが分からなくなる」という事態も起こりません。

参加者が順繰りに現金を使うことで最終的には個人間送金の関係がほぼ均一になり、旅行が終わったら大量に仮想通貨が残ってしまう人がいる、ということもありませんでした。

地味にストレスを感じていた合宿でのお金のやり取りがほとんどストレスフリーになったのは個人的にかなりビッグニュースです。今後もバンバン個人間送金アプリを使っていこうと思いました。

さて、2日目の昼食は友人Uと友人Tの担当です。友人のUの白い服にソースが飛ばないか不安でなりませんでした。

どのメンバーもかなりまともな料理を提供してきます。もはや料理合宿なのか開発合宿なのか分かりません。1人くらい絶望的に料理が下手なメンバーがいたほうがブログの筆が進んで良かったのですが。

午後もそのまま延々と開発です。

2日目の午後あたりから、だんだんと腰が痛くなってきました。椅子は背もたれもクッションも付いた普通のダイニング椅子で、粗悪品だったというわけでは全くありませんでした。ただ、やはりこうも長時間座って作業をし続けると身体にガタが来るようです。開発合宿では椅子がかなり重要であることが身を持って分かりました。

開発はかなり真面目に行われ、ときどきメンバー間でポツリポツリと会話があるものの、それ以外はほとんど話すこともなく淡々と時間が過ぎていきます。

夕方に買い物に行くついでに、気分転換を兼ねて近くの海に足を伸ばしてみました。伊豆らしい、ゴツゴツした海岸です。出向いたのは18時過ぎでしたが、この時間では少し肌寒かったです。

前日、ピザの宅配が到達範囲外であることに絶望した我々(主に僕、というか僕)は、意地でもピザが食べたくなりました。他のメンバーを誘導し、ピザ台を買わせて夕飯はピザにします。

開発合宿の記事なのに飯の写真ばかりです。まあ、開発中の写真などというものはオタクがノートPCに向かって猫背になっているだけで、1mmも絵にならないので仕方がない。

夕飯とともにビールが解禁されるのですが、1日中集中した日のビールは最高に美味いです。ロング缶1本のためだけに1日頑張っていると言っても過言ではありません。

友人Mは成人したてで、ビールはこの合宿がほとんど初チャレンジだった(たぶん)のですが、この飲みっぷりです。成人から1ヶ月経たない友人にビールの美味しさを教えてしまった僕は将来地獄に堕ちるかもしれません。

食後もビールを飲みつつ開発を続け、2日目は25時過ぎに終了しました。

3日目

4日目は帰るのみなので、実質最終日です。3日目も全員定刻に起床。3日間というのはダレずに集中して開発できる最適な時間かもしれません。最終日だからか、普段気さくながらも冷静な友人Uもテンション高めです。

友人Mはテンションが上がりすぎてバターナイフ代わりのプラスチックスプーンをへし折っていました。スプーンが先端だけになってしまい塗りにくそうです。

3日目に入るといい加減腰が限界に近づきつつあります。特に友人Uはかなり辛そうで、ソファーに移ったり少し歩いたりしていました。作業デスクのない環境での集中した作業という面から考えても、開発合宿は3日間が限界かもしれません。3日以上開発合宿を続ける場合は、より意識的に休憩を取り入れないと後半は身体がかなり厳しい状態になると思われます。

開発合宿も後半ということで、各メンバーの開発も佳境に入りつつあります。友人Tはほとんど開発が終了し、友人Mの制作物もだいぶ形になってきています。友人Tは自分が担当している仕事を集中してこなしていたようです。

僕この合宿でFlutterに入門していたのですが、なんとなく雰囲気を掴み始め、はじめてのアプリが少しずつ見えてきてました。Flutterは友人Uが職業として書けるレベルで、今回の合宿ではそれを考慮した上でFlutterに入門すると決めていました。新しい言語やフレームワークに入門する際、分からないことをリアルタイムに質問できる環境があるのはかなり心強いです。

今後も開発合宿に参加するときは、一緒に行くメンバーの強い技術を盗むことを目当てに参加するのも良いと思いました。

3日目の昼食はガストの宅配をお願いしてみました。地図で見るとかなり遠くにあるガストから配達してくれるようです。ガストのお兄さんありがとう。

天気が良かったので2日目の夕方に訪れた海岸に再度向かい、ピクニック気分で食べることにしました。

いい写真です(自画自賛)。この合宿のベストショットかもしれません(自画自賛)。

午後もさらに開発です。

友人Tと友人Mは今回の合宿で制作物を作っていたので、ここで紹介しようと思います。

友人Tは、自宅の鍵をスマホを通して管理するためのキットを開発していました。Raspberry Piを使って制御するとのことで、今回の合宿ではそのソフトウェア部分の開発をしたようです。認証周りで友人Uの助言も受けつつ、3日目の午前にはかなり形になっていたようでした。帰宅後、大学の3Dプリンタを活用しつつハードウェアの開発に移るとのことです。

友人Mは、我々が所属する情報科学類の卒業要件を確認するwebサービスを開発していました。学類が開設している各科目について「未履修」「履修予定」「履修済み」の3つの状態に設定でき、自分が卒業要件を満たしているか、満たしていなければどの科目を取れば満たせるのかを確認できます。帰宅後に僕も試してみたのですが、複雑なことを考えずにグラフィカルに要件を確認できるのでかなり使いやすかったです。ありがとう友人M。友人Mのおかげで僕も卒業できそうです。

さて、3日目の夕飯は僕と友人Tが担当した麻婆豆腐なのですが、メンバー全員が疲れ果てており、写真が1枚も残っていません。この合宿で食事の写真が1枚も存在しないのはこの1食のみです。開発の疲れもあるでしょうが、メンバーどうしの慣れもあるとは思います。このあたりからも「開発合宿3日間が最適説」が裏付けられます。

3日目は食事をしながら手短かに成果発表をして終了しました。

……と、僕はここで思い出しました。翌日の夕方には新1年生向けに学類組織の説明プレゼンをしなければならないということを。死に顔になりながらスライド資料を作り始めます。初版が完成したのは25時半、その後ボソボソと練習して結局寝たのは26時頃でした。

4日目

4日目は帰るのみです。合宿に向かう特急でビールのない特急列車は悲しいということを学習した我々は、伊東駅でしっかりとビールを調達しました。実家が静岡なので帰省時にたまに飲みますが、静岡麦酒、美味い。

この日は一般的には平日も平日の月曜日なので、周囲には朝から優雅にビールを飲んでいる乗客などいません。多少の罪悪感を感じながら飲むビールも最高です。まあ、ビールならいつ飲んでも最高ではありますが。

東京駅で昼食を取り、そのままつくばに帰宅します。

自宅で髭を剃り、新入生向けのプレゼンをすべくダッシュで大学へ。前日26時過ぎに就寝し、当日は朝からビールを入れている僕の身体はそこそこ限界です。こいつが美味すぎる。

新入生の期待を破壊するような雑さ極まりないプレゼンをし(すまん)、逃げ帰るように帰宅してようやく4日間の合宿が幕を下ろしました。

まとめ

開発合宿はかなり楽しいものでした。気のおけない友人たちと行けばストレスはほとんど感じません。旅行気分も味わえましたし、かなりの集中力でガッと作業を進めることができました。他のメンバーも楽しめたようで、1年に1回くらいはこのメンバーで合宿するのも楽しいかもしれないと言い合えたので良かったです。

今回の開発合宿で得た知見は以下です。

  • 開発合宿はいいぞ
  • 椅子・机が良い宿泊先を選ぼう
  • 飯はメンバーで楽しんで作るとGood
  • 割り勘は個人間送金アプリが超便利
  • ビールは美味い

ではまた。

はじめてのかいはつがっしゅく(ぜんぺん)

はじめてのかいはつがっしゅく(ぜんぺん)

こんにちは。須田です。

合宿から帰宅後、クレジットカードが見当たらなくて焦ったのですが、翌日無事に冷蔵庫から発見されました。皆さまいかがお過ごしでしょうか。

目次

発端

ある日突然友、人UからTwitterのDMが。何事かと思いスマホを開くと「開発合宿に行こうぜ」とのこと。開発合宿って「磯野ー!野球しようぜ!」みたいなノリで連絡されるものなんですね。よく聞けば、大学の同じ学類の友人数人で開発合宿を計画しているとのこと。参加予定者も仲の良い人たちだったので二つ返事でOKしました。

最終的に、参加者は僕を含めて4名になりました。全員筑波大学情報科学類の2017年度入学生です。

  • 友人U:Androidアプリエンジニアな人。エンジニアとして引っこ抜かれ、2018年度の1年間休学していた。
  • 友人M:web系で適当にいろいろ作ったり作らなかったりしている人。かわいい。
  • 友人T:何をやってるんだかよく分からない人。かしこい。
  • 須田:僕。

今までこの4人で揃って遊んだりすることはありませんでしたが、お互い仲の良い4人です。技術的には友人Uが頭一つ抜け出ていて、僕を含め他の3人は横並びといったところだと思います。

準備

開発合宿の案件自体は立てたものの、その後誰も準備を始めようとせず、気づけば予定日の2週間前。そろそろヤバいんじゃないのかなー、とうっすら思っていたら、友人Uが合宿先選定をはじめてくれました。

今回は、合宿の宿泊先をairbnbで探すことになりました。各メンバーで2, 3か所候補地を出した後、投票で合宿先を決めます。

……という悠長な話が2週間前に通るはずもなく、候補地の中でたまたま予約を取れた場所を確保しました。(が、結果的には仲間内の投票で2位の場所を確保できました。やったね。)

ちなみに、合宿先の選定基準は

  • コテージか一軒家を貸し切れる
  • 4人余裕で収容できる
  • (大きめのダイニングテーブルなど)机と椅子が人数分ある
  • できれば風呂が大きい場所

というもの。今回は宿泊2週間前にも関わらず、オールクリアの物件を探し当てることができました。奇跡です。

かくして、我々4人は静岡県伊東市富戸で3泊4日の合宿を行えることになりました。

さて、我々も馬鹿ではありません。ドタバタの宿選びで懲りたので、当日の予定は早めに綿密に組むことにしました。

……という夢を見ました。3泊4日の合宿に行くというのに、前日の昼まで翌日の集合場所すら決まっていないとはなんとも平和な話です。

1日目

最終的な集合時刻は前日の19時に決定しました。俺たちはギリギリを攻めるぜ。

つくば駅からつくばエクスプレス北千住駅へ、北千住駅から上野東京ラインで東京駅へ。東京駅からは特急踊り子号で静岡県伊東駅へ向かいます。

旅行先に向かう特急の車内ではビールで乾杯と相場が決まっています。我々もご多分に漏れず乾杯を。

……と言いたいのですが、計画性が絶望的に欠如している我々は東京駅でビールを買うなどという発想に至りません。「なぜあの席の男女は乾杯をしているのに我々にはお茶すら無いのか」「横浜駅の30秒停車で何とか買えないか」などと嘆きあっても意味はありません。極めて健全な旅路となりました。

伊東駅からは伊豆急行に乗り換えて富戸駅に向かいます。ちょうど桜が満開で、車景もきれいでした。

富戸駅着。蛇足ですが、富戸駅静岡県で最も東にある駅だそうです。今回の合宿で静岡県の最東西南北駅を制覇しました。

富戸駅から合宿先までは海に向かって延々と下っていきます。インスタ映えしますね。オタクはインスタをやらない(偏見)ので、合宿中は一回もインスタを開きませんでした。オタクなので。

合宿先に着いた後は、今後3日半の生活について取り決めを行っておきます。今回の合宿では次の2つだけルールを決めました。

  • 昼食・夕食は当番制で、それぞれ2人組で担当する。
  • 活動開始は9時、昼食は13時、夕食は19時。

じゃんけんで食事の担当決めを行い、ルールを決めたらさっそく各々開発に取り掛かります。

今回の合宿は何か同じものを開発するということは無く、各参加者がそれぞれ好きなように好きなものを作ったり習得したりするというスタイルです。開発合宿というよりは、もくもく会スペシャルと言った方が適切かもしれません。

開発中の口慰みにチョコを真ん中に置いていますが、友人Mがモリモリと食べるのでみんな焦って取り合い、2時間ほどで消滅しました。

4人とも根は真面目なので、初日からかなりの集中力で開発に取り組むことができました。いつもと違う場所なのでそれなりに緊張感を持ちつつも、ゆるいメンバーでリラックスした雰囲気があったので、集中力を保つのには最適な環境だったと思います。

宿泊先の家もかなり快適な環境でした。写真にもあるとおり、ダイニングテーブルが4人分作業スペースを確保しても余裕のある広さだったのは非常に高評価です。家は2階建てになっており、1階が寝室、2階がキッチン・リビング・ダイニング・風呂という構造です。寝るスペースと活動するスペースがきちんと区別できたのも、メリハリを付ける上で良い方向に働いたと思いました。

この家は3泊4日で一人あたり15,000円ほどだったのですが、家の広さや設備、清潔さを考えると猛烈にお得だと思います。4, 5人で開発合宿を考えている方には非常におすすめできる物件でした。

ところで、現地に着いてみてから判明したことなのですが、富戸地区には飲食店が無いのはもちろんのこと、ピザの配達エリアからも外れています。我々に残された選択肢は配達時間50分のガストの宅配か自炊のみ。話し合いの結果、3日目の昼に切り札の宅配ガストを召喚することにし、それ以外は自炊することになりました。夕方になり、食材を調達すべく近所のスーパーに向かいます。

(画像はGoogleマップより)

いい味を出している田舎のスーパーです。店は小さいですが、商品はそれなりに充実しており、生活に困ることはありませんでした。ただ、海が近いので海産物が充実しているのではないかと期待したのですが、近所の富戸港は漁を行っていないのか、非常に寂しい品揃えでした。

購入物を持ち帰るべくリュックサックを空にした友人Mは、なぜかその空のリュックサックを宿に置いてきました。我々の計画性の無さも極まってきた感があります。本来レジ袋は有料とのことですが、店員のおばちゃんの「若い男の子だからサービスしてあげる♡」という反応に困る発言により、レジ袋をサービスしてもらって商品を持ち帰ることができました。

夕飯は「合宿と言えばこれだろう」という安直な発想に基づいてカレーにしました。担当は僕と友人Mです。引っ越しの際に2年間開封されなかった包丁とまな板が発掘されるレベルで自炊をしない友人Mに不安を覚えつつも、美味しく作ることができました。なお、ご飯は炊きすぎました。

初日は食事の後も開発が続き、最後の一人が寝たのは25時ほどだったと思います。なかなか充実した初日となりました。

はじめてのかいはつがっしゅく(こうへん)へ続く。

お前らの自炊は自炊しすぎている

お前らの自炊は自炊しすぎている

こんにちは。近所のまぜそば屋に行くと必ず知り合いに会うので、そろそろ自分専用のまぜそば屋が欲しい須田です。皆さまいかがお過ごしでしょうか。

今日は自炊否定派に捧ぐ投稿です。

お前らの自炊は自炊しすぎている

自炊の話題になると、自炊否定派から必ず「自炊って面倒じゃん」「自炊の後の片付けが大変」「自炊してもそれほど節約にならない」という意見が出ます。

うるせ〜〜〜〜〜〜〜!!!!!!知らね〜〜〜〜〜〜〜〜!!!!!!

実際のところ、自炊は面倒ではないし、後片付けも大変ではないし、節約になります。では自炊否定派はなぜ「面倒で」「大変で」「節約にならない」などと言うのか。

そう、お前らの自炊は自炊しすぎているからだ。

自炊しすぎるとは何か

例えば、世の中で一般的に挙げられる「調理が簡単な料理」には、チャーハンがあります。チャーハンは簡単です。ご飯を炊いて、炊きあがったら卵と絡めておいて、ネギを切って、お肉を細かく切って、油を垂らしたフライパンで強火で順番に炒めて、塩コショウをして、鶏ガラスープで味を整えるだけで完成です。

五目チャーハンのレシピ・つくり方 | キッコーマン | ホームクッキング

簡単なわけねぇだろアホか!!!!!!!!!!!

断言しますが、チャーハンは絶対に調理が簡単な料理ではありません。手順が地味に多いですし、何より「強火で炒める」のあたりに調理を失敗する匂いがプンプンします。しかも完成したチャーハンは大抵の場合少しお米の潰れたいわゆる「家庭チャーハン」で、お店の味には程遠いはず。コスパ最悪です。

世に言われる「調理が簡単な料理」の多くは、「(家事に慣れた主婦/主夫が家庭で複数人分まとめて作るのに)調理が簡単な料理」です。我々のようなプロ一人暮らしたちはこの甘言に騙されてはいけません。

チャーハンを作って「やっぱ自炊って面倒だし大変だしコスパ悪いわ」と思った君は大正解です。チャーハンを作るのは自炊しすぎだからです。

チャーハンを作るのも肉じゃがを作るのもハンバーグを作るのも、全て自炊しすぎです。

俺たちは作るために作るのではない、食べるために作るのだ

自炊の目的は2つあります。1つめは料理を作るということ、2つめは腹を満たすためということです。

1つめの目的を持っている人間は本稿の対象読者ではありません。料理をしたくて自炊をしている人間は、手間をかけて美味しいものを作ることに喜びを覚える人種の人間です。勝手に精進してください。

2つめの目的、すなわち腹を満たすために自炊をするという人間は、本稿のターゲットです。繰り返しますが、我々の目的は「腹を満たすこと」であり、「料理を作ること」ではありません。安く、短時間で、面倒無く料理を口にすることができれば目的達成です。

そうであれば、料理の手間は少ないに越したことはありません。チャーハンのように手間の多い料理を作ることは無いのです。作るためにつくるのではなく、食べるために作るという我々には、我々なりの自炊スタイルがあるはずです。

無精者よ、レシピを見るな

料理が下手な人に対するアドバイスとして「はじめはレシピどおりに作れば良いんだよ」は良く言われるものです。料理が下手な人に対するアドバイスであれば、これは全く問題ありません。レシピを見て、火加減や調味料の量などをだんだん身体に染み込ませていけば、いずれ料理は上達します。

しかし、無精者に対する「レシピを見れば楽ちんだよ」というアドバイスはクソです。だいたい、レシピなんてものが存在する時点で、その料理は手間が多すぎます。

「レシピにするまでもないが美味しい自炊」というのは実はたくさんあります。例えば、キャベツを手で引きちぎりってタッパーに詰め、その上に適当な肉を置いて、蓋を軽く乗せてレンジでチンすれば、温野菜+蒸し肉の完成です。調理手順は「キャベツを手でちぎる」のみ。あとは電子レンジが止まるまで踊っていればよろしい。

野菜や肉は、それ自身がかなり美味しいものです。極端な話、何らかの方法で熱さえ通せば美味しくいただけます。味付けは塩コショウでも良いでしょうし、ソースをかけたりマヨネーズをかけたり、時には焼肉のタレなんかも良いでしょう。加熱方法×味付けで、レパートリーは無限に広がります。

無精者よ、レシピを見るな。レシピを見なくても食べられるものは絶対に仕上がります。

ちなみに、この生活に物足りなくなってきたら、あなたは確実にステップアップしています。あなたは既に無精者ではありません。レシピを見て、自分好みの料理にチャレンジしましょう。

焦げるなら焼くな

料理を失敗ポイントで再頻なのは「焦げる」ということです。

ですが、良く考えてください。焦げるのは焼くからです。逆に言えば、焦げるなら焼かなければよいのです。

焼く以外にも調理方法は無限にあります。タッパーに入れて電子レンジでも良いですし、茹でても煮ても良いでしょう。煮るのは面倒な気もしますが、火をつけて放置しておけば良いので実は非常に気軽です。

ものを焼く場合、火加減がシビアに効いてきます。焼くという行為は案外手間が多く、難しい行為です。

もし何かを焼きたい場合、これだけは知っておくと良いということがあります。それは中火の火加減です。中火は「フライパンの底にギリギリ火が触れる」という火の強さです。中火は「フライパンの底にギリギリ火が触れる」という火の強さです。大事なことなので二回言いました。基本的に、中火で焼いていけば焦げることはありませんし、中まで火が入ります。何か明確な理由が無い限り、強火で調理をすることは考えなくてOKです。中火を死守しましょう。

皿を増やすなって言ってんだろ

洗い物を無駄に増やしてはいけません。少しだけ工夫すれば、洗い物の量を劇的に減らせます。

たとえば、切った食材は皿に避難させるのではなく、まな板の端に置いておけば良いです。1人前の料理なら、まな板の上だけでも十分なスペースがあるはずです。どうしても調理中に皿が必要な場合は、お茶碗などの後ほど食べる時に使う皿を調理皿として使うべきです。これだけで洗い物が半減します。

電子レンジが止まるのを待っている間は踊っていれば良いと書きましたが、あれは嘘です。その時間で皿を洗っておきましょう。包丁はまだ使いますか?使い終わった鍋や菜箸はありませんか?空いた時間に順次洗い物を片付けていけば、食後の洗い物は食べる時に使った皿だけになるはずです。

食後に残る皿を増やさないということは平穏な食後生活を送る上で最重要です。

企業努力に五体投地しろ

「自炊する」と言って何もかも自炊することを考える人間がいますが、今は原始時代ではありません。街にはスーパーがあってお惣菜を売っていますし、冷凍食品コーナーにも無数の商品が並んでいます。

本稿の冒頭で「チャーハンは簡単な料理ではない」と訴えましたが、それでもチャーハンを食べたくなったら冷凍チャーハンを買ってきてレンジでチンすれば良いだけです。レンジでチンしてるので立派な自炊です。

これマジで美味い

餃子が食べたくなったら味の素の冷凍餃子を買ってくればよいのです。味の素の冷凍餃子は水すらいらず、フライパンにゴンゴン並べて蓋をして中火で放置すれば完成です。フライパンを使っているので間違いなく自炊です。

これマジで楽

冷凍食品やお惣菜には企業努力が溢れています。一昔前より、味も見た目も食感も数十段アップしています。冷凍チャーハンなど、電子レンジで調理しているのになぜあれほどパラパラになるのか不思議で仕方がありません。

我々は味の素やマルハニチロニチレイフーズ企業努力に五体投地し、ありがたく活用するべきです。

まとめ

  • レシピ化されているような料理を無理に作らず
  • 焦げない火加減だけは理解し
  • 調理中は洗い物をサボることのみ考え
  • 企業努力に五体投地

すれば、自炊は決して大変でも難しくもありません。

文中で紹介した「キャベツ千切って肉載せてレンジでチン」な料理であれば、キャベツ10円+肉200円=210円で定食ができます。どう考えても自炊は安上がりです。

自炊は難しい、面倒と決めつけるのではなく、脳内の自炊ハードルをガンガン下げて、楽しい自炊生活を送っていきましょう。

最後に

これで最後です。