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

TL;DR

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

本文

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

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

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

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

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

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

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

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

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

須田さんがよくやるweb開発構成

お久しぶりです。

最近ビールが好きすぎていい加減太りそうな須田です。将来はビールと結婚したい。

久々にブログを書く気分なので、2限分の空きコマを使って普段自分がよくやるweb開発の構成を紹介する記事を書いていこうと思います。

サーバー

VPS(レンタルサーバー)

バックエンドで何かを動かしたいようなときは、最近はVPS(Virtual Private Server)を使っています。サーバーはVultrという格安サーバーをレンタルしています。このVultrというサービスはなかなかバケモノで、最低構成ならば月額350円でレンタルできます。遊びに使うだけならばコスパ最強サーバーだと思います。

僕のVPSではdokkuというものが動いており、その上で各サービスが走っています。dokkuは個人のサーバー上でHeroku的なサービスを動かせるという代物で、面倒なNginxやApacheの設定、SSLの設定、その他諸々をよしなにやってくれます。dokkuについては別日に記事にしたいところです。

Netlify

Netlifyは静的ページの配信サービス(ホスティングサービス)です。静的なHTML/CSS/JavaScriptの配信をするときは100%お世話になっています。

SSLの設定を自動でやってくれたり、無料プランでも独自ドメインを使い放題だったりと、かなり至れり尽くせりなサービスです。

デプロイも自分で手作業でやる必要はなく、GitHubと連携させることでmasterブランチにプッシュしたタイミングで自動デプロイが走ってくれます。デプロイの際にはビルド用のスクリプトを走らせることができるので、例えばwebpackのビルドコマンドを走らせたり、Nuxt.jsのgenerateコマンドを実行したりすることができます。

他のホスティングサービスの代表例としてはGitHub Pagesがありますが、個人的にはNetlifyを強く推します。

バックエンド

Firebase

バックエンドが必要なwebサービスを作るときは、基本的にFirebaseを使っています。

FirebaseはGoogleが手がけるBaaS(Backend as a Service)です。NoSQLなデータベースであるFirestore、ストレージであるStorage、認証システムであるAuthenticationなどを利用可能です。また、Functionという機能があり、サーバサイドNode.jsを書いて動かすことも可能です。

個人のお遊び開発+α程度ならば基本的に無料プランで何とかなりますが、Firestoreの読み書き回数が多いサービスを作るときは従量制で課金しています。例えば、母校の学校祭の待ち時間表示サービスを作ったときは利用者が多かったので課金しました。

Firebaseはドキュメントも非常にしっかりしているので、初心者でもかなり使いやすいと思います。

Ruby on Rails

リレーショナルなデータベースを使いたいときは、Railsで簡単なAPIを組んで使うことが多いです。必ずしもRailsである必要性はありませんが、比較的手に馴染んでいるのでRailsを使っています。

最近のRails(Rails 5以降)にはAPIモードという機能があり、Railsアプリを作るときに

$ rails new <your-app-name> --api

--api オプションを付けて生成すると超簡単にAPIサーバを構築できます。あとはRailsの便利コマンドたちをポチポチ実行し、ルーティングやらユーザ認証やらを付ければバックエンドが完成してしまいます。APIモードと通常モードの違いはQiitaのこの記事が詳しいのでご参照ください。

Node.js

Node.jsでサーバーを書いて直接動かすというのは僕は滅多にやりませんが、やらないわけではありません。

「何かの事情でFirebaseは使えないけどNoSQLを使いたい」という場合、Express(Node.jsのサーバーフレームワーク)+MongoDBを使うことがあります。あんまり好きな構成ではないので、やるときは嫌々です。

フロントエンド

これを読んだあなたは二度とCSSJavaScriptを手書きするな(過激派)

人類はいい加減、手書きのCSSJavaScriptから卒業するべきです。TypeScriptとSass/Scssを使おう、TypeScriptとSass/Scssを使おう、TypeScriptとSass/Scssを使おう(大事なことなので3回言いました)。

TypeScriptとSassを未導入の皆さんは今から5秒以内に下記コマンドを実行してください。

$ yarn global add sass
$ yarn global add typescript

TypeScriptはJavaScriptに目に見える形で型の概念を導入してくれます。型を導入することでエディタの補完がワンダフルに動作しますし、不要なバグを防止することができます。

SassはCSSでは力不足な部分を補ってくれます。例えば変数の宣言が可能だったり、for文を使って繰り返し登場するスタイルを省略して書くことが可能です。

