console.blog(self);

技術、読んだ本、いろいろ。

React TutorialをES6で書きなおしてみた

できあがったのはこちら。

Reactを触ってみたいと思って、チュートリアルをやってみた。翻訳してくださっているかたがいて、訳もわかりやすくてとても助かった。

チュートリアルでは JSXTransformer.js を使っているので、JSXの変換などを考える必要はない。手軽なんだけど、Reactを使おうとしたらけっきょくコンパイルしなきゃいけない。

コンパイルにはBabelが必要になる。Babel使うならES6も使いたい。ES6の変換は自動的にやりたいし、ファイルはminifyしたいし、minifyしたらsource mapが欲しいし、変更したら自動的にブラウザを更新したい。って感じであれこれやってみた。

Environment

ディレクトリ構成はこんな感じ。

react-tutorial-es6
├── LICENSE
├── README.md
├── comments.json
├── dist
│   ├── app.js
│   └── app.js.map
├── gulpfile.js
├── index.html
├── package.json
├── public
│   └── index.html
├── server.js
└── src
    └── app.jsx

package.jsonはこんな感じ。

gulpfile.jsはこんな感じ。

var gulp        = require('gulp');
var browserify  = require('browserify');
var babelify    = require('babelify');
var uglify      = require('gulp-uglify');
var source      = require('vinyl-source-stream');
var buffer      = require('vinyl-buffer');
var sourcemaps  = require('gulp-sourcemaps');
var filter      = require('gulp-filter');
var nodemon     = require('gulp-nodemon');
var browserSync = require('browser-sync').create();

gulp.task('browserify', function() {
  browserify('./src/app.jsx', { debug: true })
    .transform(babelify)
    .bundle()
    .on("error", function (err) { console.log("Error : " + err.message); })
    .pipe(source('app.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
    .pipe(uglify())
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./dist'))
});

gulp.task('watch', function() {
  gulp.watch('./src/*.jsx', ['browserify'])
});

gulp.task('browser-sync', ['nodemon'], function() {
  browserSync.init(null, {
      proxy: 'http://localhost:3000',
      port: 8000
  });
  gulp.watch(["public/**", "dist/**"], function() {
    browserSync.reload();
  });
});

gulp.task('nodemon', function() {
  return nodemon({
    script: 'server.js'
  }).on('restart', function() {
    setTimeout(function() {
      browserSync.reload();
    }, 500);
  });
});

gulp.task('default', ['browserify', 'watch', 'browser-sync']);

browserifyでES6を変換したり、source map作ったり、minifyしたりする。これだけでもいろいろはまった。

チュートリアルではサーバが必要になる。いろいろな言語でのサーバのサンプルが付いている。Node.jsのサーバ用のスクリプトをnodemonで起動する。サーバ起動後にbrowser-syncを動かす。ここでもいろいろはまった。

ふぃー。

ES6

JavaScriptをES6に書きなおしていく。

import

必要なライブラリの読み込みはimportで行う。bowerは使わないことにした。

これを

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/jsx">
      // Your code here
    </script>
  </body>
</html>

こんな感じにapp.jsにまとめる。

<DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello React</title>
</head>
<body>
  <div id="content"></div>
  <script src="./dist/app.js"></script>
</body>
</html>

importはこんな感じ。JSXTransformer.jsはコンパイルするからいらなくなる。markedはあとで使う。

import React  from 'react';
import $      from 'jquery';
import marked from 'marked';

React.Component

React.createClassはReact.Componentを継承したクラスとして書きなおす。

これを

// tutorial1.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('content')
);

こんな感じにする。他の部分も同様に書きなおす。

// tutorial1.js in ES6
class CommentBox extends React.Component {
  render() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
}
React.render(
  <CommentBox />,
  document.getElementById('content')
);

let

varはletにする。これを

    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});

こんな感じにする。他の部分も同様に書きなおす。

    let rawMarkup = marked(this.props.children.toString(), {sanitize: true});

getInitialState

getInitialStateはコンストラクタに書きなおす。これを

// tutorial12.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

こんな感じにする。

class CommentBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = {data: []};
  }
  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
}

thisのあれこれ

thisが迷子になった。

Ajaxを書きなおす

コールバックをArrow Functionにする。bind(this)が不要になる。これを

// tutorial13.js
var CommentBox = React.createClass({
  // ...

  componentDidMount: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },

  // ...
});

