YAPC::ASIA Tokyo 2015に参加してきた #yapcasia

YAPC::ASIA Tokyo 2015

2015年8月22日に開催されたYAPC::ASIA Tokyo 2015に参加してきました。1日目は参加できず2日目のみの参加です。なお僕は初参加で、かつYAPCは今回で最後らしいです。

セッション

yapcasia.org

スピーカーのNathan LeClaireさんはDocker Machineの中の人。Polyglotとは複数言語、複数ツールを扱う人のことを指す。

今まで、エンジニアが新しい技術を学ぼうとするとき、毎回開発環境の方法を学ばなければならず、それが新しいユーザ参入の障壁となっていた。特に今のフロントエンド界隈は6ヶ月で新しい技術が生み出され(AngularJS,React.js,Grant,Gulp,etc..)、そのたびに環境構築に学習コストを払うのは辛い。

Dockerの魅力はそういった参入障壁をなくし、誰でも簡単に開発を始められるようにすること。ユーザの島と技術の島の橋渡しをする存在ということでした。

なお、質疑応答でDocker.incの中の人はほとんどがboot2dockerを使っているとのこと。

yapcasia.org

speakerdeck.com

Docker3兄弟とは、

  • Docker Machine
  • Docker Compse
  • Docker Swarm

のことを指す。(勝手に呼んでいる)

これにDocker Toolboxが加わり4兄弟になった。Docker Toolboxは3兄弟 + VirtualBox + GUIツールの全部入り。

Docker3兄弟のおかげでDockerがゆるふわに使えるようになった。開発環境では十分利用可能で、プロダクションに使うにはまだ課題があるがロードマップが提示されている。

yapcasia.org

togetter.com

歴代の実谷委員長が集まり、今までのYAPCの思い出話から、イベント運営に関するノウハウが語られました。以下内容を箇条書き。

  • 入場者数は300人から2000人
  • スポンサー数は6社から60社
  • ワイエーピーシー?ヤップク?ヤップシー。
  • ✕ 「ヤップシーアジア」◯ 「ヤップシーエイジア」
  • ライチタイムをリジェクトしたらTwitterが「お腹すいた」で溢れた
  • トーク観るのは素人
  • LTはYAPC発祥(Asiaではない)
  • 高橋メソッドもそこで生まれた
  • 大学を会場にしたいときはヒエラルキーを理解する

yapcasia.org

www.slideshare.net

mozaic.fmのJxckさんによるHTTP2のお話。

HTTP2はすでにRFCになっており、策定するフェーズから使うフェーズになっている。既にいくつかのHTTP2実装が世に出されている。

HTTP1.1はシンプルな分、これ以上速度をあげることに限界がきていた。Keep AliveやFile Concatなどの実装を駆使して対応してきたが、これらはバッドハックであり、ビルドシステムなどが複雑化していく。

HTTP2は1つのコネクション上でストリームを多重化することで、3 way handshakeが1回で済み、パフォーマンスの一番美味しいところを維持できる。

かつ、セマンティクス(各メソッドの意味とかHTTPステータスの意味とか)は維持され、下位互換も保証される。

我々はどうするべきか?

  • よく知らない/導入しない

HTTP1.1はこれからも生き続けるので問題ない

  • 理解した/導入しない

戦略としてはありだが、今後もパフォーマンスのためにバッドハックをし続けなければならない。どこかで見定める必要あり。

  • よく知らない/導入する

エコシステムの成熟しだいではあり。そもそもHTTPはよく知らなくても動くべきだし、既にクライアント(ブラウザ)は知らないうちに導入されている。(ただ、エンジニアとしてはどうなの...)

さいごに

特に何を聴くか考えずに来たけど割りとDocker寄りになったので自分でも興味があるのだろう。Dockerちゃんと勉強します。

とても素晴らしいイベントだっただけに1日しか行けなかった悔しさとこれで最後という悲しみがあります。実行員会の方々、スピーカーの方々、その他運営に関わった方々には大変感謝しております。ありがとうございました。

Photos

diffコマンドでファイルではなく標準出力を比較する(Process Substitution)