TypeScriptはJavaScriptの、SassはCSSの完全上位互換です。 TypeScriptと称してJavaScriptを書いても、Sassと称してCSSを書いても全く問題ありません。学習コストは実質0です。使わない理由がありません。使いましょう。

超シンプルなwebページの場合

超シンプルなwebページの場合、特にビルドツールなどは使わず、適当にTypeScriptとScssを書きます。例えば以下のような構成。

┣ root
┃   ┣ dist
┃   ┃   ┗ index.html
┃   ┣ src
┃   ┃   ┣ index.ts
┃   ┃   ┗ style.scss

ファイル数の3つの、超シンプル構成です。この状態で下記コマンドを打つと、

$ tsc src/index.ts --outDir dist
$ sass src/style.scss dist/style.css

distディレクトリ内に index.jsstyle.css が生成されます。これを index.html で適当に呼んであげればOKです。

ややシンプルなwebページの場合

ややシンプルなwebページの場合、webページを作成するディレクトリの中で初めに

$ yarn init

コマンドを打ちましょう。こうしてあげればプロジェクト内でNode.jsの便利な外部ライブラリたちを使い放題です。

おおよそ以下のような構成になります。

┣ root
┃   ┣ dist
┃   ┃   ┗ index.html
┃   ┣ src
┃   ┃   ┣ index.ts
┃   ┃   ┗ style.scss
┃   ┣ node_modules
┃   ┃   ┗ [山のようなモジュールたち]
┃   ┣ gulpfile.ts
┃   ┣ tsconfig.json

上記の例では、「タスクランナー」と呼ばれるツールの一つであるgulpを使っています。先ほどの「超シンプルなwebページの場合」では手で tsc コマンドと sass コマンドを実行してTypeScriptとSassをトランスパイルしましたが、プロジェクトの規模が大きくなってくると一々打つのは面倒になります。そこで活躍するのがタスクランナーで、一発コマンドを打てば全て良い具合にやってくれるようになります。

gulpfile.ts にビルド用のスクリプトをモリモリ書いておき、いざビルドをするときは

$ yarn gulp

を打てばOKです。

gulpの詳細については山ほど記事がありますのでそちらを参照してください。今度僕も何か書くかもしれません。(ところでgulp 4からはTypeScriptで直接gulpfileを書けるようになったんですね。素晴らしい。)

tsconfig.json はTypeScript用の設定ファイルです。 tsc コマンドにいちいち長々とオプションを付ける必要がなくなるので、ある程度以上の規模のプロジェクトになったら置いておきたいファイルです。

複雑なwebページの場合

「ややシンプルなwebページの場合」 を発展させていっても全然問題は無いのですが、複雑なwebページの場合、僕はNuxt.jsを使って楽をすることが多いです。

Nuxt.jsはVue.jsアプリケーションを作るためのフレームワークです。通常Vue.jsを使ってアプリケーションを作る場合、ルーティングのためにVue Routerを、状態管理のためにvuexを……、と様々を組み合わせて構築していくのですが、Nuxt.jsではこれらを良い具合に詰め合わせ、よしなに動くようにしてくれます。

Nuxt.jsは基本的にはJavaScriptで書くことになりますが、少し工夫をすればTypeScriptで書くことも可能です。ただ、Nuxt.jsのTypeScript対応はまだまだ発展途上という雰囲気があり、まだ洗練されてはいないなという印象です。

Nuxt.jsの良い点はここで語ると長くなりすぎるので、別の機会に記事を書きたいと思います。

おわりに

サクっとつまみ食い的に書いてしまったので内容が非常に中途半端になってしまいました。

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

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

お疲れ様でした。

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

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

enPiTにがっかりしたので、勢いだけでものすごくイキった何かを始める話

enPiTにがっかりしたので、勢いだけでものすごくイキった何かを始める話

こんにちは。

最近物忘れが激しく、2日連続でレタスを1玉ずつ買ってしまいました。レタスサラダとレタススープとレタスチャーハンな夕飯を楽しんでいます。僕は元気です。

みなさまいかがお過ごしでしょうか。

主専攻実験前夜

僕が通う筑波大学情報科学類では、3年生から主専攻に分かれ、それぞれの主専攻で実験を行います。主専攻実験にはテーマがいくつか定められており、学生たちは自分に与えられた選択肢の中からテーマをチョイスし、1年をかけて2つのテーマの実験をこなします。