こんな感じにする。他の部分も同様に書きなおす。

class CommentBox extends React.Component {
  // ...

  componentDidMount() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      type: 'POST',
      success: data => this.setState({data: data}),
      error: (xhr, status, err) => console.error(this.props.url, status, err.toString())
    });
  }

  // ...
}

setInterval

こちらもthisが変わってしまう。これを

// tutorial14.js
var CommentBox = React.createClass({
  // ...

  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },

  // ...
});

こんな感じに書きなおす。

class CommentBox extends React.Component {
  // ...

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval( () => this.loadCommentsFromServer(), this.props.pollInterval );
  }
  // ...
}

this.loadCommentsFromServer だとthisが変わって loadCommentsFromServerthis.props.url が見つからなくなるので

    setInterval( this.loadCommentsFromServer, this.props.pollInterval );

こんな感じになった。

    setInterval( () => this.loadCommentsFromServer(), this.props.pollInterval );

こんな感じでもよい。

    setInterval( this.loadCommentsFromServer.bind(this), this.props.pollInterval );

onSubmit

こちらもthisが変わってしまうので、onSubmit={this.handleSubmit}

// tutorial16.js
var CommentForm = React.createClass({
  handleSubmit: function(e) {
    // ....
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

onSubmit={this.handleSubmit.bind(this)} に書きなおす。

class CommentForm extends React.Component {
  handleSubmit(e) {
    // ....
  }

  render() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
}

onCommentSubmit

onCommentSubmitのthisも変わってしまうので onCommentSubmit={this.handleCommentSubmit}

// tutorial17.js
var CommentBox = React.createClass({
  // ...

  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

onCommentSubmit={this.handleCommentSubmit.bind(this)} に書きなおす。

class CommentBox extends React.Component {
  // ...
  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data}/>
        <CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} />
      </div>
    );
  }
}

app.jsx

最終的にはこんなコードになった。

"use strict";

import React  from 'react';
import $      from 'jquery';
import marked from 'marked';

class CommentBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = {data: []};
  }

  loadCommentsFromServer() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: data => this.setState({data: data}),
      error: (xhr, status, err) => console.error(this.props.url, status, err.toString())
    });
  }

  handleCommentSubmit(comment) {
    let comments = this.state.data;
    let newComments = comments.concat([comment]);
    this.setState({data: newComments});
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: data => this.setState({data: data}),
      error: (xhr, status, err) => console.error(this.props.url, status, err.toString())
    });
  }

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval( this.loadCommentsFromServer.bind(this), this.props.pollInterval );
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data}/>
        <CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} />
      </div>
    );
  }
}