いざ使おうとして良く忘れるのでブログに書いておく。

bashzshのProcess Substitutionを使えば、diffをファイルからではなく標準出力の結果から比較できる。(実際は/dev/fd/に一度ファイルとして吐かれるのだけど)

$ diff <(echo "hoge") <(echo "fuga")
1c1
< hoge
---
> fuga

参考:

http://tldp.org/LDP/abs/html/process-sub.html

『アジャイルサムライ横浜道場 特別編「RSGT再演 くじびきイテレーション改』 に参加してきました #agilesamurai #横浜道場

2015年5月12日に開催された『アジャイルサムライ横浜道場 特別編「RSGT再演 くじびきイテレーション改』 に参加してきました。

yokohama-dojo.doorkeeper.jp

www.slideshare.net

今回は特別回として今給黎さんを講師にお招きし、ゲームを通してスクラムを体験するというワークショップを行いました。細かいルールはスライドの方に書いてありますが、1ターンを1dayとし、1ゲームを1スプリント、場のカードをバックログに見立て、それを見ながら見積もりをします。そして手札のカードを使ってバックログを消化し、最後にふりかえりをするというスクラムの一連の流れがこのゲームに凝縮されています。

これを5セット行いますが、セットとともに追加ルールが加わります。加わるルールは以下になぞらえられたものでした。

  • 個々人が決められたタスクしかできない(単能工)
  • タスクを協業できる(協業)
  • 個々人の能力が違い、それぞれの能力をチームは把握している
  • 個々人の能力が違い、それぞれの能力をチームは把握していない

これらを通して学んだことは、

  • 決められた仕事しかできないと、スプリント内で消化できるタスクが制限される
  • 優先度が高くコストが重いタスクでも、協業できれば(タスクを分割すれば)確実に消化できるようになる
  • 個々人の能力をチームが把握していれば、見積りの精度が増す。
  • 逆に、個々人の能力をチームが把握していないと、アクシデントが起こる可能性が増す(見積もりを予測しにくくなる)

といったことでした。事実、僕らのチーム*1は能力を把握していないときの見積もりのブレが1、把握しているときが5と顕著に現れました。

さいごに

今回のワークショップはとても白熱し、たのしくスクラムが学べたかと思います。 講師をしていただいた今給黎さん、スタッフの方々、参加された方々ありがとうございました。

*1:チーム名は「Dieふごう」にしました

kawasaki.rb #23 に参加した & 寄稿した #kwskrb

2015/4/23に行われたkawasaki.rbに参加してきました。

今回はこちらのブログに内容をまとめるのではなく、kawasaki.rb公式ブログに寄稿という形をとりました。以下がその記事です。

kawasaki.rb #023を開催しました #kwskrb

大体の内容は上の記事に書かれているのでこちらには個人的な内容を書きます。 寄稿する流れとなったのは以下のツイートから

と、kawasaki.rb発起人のchezou氏がMacを会社に置いてきてしまったので、代わりにパーフェクトRubyの電子版が入っていた僕のMacで進行することにしました。初めて進行をしましたが、予想以上に大変でした。傍から見ててもてんやわんやしてたと思います。

というわけで今回試したコードの履歴は僕のpryのhistory上にあったので、ついでに寄稿することにしました。

終わってから

そういえば、今回のところは以前読書会した「なるほどUnixプロセス」で深く理解してたはずなのに、あまり気の利いた発言できなかったなぁとか終わってから思ってました。良い本ですので読んだほうが良いです。紹介は以下から。

norizabuton.hateblo.jp

最後に

kawasaki.rbの皆様ありがとうございました。次回もよろしくお願いします。

Fluxを修得するためにkriasoft/react-starter-kitをコードリーディングした

発端

実践的なFluxの勉強するためになにかいいサンプルコードないかと漁っていたら、KriaSoftがGithub上で公開しているreact-starter-kitが自前でReact.jsでのページネーションを実装してたりいろいろと参考になったのでコードリーディングしてみました。

Github

kriasoft/react-starter-kit · GitHub

DEMO

