console.blog(self);

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

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

融けるデザイン

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

周りの人たちが絶賛していて、気になっていたけど読んでいなかった。先日の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

余談

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

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

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

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

海辺のカフカ

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