console.blog(self);

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

RubyKaigi2015に行って、Ruby 2.3.0-preview2をインストールした

今年もRubyKaigiに行ってきた!

去年はこんな感じだった。

初日のKeynoteの最中にRuby 2.3.0-preview2がリリースされた。

というわけでさっそくインストールしてみた。

Ruby 2.3.0-preview2のインストール

開発環境はMacで、homebrewとrbenvを利用している。homebrewのインストール方法とかは割愛。まだ rbenv や ruby-build をインストールしていない場合は、こんな感じでインストールする。

brew install --HEAD ruby-build
brew install rbenv

すでにrbenvやruby-buildがインストールされている場合は、こんな感じでアップデートする。

brew update && brew upgrade --HEAD ruby-build
rbenv install 2.3.0-preview2

ruby-buildのインストールやアップデートで --HEAD のオプションを渡す必要がある。これで最新のruby-buildを利用できる。このあたりのことは rbenv/ruby-build に書いてある。

brew upgrade --HEAD ruby-build
brew install --HEAD ruby-build

続いてRuby2.3.0-preview2をインストールする。

rbenv install 2.3.0-preview2

ちゃんとインストールできたか確認する。

rbenv versions
  system
  2.2.2
* 2.2.3 (set by /Users/sada/.ruby-version)
  2.3.0-preview2

よさそう。rbenv globalで利用するRubyのバージョンを指定できる。

rbenv global 2.3.0-preview2

RailsアプリケーションのRubyバージョン変更

Prottでは .ruby-version を利用している。RAILS_ROOTにある .ruby-version のバージョンを書き換える。

2.3.0-preview2

RAILS_ROOTのRubyのバージョンを確認する。

ruby -v
ruby 2.3.0preview2 (2015-12-11 trunk 53028) [x86_64-darwin15]

いい感じ。Rubyのバージョンが変わったので、bundlerをインストールする。

gem install bundler

rbenv rehash したあとに、bundle install で gem をインストールする。

rbenv rehash
bundle install

ProttはRSpecを使っているので、テストを流して問題ないことを確認した。CHANGELOGから問題の起きそうな変更がないことを確認した。 サーバを起動して簡単な動作確認を行ってみましたが、特に問題は起きなかった。

プロダクションに投入するときにはもうちょっとしっかり確認するけど、だいたいはこんな感じ。さくっと動いてよかった。

&.(ぼっちオペレーター)や did_you_mean をちょこちょこ試した。楽しい。クリスマスに正式版がリリースされる予定なので、積極的にバージョンアップしていきたい。

まとめ

Goodpatch は RubyKaigi 2015 のゴールドスポンサー。まだ小さい会社だけど、好きな技術のカンファレンスのスポンサーになったり、こういう活動ができてうれしい。

もちろんRubyエンジニアを募集していて

そのほかの職種も募集している。

興味があったら、気軽にぽちったり、連絡ください。

RubyKaigi 2016は京都で開催される。楽しみ。

Software in 30 Days スクラムによるアジャイルな組織変革"成功"ガイド

「Software in 30 Days スクラムによるアジャイルな組織変革"成功"ガイド」を読了した。

スクラムに関する本だけど、スクラム自体がテーマではなく、スクラムをどうやってチームや組織に適用していくのか、といった内容だった。

目次はこんな感じ。

  • 第1部 なぜ世界のあらゆるビジネスが30日間でソフトウェアを作れるのか
    • 第1章 ソフトウェアの危機:間違ったプロセスは間違った結果を生む
    • 第2章 スクラム:正しいプロセスは正しい結果を生む
    • 第3章 自分でやってみる:パイロット
    • 第4章 私は何ができるのか
  • 第2部 どうやって30日間でソフトウェアを作るのか
  • 付録A 用語集
  • 付録B スクラムガイド
  • 付録C エンタープライズアジリティを獲得するためのプレイブック

勝手に本書の内容を分けてみると、こんな感じだと思う。

本書を翻訳された吉羽さんのエントリに、こんなことが書かれていた。本書の内容が端的に示されていると思う。

本書は、ジェフとケンにとって初の「ソフトウェア開発をしない人向けに書いた」本です。

<中略>

スクラムのプラクティスはそれほど書かれているわけではなく、スクラムの考え方、組織への適用にフォーカスをあてているので、開発チームの方というよりは、マネージャー層以上の方が読まれるといいと思います。

Software in 30 Days スクラムによるアジャイルな組織変革“成功"ガイド 発売のお知らせ | Ryuzee.com

なぜソフトウェア開発プロジェクトは失敗するのか

第1部では、なぜソフトウェア開発プロジェクトが失敗するのか、いろいろな事例をもとに解説されている。

スクラムの本は他にも読んでいたので、この章ではあまり新しい発見はなかった。ただ、いろいろな事例が紹介されているので、スクラムがうまく導入できない状況の人には、参考になることが多いと思う。

チームでのスクラム

第2部では、スクラムをどうやって適用していくかという内容になる。

第5章、第6章では、普通のスクラムの事例が紹介されている。数名のチームでのスクラムの解説で、基本的な考え方やスプリントなどのプラクティスがいくつか紹介されている。

組織でのスクラム

第7章から第10章、ここがいちばん面白かった。複数スクラムチームで、どうやって巨大なソフトウェアを開発していくのか、スクラムを適用すことでどうやって組織を変革していくのか、といったことが、こちらも多くの事例をもとに解説されている。

第7章ではAdobe Premiere Pro CS3からCS5までの開発チームの変化が紹介されていて、そこが読み物としても特に面白かった。

Adobeのチームでは、個別のチームが開発を行いビックバン統合(リリース前にすべてを一気に統合する)でたくさんのバグがでてしまう状況から、スプリントごとにシステムを統合することで安定した開発ができるようになった。

第8章の組織や企業の変革に関する内容で、こんな一文があって印象的だった。

スクラムを組織全体に導入しようとする上級役員がいる場合、組織で最も変革を楽しみ、成功を得やすいのは、その上級役員である。

また第10章ではこんな文章があって、これは短い内容だけど納得した。

組織変革の際には、2つのスクラムチームを作る。

  1. 変革チーム:スクラムを使って組織を変革し、ビジョンを達成する。
  2. 展開チーム:スクラムを使って変革の実業務を行い、変化を引き起こす。

スクラムのやりかた

本文内で、スクラム自体の内容が少ない分、付録で補われている。スクラムガイドはPDFで読むことができる。

少ない量で、スクラムの解説がされているけど、短い分ちょっと分り辛い。スクラムを知っている人にはわかりやすいガイドだけど、スクラム自体を知りたいなら、ほかの書籍を読んだほうがよさそう。

用語集は短いけれど、わかりやすくまとまっていていい感じ。

まとめ

読んでたら、次のプロジェクトのメンバーのアサインや役割とか、こうしたらうまく回るんじゃないかとか、やりたかったあれができるようになるんじゃないかとか、いろいろアイディアがでてきた。

スクラムをちゃんと適用するのはけっこう大変で、自分のチームでもいくつかのプラクティスしか使っていない。もうちょっといろいろやっていきたいところ。

本書では旧態依然な企業が多く紹介されていた。今の会社は組織を作っている途中なので、組織変革というより、組織作りの真っ最中。だからいろいろなことが試しやすい。開発が必要なプロジェクトも多く、これから新しい問題がたくさん出てくると思う。それをうまくこなしていけるよい組織にするために、開発プロセスもちゃんと考えていきたい。

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