React.js Starter Kit

ちなみにGithubのdescriptionにも書かれていますが、このstarter-kitはBabel (ES6), JSX, Gulp, Webpack, BrowserSync, Jest, Flowとモダンなツールが全部入りなので、あらゆる方面から勉強になりました。

なお、これから貼るコードは抜粋なので、ソースコードを落としてエディタで見るかDevelopmentToolを実行しながら確認した方が良いかと思います。

src/templates/index.html

<!doctype html>
<html class="no-js" lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title><%- title %></title>
    <meta name="description" content="<%- description %>">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="apple-touch-icon" href="apple-touch-icon.png">
    <link rel="stylesheet" href="/css/bootstrap.css">
    <script src="/app.js"></script>
  </head>
  <body>
    <!--[if lt IE 8]>
      <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
    <![endif]-->
    <%= body %>
    <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
    <script>
      ...
    </script>
  </body>
</html>

まずはindex.htmlから。GoogleAnalyticsのコードなどを除けばこれだけ。 bodyは変数となっており、ここはserver側で定義しています。

src/server.js

// The top-level React component + HTML template for it
var App = React.createFactory(require('./components/App'));
var templateFile = path.join(__dirname, 'templates/index.html');
var template = _.template(fs.readFileSync(templateFile, 'utf8'));

server.get('*', function(req, res) {
  var data = {description: ''};
  var app = new App({
    path: req.path,
    onSetTitle: function(title) { data.title = title; },
    onSetMeta: function(name, content) { data[name] = content; },
    onPageNotFound: function() { res.status(404); }
  });
  data.body = React.renderToString(app);
  var html = template(data);
  res.send(html);
});

サーバ側はexpressで動作しています。ReactのComponentであるAppを定義し、renderToStringで描画しています。Isomorphic感が出ています。

src/components/App/App.js

  render() {
    var page = AppStore.getPage(this.props.path);
    invariant(page !== undefined, 'Failed to load page content.');
    this.props.onSetTitle(page.title);

    if (page.type === 'notfound') {
      this.props.onPageNotFound();
      return React.createElement(NotFoundPage, page);
    }

    return (
      <div className="App">
        <Navbar />
        {
          this.props.path === '/' ?
          <div className="jumbotron">
            <div className="container text-center">
              <h1>React</h1>
              <p>Complex web apps made easy</p>
            </div>
          </div> :
          <div className="container">
            <h2>{page.title}</h2>
          </div>
        }
        <ContentPage className="container" {...page} />
        <div className="navbar-footer">
          <div className="container">
            <p className="text-muted">
              <span>© Your Company</span>
              <span><a href="/">Home</a></span>
              <span><a href="/privacy">Privacy</a></span>
            </p>
          </div>
        </div>
      </div>
    );
  }

実際にbody部に描画されるのはApp.jsのrender内に定義されています。 また、ページネーションを実現しているhandleClickもこのApp.jsで定義されています。

  handleClick(event) {
    if (event.button === 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.defaultPrevented) {
      return;
    }

    // Ensure link
    var el = event.target;
    while (el && el.nodeName !== 'A') {
      el = el.parentNode;
    }
    if (!el || el.nodeName !== 'A') {
      return;
    }

    // Ignore if tag has
    // 1. "download" attribute
    // 2. rel="external" attribute
    if (el.getAttribute('download') || el.getAttribute('rel') === 'external') {
      return;
    }

    // Ensure non-hash for the same path
    var link = el.getAttribute('href');
    if (el.pathname === location.pathname && (el.hash || link === '#')) {
      return;
    }

    // Check for mailto: in the href
    if (link && link.indexOf('mailto:') > -1) {
      return;
    }

    // Check target
    if (el.target) {
      return;
    }

    // X-origin
    var origin = window.location.protocol + '//' + window.location.hostname +
      (window.location.port ? ':' + window.location.port : '');
    if (!(el.href && el.href.indexOf(origin) === 0)) {
      return;
    }

    // Rebuild path
    var path = el.pathname + el.search + (el.hash || '');

    event.preventDefault();
    AppActions.loadPage(path, () => {
      AppActions.navigateTo(path);
    });
  }