主専攻実験には当然主専攻の色が色濃く反映されたテーマが並びますが、いくつかは主専攻を超えた枠組みで実験が行われます。その一つが「enPiT」と呼ばれるものです。enPiTはチーム開発を経験することに焦点を当てた実験テーマです。春学期は学生が各々自らの技量を高めるべく自学に当たり、秋学期にはチームに分かれ、実際にプロダクトを作る実習を行います。秋学期にはアジャイル開発を始め、効果的な問題解決型プロダクトの作り方を実践的に体得できるようです。

大学でのアカデミックな研究よりも実際にプロダクトを作る方が楽しいと感じる僕は、enPiTに飛びつくこととなりました。春学期は自学がメインということで、自分がやりたいことを自由にやれるという点も魅力に感じました。

主専攻実験初日

これから何を学ぼうかな、どんなものを作ろうかな、と多少ワクワクしながら初日の主専攻実験に向かいました。会場の教室に行ってみると、仲の良い友だちや他の学類の顔見知りもいて、雰囲気は悪くありません。

が、想定外は突然訪れるものです。

担当教員 「春学期はハンズオン形式でRuby on Railsを学んでいきます」

マジか。例年は各自でRailsチュートリアルを走破すると聞いていたんだが。講義スケジュールを見てみると、確かに春学期の半分程度が講義やガイダンスになっています。

まあ、この程度は正直ある得るだろうなとは思っていました。例年のRailsチュートリアル自学も、自力でチュートリアルを走破できる人はあまりいないと聞いていましたし、今年から主担当の教員が変わったということも知っていました。

僕は、「俺がはRailsを書けるぜ」とは到底言えませんが、Railsチュートリアルは完走しており、初歩的なチュートリアルはもう不要です。講義は面倒だな、と思いつつ講義スライドを見ていると目に飛び込んできたのがこのスライドです。

ち ょ っ と 待 て 。

待ってくれ。いくらなんでもこれは酷い。「講義形式にするならせめてRailsチュートリアルよりも分かりやすく噛み砕いた内容が良いな」という僕の淡い期待を0.2秒で吹き飛ばす、破壊力のあるスライドです。「ここで衝撃の事実」。本当に衝撃の事実すぎます。このスキル感なら、そこらのRailsが得意な学生に教壇に立ってもらったほうがマシだとさえ思えます。

名誉のために記しておくと、担当教員の方はデータベース関係の研究をされている先生で、情報セキュリティやプライバシ、個人情報保護の面でかなり強力な知識を持っていらっしゃるとのことです。たしかに技術のベクトルは合っていますが、Railsを教えるとなると、うーーん……。

文句ばっかりでも仕方がないのでイキる

文句ばかり言って何もしない人間はクソ野郎です。イキったり目立ったり空回りしたりする人間もまたクソ野郎ですが、個人的には後者のクソ野郎でありたいです。

ということで、ぼくらのenPiT というプロジェクトを(勝手に)始めることにしました。

詳しくはREADMEを読んでいただければよいのですが、要するに、enPiT公式よりも多種多様で充実したチュートリアルを僕らで勝手に作っちゃおうぜ というプロジェクトです。

フレームワークRailsに限らなければ、enPiTの講義で行うチュートリアルレベルの内容なら実装できてしまう学生は少なくないはずです。最近流行りのVue.jsで実装しても良いでしょうし、AndroidアプリiOSアプリとして開発しても良いでしょう。自分の使える言語・フレームワーク・技術でチュートリアルを書くことが目的です。

enPiTの内容に沿わなくても、例えば「gitの使い方入門」や「enPiTのチュートリアルを発展させる」なんていうチュートリアルがあっても面白いと思います。

人が書いたチュートリアルを実践してみるのも面白いでしょう。自分が全く知らない言語・フレームワーク・技術に新しくチャレンジするのも良いですし、自分がよく知っている内容のチュートリアルに文句を付けて改善していくのも楽しみ方の一つです。

「ぼくらの」と言いつつ自分一人の自己満足目的でやるつもりではいます。万が一「面白そうじゃん」と思ってくれる方がいるなら、ぜひ一度READMEを読んでみてください。参加方法なども書いておきました。

最後に思うこと

自由律俳句です。

勢いだけで
  始めたものごとって
    続かないんだよなぁ
          すだめ

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

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

こんにちは。須田です。

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

目次

発端〜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
  • 割り勘は個人間送金アプリが超便利
  • ビールは美味い

ではまた。