class CommentList extends React.Component {
  render() {
    let commentNodes = this.props.data.map( comment => {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
}

class CommentForm extends React.Component {
  handleSubmit(e) {
    e.preventDefault();
    let author = React.findDOMNode(this.refs.author).value.trim();
    let text   = React.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    this.props.onCommentSubmit({author: author, text: text});
    React.findDOMNode(this.refs.author).value = '';
    React.findDOMNode(this.refs.text).value   = '';
    return;
  }

  render() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
}

class Comment extends React.Component {
  render() {
    let rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
}

React.render(
  <CommentBox url="comments.json" pollInterval={2000} />,
  document.getElementById('content')
);

ふぃー。

まとめ

完成したチュートリアルをがっと書きなおしてみたら、いろいろ動かなくて、けっきょく step by step で書きなおした。ES6力が足りなかった…。

実際に手を動かして、たくさんはまって、悩んで、解決していくという過程は大切だと思った。Arrow Functionのthisの動作は知っていたけど、書いてみたらはまった。やっぱり知っていることとできることは違うよね。

Reactさわってみたかったし、gulpもそのうち使ってみたかったし、ES6も試してみたかったから、まあいいんだけど、予備知識なくはじめるのは大変だなぁと思った。とはいえ情報はたくさんあるし(古い情報も多いけど)、2日くらい触れば簡単なことはできるようになる。学習コストよりはメリットのほうが大きい。当然だけど。

前々から思っていたけど、最近のフロントエンドの環境構築はサーバサイドのそれに似ている。Railsの環境構築みたいな。でもいまのフロントエンドの環境構築はいろいろ過渡期で大変だと思う。Railsの環境構築も同じような時期があったけど、ある程度落ち着いてきた気がする。

ちょうどいい粒度の内容で、書いていて楽しかった。やっぱりプログラミングはいいな。

参考URL

融けるデザイン

「融けるデザイン」を読了した。わかりやすく、これまで気づかなかったことに気づかせてくれる、とてもよい本だった。

周りの人たちが絶賛していて、気になっていたけど読んでいなかった。先日のUI Crunch #5の渡邊恵太さんのセッションがとてもよい内容で、イベント後にお話させていただいて、これは読まなくてはと思い購入した。

ちょっと前までWEB+DB PRESSで連載されていて、いまはWebから読むことができる。

印象に残ったところをつらつらと

今この本を読んでいる文字から目を少し離して自分の体がどのように環境と関わっているか見てほしい。環境がどこかで途切れているだろうか?あなたはまぎれもなく環境の中にいるし、知覚や身体は環境を能動的とか受動的とか考えずに利用しているし、利用されてもいる。この状態に気づくべきである。

融けるデザイン P.62

こんなふうに環境をとらえたことがなかった。本を読んでいるとき、本は本であり、椅子は椅子であり、机は机だと思っていた。でも環境という意味では、確かに繋がっていて、途切れていない。本を読むときのコンテキストを考える、というのともちょっと違う気がする。

なにも考えずに、椅子に座り、机に向かい、本を読んでいた。それが環境の中にいるという状態だったのかもしれない。

インターフェイスがなくなることはない。境界の場所が変わるだけである。モノや道具の利用は自己帰属をもたらし、インターフェイスの場所が変わる。ペンを持てばペン先までが身体になり、ペン先と紙で知覚行為=インタラクションが発生する。車を運転すれば、車全体が身体となり、車と外界で知覚行為が発生する。

そして、知覚と行為=インタラクションが生まれるところで体験は生まれる。自己へ帰属した新しい道具が世界の知覚を拡大し、そこに新しい「可能」を体験する。これが身体拡張の原理である。

融けるデザイン P.69

インターフェイスの場所を考えるというのは面白い。ペンのインターフェイスは、手とペンが触れ合うところではなくペン先になる。でもペン自体の重さやバランス、手とペンが触れ合うところの感触で、ペン自体の使いやすさも変わっていくる。

最終的なペンの使いやすさは、やっぱりペン先と紙との間で決まるのだろう。インタラクションが発生するのはペン先と紙の間で、それが体験になる。こういうことを、これまで考えたことはなかった。

面白いのは、私たちのその印象表現である。私たちはそれを「ひっかかり」として感じたり、もどかしさを感じたり、「重い」といった表現をする。一般的なディスプレイと一般的なマウスで、触覚的なフィードバック機構がないにもかかわらず、「触覚的な」感覚を体験する。

融けるデザイン P.118

この文章を読んでいて、最近のMacBookに搭載されているフォースタッチを思い出した。 マウスカーソルが思い通りに操作できないときに重いと感じる。マウスカーソルの情報は視覚的に得ているけど触覚的に感じる。これに対しフォースタッチでは、トラックパッドをクリックすると凹んでいないにも関わらず、触覚的なフィードバックによって押したような感覚を得られる。これからは触覚的なフィードバックによる新しい体験が増えていくのかもしれない。

ちなみにSafari9ではフォースタッチを取得するeventが追加されている。

Force Touch Trackpad Mouse Events

Safari’s new mouse event property, webkitForce, provides events and force information from Force Touch Trackpads. See Responding to Force Touch Events from JavaScript for an introduction to Force Touch Operations.

Safari 9.0

なにに使えるのかさっぱりわからないけど、これから面白い利用例がでてくるのかな。

なにより、自己帰属感は多くの道具屋機械で検討できる軸であるし、画面の中のいわゆるバーチャルと読んでいる対象の軸としても検討できる普遍性の高いものなのだ。私たちは時折「やっぱりアナログがいい」と言うことがあるが、こういったアナログの感覚の良さは自己帰属感にあるとも言える。自己帰属感というキーワードは、このように今まで説明がつきにくかった何かに触れた時に生まれる感覚や感触に新しい視点を提供し、考察の幅を広げてくれるのだ。

融けるデザイン P.200

この文章で自分の感じていたことが、とてもわかりやすく説明されていた。KindleiPhone電子書籍を読むことがあるけど、やっぱり紙の本が好きだった。電子書籍と紙の本で、自分は紙の本に対してより自己帰属感を持っていたのだと思う。

これは読書の仕方にも関連すると思う。同じ小説を繰り返し読んで、いくつかの本については、どのあたりになにが書いてあるのか、そこで使われている書体も覚えていたりする。こういった感覚は、紙の本ならではのものだし、そこから紙の本に対し自己帰属を感じていたのだと思う。

もちろん、そうではない本も多く、電子書籍のほうが読みやすい、使いやすいと感じることもあった。自己帰属感の高い読書や書籍を考えてみると、なかなか楽しい。これからはそういったことを意識しながら本を読んでみたい。

まとめ

本書のタイトルを見たときに、なにが「融ける」のだろうか、と思った。おそらく融けるのは境界か、境界の位置だと思う。境界とはインターフェイスそのもので、境界が変化し、新しく生まれる。本書では、それらをどうデザインするのかを考えるために「自己帰属感」というキーワードをベースにさまざまな事例や検証結果が解説されている。

本書で紹介されている事例は、書籍のサポートサイトにまとまっているし、渡邊さんのサイトではより多くの内容が紹介されている。

「融けるデザイン」では、言葉にしようとも考えたことのないけど確かに体験したことのある感覚が、とてもわかりやすく文章になっている。例えばこんな感じ。

自分の手足はいつもどうだろうか。制御できていると思っているのではないだろうか。しかし逆ではないだろうか。つまり制御できているからこそ、「自分の」手足ではないか、ということだ。そう捉えることができれば、投げたボールは、制御できる範囲で身体といえるのではないだろうか。

融けるデザイン P.91

文章からすっとイメージできた。文意を理解しやすいから、文章が自己に帰属している感覚を得られたのかも。

デザインと技術はどちらも重要だけど、技術によってなにかができるようになって、そのなにかをより洗練させるためにデザインが必要になる、ということがよくある。例えば、スマートフォンがなければレスポンシブ・ウェブ・デザインもなかったと思う。技術とデザイン、その繰り返しがずっと続いている。

より洗練されたデザインやインターフェイスを支えるために、エンジニアとしてなにができるのかを考えるのは楽しい。難しいけど。タイトルにデザインとあるけど、エンジニアこそ参考になる内容だと感じた。


融けるデザイン ―ハード×ソフト×ネット時代の新たな設計論
渡邊恵太
ビー・エヌ・エヌ新社 (2015-01-21)
売り上げランキング: 9,535

余談

内容と本文で使われている書体が合っている気がして、とても読みやすかった。たぶん筑紫明朝だと思う。書体のやわらかい感じと、「融ける」っていう言葉が合っている気がする。

ヘーゲルは<自己意識> というものを規定し、人間はただ単に自己と客体を離ればなれに認識するだけではなく、媒介としての客体に自己を投射することによって、行為的に、自己をより深く理解することができると考えたの。それが自己意識」

「ぜんぜんわからないな」

「それはつまり、今私があなたにやっていることだよ、ホシノちゃん。私にとっては私が自己で、ホシノちゃんが客体なんだ。ホシノちゃんにとってはもちろん逆だね。ホシノちゃんが自己で、私が客体。私たちはこうしてお互いに、自己と客体を交換し、投射しあって、自己意識を確立しているんだよ。行為的に。簡単に言えば」

海辺のカフカ

自己と客体の投射と交換。

半年のふりかえり

あっというまに半年が過ぎた。今年もあと半分。ふりかえり。

ダイジェスト

1月に入社した。フロントエンドもサーバサイドもがりごり書いていこう思っていた。ちゃんと初心が残っているのがよい。

いろいろ書きつつインフラも見つつ、デプロイやコードチェックをしながら毎日を過ごしていたらあっという間に1ヶ月が過ぎた。

そのうちシステム全体のいろいろなことを任され、コードを書く時間がちょっと減ってきて、なんだか変だなーと思っていた。そして2月中旬にこれまでメインで開発していたエンジニアの方が退職すると聞いて、びっくりするとともにちょっと納得した。

まあ、いろんなことがあるよね、と思いつつ、3月以降の業務はマネージメントが中心になった。以前はまったく楽しくなかったマネージメントが、このチームではけっこう楽しかった。チームのエンジニアはみんな優秀な人たちで、自分は現在のチームに足りない部分を補っている感じがした。とはいえなかなか大変だった。

その後、大きな機能を2つリリースして、ちょっと落ち着いて、マネージメント以外の技術的なこともあれこれしながら日々を過ごしている。

毎日があっという間に過ぎていく。もう2年くらい経った気がする。

開発のあれこれ

ちょっとまえに、取材してもらった。どうやって開発しているか、よくまとまっていると思う。

Prottではこんなものを使ってる。最先端とはいかないけど、けっこうモダンな開発環境だと思う。

この半年、チームでいろいろなことやってきた。大きなリリース。

Prottは正式リリースから10ヶ月くらい経った。ちょっとずつたまってきた技術的負債を、ちょっとずつ返済するため、いろいろバージョンアップしている。セキュリティのhotfixにも対応している。

これまでは機能開発を優先していたけど、いまはパフォーマンスチューニングを頑張っている。

Railsのレイヤーではbulletやstatsprofを使って、測定と改善をしている。インフラもSPDYいれたり、チューニングしたり、バージョンアップしたり。New RelicやMackerelを使ってサービス全体の計測も行っている。フロントエンドはChrome Developer ToolsでTimelineみたり、Profileとったり、HARとったりして計測と改善をしている。

自分自身がコードを書いたり作業したものは少なくて、チームメンバーが対応してくれている。僕だけでは、これだけのことをとてもこなせない。チームや会社を巻き込んで、こういったことに取り組んでいけるのがとてもよい。継続的なメンテンナンスの重要性を説明して、それを理解してもらえている。

マネージメントのあれこれ

プロダクトマネージャー、プロジェクトマネージャー、プロジェクトリーダー、とかマネージメントをする立場の人の呼び方はいろいろあるけど、自分にとってはスクラムマスターかアーキテクトがしっくりくる。

入社したときは12名くらいだったチームが、いまは18名くらい。半年で1.5倍になった。

これまで経験したプロジェクトでは、エンジニアが増えるのはあまり嬉しいことではなかった。プロジェクトのスケジュールを守るために、帳尻を合わせるために、頭数としてエンジニアを追加するという意図が明確だったから。

でもいまはエンジニアが増えるたびに、チームとしてできることが増えている。メンバーが増えたら、できることが増えるようなチームにしたいと思っている。

こんなことを考えながら、マネージメントしている。

相手がエンジニアでもデザイナーでもセールスでも、マネージメントってそれぞれの人が最高のパフォーマンスを発揮できる環境を作ることだと思っている。メンバーには得意なことを存分にやってもらう。それ以外のことを引き受けるのがマネージャーの仕事になる。かなり大変で、とても難しいけど。

もちろん大変なことはそれなりにある。いろいろなものが揃っていないけど、無いものを嘆いてもしかたないし、ある程度わかって入社しているし、足りないなら揃えていけばいいと思っている。

6月から毎週金曜日を bug fix & document day にしている。 金曜日にバグを潰したり、ドキュメント書いたりすることを推奨している。特に決まりはないけど、いい感じにバグを潰して、ドキュメントを書いている。未修正のバグはたまっていくし、開発しているけどドキュメントがあまりないし、いろいろ揃っていなかったから、揃えていきたかった。

こういうことを考えて、実行していけるのは楽しいし、やりがいもある。

まとめ

僕はデザインの大切さを意識するし、経営層は技術の重要性を理解してくれる。そしてお互いに品質を大切にする。そこで不毛な議論がなく、根底の部分での認識にずれが少ない。こういった認識が通底しているのはとてもやりやすい。

自分の技術者としてのバックボーンがマネージメントを支えていると思う。いまはあまりコードを書いていないので、技術的な成長は鈍化している。これまでの貯金を使っている感じ。

でも貯金が減ったら、また貯めればいいじゃないか、と思っている。好きな技術調べて、勝手にコードやblog書いて、また勉強すればいい。そうやってエンジニアとしてやってきたんだし。

いまは、いましかできないことをやっている感じがある。プロダクトととも成長するというより、プロダクトを成長させたい。

アラン・ケイのこの言葉をたまに思い出す。

未来を予測する最善の方法は、それを発明することだ

The best way to predict the future is to invent it.

アラン・ケイ - Wikipedia

この先どうなるのかさっぱりわからないけど、試行錯誤を続けながらよいサービスを作りたい。

We are hiring!!

いろんな職種で募集している。

一緒にProttのサーバサイドを担当してくれる人が来てくれるとうれしい!興味があればぜひ。

おまけ

もう半年たったら年末になる。いったい何をやっているのか想像もつかなくて楽しい。