根幹は最後にあるAppActions.loadPageで、その前のif文は対象としないリンクを弾くためのガード節です。

休憩

Actionが出てきたので、Fluxについておさらいしておきます。

f:id:Peranikov:20150420154018p:plain

よくあるFluxのフローをまとめた図です。今見てきたところはViewの部分で、これからViewからのイベントハンドリングを管理するAction、各Storeにメッセージを発信するDispatcher、データを管理するStoreへと流れていきます。コードリーディングする際にも、一連の流れに沿っているFluxの考え方の恩恵が受けられますね。

src/actions/AppActions.js

  loadPage(path, cb) {
    Dispatcher.handleViewAction({
      actionType: ActionTypes.LOAD_PAGE,
      path
    });

    http.get('/api/page' + path)
      .accept('application/json')
      .end((err, res) => {
        Dispatcher.handleServerAction({
          actionType: ActionTypes.LOAD_PAGE,
          path,
          err,
          page: res.body
        });
        if (cb) {
          cb();
        }
      });
  }

App.jsから呼んでいたloadPageの抜粋です。Dispatcher.handleViewActionを呼んだ後に、サーバへGETでリクエストし遷移先のbodyを受け取り、Dispatcher.handleServerActionを呼んでいます。サーバ側より先にシンプルなDispatcherのコードを見てみます。

src/core/Dispatcher.js

let Dispatcher = assign(new Flux.Dispatcher(), {

  /**
   * @param {object} action The details of the action, including the action's
   * type and additional data coming from the server.
   */
  handleServerAction(action) {
    var payload = {
      source: PayloadSources.SERVER_ACTION,
      action: action
    };
    this.dispatch(payload);
  },

  /**
   * @param {object} action The details of the action, including the action's
   * type and additional data coming from the view.
   */
  handleViewAction(action) {
    var payload = {
      source: PayloadSources.VIEW_ACTION,
      action: action
    };
    this.dispatch(payload);
  }

});

Dispatcherの仕事は単純で、各StoreにActionからの通知を横流しします。ここでは、Serverからのデータか、Viewからのデータかでメソッドを使い分けています。Dispatcherはpayloadと呼ばれるオブジェクトでactionオブジェクトをラップし、データ元などの情報を付加してdispatchメソッドでStoreへ通知します。

Storeで通知を受ける前に、Actionから呼んでいたサーバ側の処理を覗いてみます。

src/server.js

//
// Page API
// -----------------------------------------------------------------------------
server.get('/api/page/*', function(req, res) {
  var urlPath = req.path.substr(9);
  var page = AppStore.getPage(urlPath);
  res.send(page);
});

ページのbodyを返すサーバのAPIでは、なんとStoreのコードを呼んでいました。

src/stores/AppStore.js(Serverからの呼び出し)

var pages = {};

...

if (__SERVER__) {
  pages['/'] = {title: 'Home Page'};
  pages['/privacy'] = {title: 'Privacy Policy'};
}

var AppStore = assign({}, EventEmitter.prototype, {

...

  /**
   * Gets page data by the given URL path.
   *
   * @param {String} path URL path.
   * @returns {*} Page data.
   */
  getPage(path) {
    return path in pages ? pages[path] : {
      title: 'Page Not Found',
      type: 'notfound'
    };
  },

...

AppStoreのgetPageでは、与えられたパスに対するオブジェクトを返しています。存在しないパスに対するハンドリングも行っており、ここでルーティングの役目を担っています。

サーバ側はこれで終わりなので、StoreがDispatcherからの通知を受けたところから再開します。

src/stores/AppStore.js(Dispatcherからの通知)

AppStore.dispatcherToken = Dispatcher.register((payload) => {
  var action = payload.action;

  switch (action.actionType) {

    case ActionTypes.LOAD_PAGE:
      if (action.source === PayloadSources.VIEW_ACTION) {
        loading = true;
      } else {
        loading = false;
        if (!action.err) {
          pages[action.path] = action.page;
        }
      }
      AppStore.emitChange();
      break;

    default:
      // Do nothing

  }

});

AppStoreの下部の方でDispatcher.registerを実行しており、これによりDispatcherからのdispatchメソッドによって通知が受け取れるようになっています。

通知を受けた後に処理をしているのはActionTypesがLOAD_PAGE、つまりAppActionのloadPageメソッドからの通知を受けた時で、かつPayloadSourcesがVIEW_ACTION以外、つまりSERVER_ACTIONの時ですね。(VIEW_ACTIONの時にもloading = trueと代入していますが、この変数を実際に使っている箇所はありませんでした。)

AppSotreのpagesオブジェクトに、パスと対応するbodyの情報をセットし、emitChangeを呼んでStoreの仕事は終わりです。

  /**
   * Emits change event to all registered event listeners.
   *
   * @returns {Boolean} Indication if we've emitted an event.
   */
  emitChange() {
    return this.emit(CHANGE_EVENT);
  },

emitChange内部ではただEventEmitterのemitを呼んでいるだけですね。 で、後はViewがStoreからの通知を受け取ってレンダーするだけ…かと思っていたのですが、このサンプルではView側でStoreの通知を監視していませんでした。そこで、まだ解説していなかったコードを見ていきます。

src/components/App.js(AppActions.loadPage後)

handleClick(event) {
...
event.preventDefault();
    AppActions.loadPage(path, () => {
      AppActions.navigateTo(path);
    });

handleClick内でAppActions.loadPageを実行後、コールバックでAppActions.navigateToを実行しています。

src/actions/AppAction.js

navigateTo(path, options) {
    if (ExecutionEnvironment.canUseDOM) {
      if (options && options.replace) {
        window.history.replaceState({}, document.title, path);
      } else {
        window.history.pushState({}, document.title, path);
      }
    }

    Dispatcher.handleViewAction({
      actionType: ActionTypes.CHANGE_LOCATION,
      path
    });
  },

AppActionsのnavigateToでは、pushState/replaceStateで履歴を操作していました。その後はactionTypeをActionTypes.CHANGE_LOCATIONとしてDispatcher.handleViewActionを実行しています。そして、CHANGE_LOCATIONをどこで監視しているのか調べていくと、

src/app.js

function run() {
  // Render the top-level React component
  let props = {
    path: path,
    onSetTitle: (title) => document.title = title,
    onSetMeta: setMetaTag,
    onPageNotFound: emptyFunction
  };
  let element = React.createElement(App, props);
  React.render(element, document.body);

  // Update `Application.path` prop when `window.location` is changed
  Dispatcher.register((payload) => {
    if (payload.action.actionType === ActionTypes.CHANGE_LOCATION) {
      element = React.cloneElement(element, {path: payload.action.path});
      React.render(element, document.body);
    }
  });
}

と、app.jsのrunメソッドの中でDispatcher.registerを呼んでいました。 この後はCloneされたAppのrenderが呼ばれ、ここで描画が完了します。

最後のここの部分に関しては、Fluxに従うのであればStoreから通知を受けたViewで処理するのではないのかと思いましたが、アプリケーション全体に関わる部分だからapp.jsで処理をしているとかでしょうか。

ちなみに、App.js内でpopstateをaddEventListenerすることでブラウザバックにも対応していました。

...
componentDidMount() {
    window.addEventListener('popstate', this.handlePopState);
    window.addEventListener('click', this.handleClick);
  }
...

handlePopState(event) {
    AppActions.navigateTo(window.location.pathname, {replace: !!event.state});
  }

わからなかった英単語まとめ その1

英語の記事を読んだりした中でわからなかった単語をまとめるメモ

curiosity

a 好奇心

[用例] from [out of] curiosity 好奇心から、物好きに

b <...したい> 気持ち

[用例] She satisfied my curiosity to know the reason. そのわけを知りたいという私の好奇心を満足させてくれた

satisfied

a 満足した、満ち足りた

[用例] a satisfied customer. 満足した客

detect

a 見つける,看破する; 発見する; 検出する.

[用例] detect a lie. 嘘を見破る

sophisticate

a <人を>世間慣れさせる, 洗練させる

b <機械などを> 複雑化する、精巧なものにする

kawasaki.rb #22 に参加してきました #kwskrb

2015/3/25に開催されたkawasaki.rb #22に参加してきました。

Doorkeeper

Kawasaki.rb #022 - Kawasaki.rb | Doorkeeper

Togetter

togetter.com

パーフェクトRuby読書会

以降のコードはRuby 2.2.0で試しています。

3-5-12 ::を使ったメソッド呼び出し

Time::now

ドットでなく::でメソッドを呼ぶことができます。ちなみにrubocopに怒られます。

3-5-13 メソッド定義の取り消し

undefを用いることでメソッドの定義を取り消すことができます。

def greet
  puts 'hora'
end

greet # => "hora"

undef greet

greet # => undefined local variable or method `greet'

ところでこれっていつ使うんだろうという話題になり、例えばRails上でデザイナと協業する際、View側で代入メソッドを呼ばせたくない時などにundefして置く、という話を聞きました。

3-5-14 メソッドに別名をつける

aliasを用いることでメソッドに別名を付けることができます。

alias greet puts
greet "hi" #=> "hi"

また、元のメソッドをundefしても別名としたメソッドは影響を受けません。

undef puts
greet "ho" #=> "ho"

ここでは以下のコードが話題となりました。

def foo
  raise
end

alias bar foo

bar #=> `foo': unhandled exception

と、呼び出しているのはbarですが例外はfooで発生したというメッセージになっています。

さらに、

def foo
  raise
end

alias bar foo

undef foo # fooをundefしてみる

bar #=> `foo': unhandled exception

と、fooはundefしているにも関わらずfooの名前が表示されるという現象があります。

3-6-1 文字列の入出力

puts、print、sprintfなどの説明がありました。sprintfは、そのショートハンドであるString#%の方がよく使うという話をしました。

sprintf("%04d", 1) #=> "0001"
"%04d" % 1         #=> "0001"

String#%のパラメータは配列でも渡せます。

"%03dパーセント中の%02dパーセント" % [100, 50] #=> "100パーセント中の50パーセント"

なんとハッシュでも渡せるとのことでした。

"あなたは%<num>08d人目のお客様です" %  {num: 10} #=> "あなたは00000010人目のお客様です"

なんか「名前付きキャプチャ」っぽい!ということで、名前付きキャプチャの話題となりました。

/(?<num>\d+)/ =~ "10人目"
num # => "10"

個人的にはこの機能を知らなくて、いきなり変数にバインドされるというのはいくらRubyでも衝撃的でした。 ただ、

m = /(?<num>\d+)/.match("10人目")
m[:num] #=> "10"

とすればシンボルでアクセスできるので安全に取り扱うことができます。

今回の読書会はここまででした。今回はなんとも濃い内容でした。

セッション

受託開発でAnsibleを導入した話 @Peranikov

www.slideshare.net

会社でAnsibleを試して少し知見が溜まったのでしゃべりました。 触ったと言っても1日ちょっと試しただけですが、それでもきちんと動くものにはなりました。これもAnsibleのシンプルさのおかげだと思っています。

今回のスライドではtaskについてしか触れていませんが、実際にはvars(変数定義)やtemplate(テンプレートエンジンでconfigファイルを動的に変更)などの機能もあるので実運用にはかなうんじゃないかなと思います。

なにより、PostgresモジュールがAnsible自体に組み込まれているのは個人的には凄く大きいです。(外部のPlaybookに依存したりしなくて済む)

Gemの作り方 ~BacklogのAPIを利用して~ @kon_yu さん

speakerdeck.com

Gemまだ作ったことないです... RubyGemsの作り方から公開とハマりどころまで丁寧に解説されているので、実際作ることがあったら参考にしたいです。

おわりに

kawaski.rbのみなさま、NTT-AT様ありがとうございました。次回もまたよろしくおねがいします。

ちなみに函館グルメはラッキーピエロハセガワストアのやきとり弁当、カール・レイモンのソーセージがお勧めです。