詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

ウェブ開発チームのマネジメント:『モチベーション3.0』を読んだ

モチベーション3.0 持続する「やる気!」をいかに引き出すか

モチベーション3.0 持続する「やる気!」をいかに引き出すか

年末なのでウィッシュリストを棚卸しして、『モチベーション3.0』を読んだ。今は開発チームのマネージャ的な立ち位置で働いているので、メンバーに如何に気持よく働いてもらえるか、というのが最近気になっている。

前提

人間をつき動かすモチベーションには三種類ある。

  • モチベーション 1.0: 生存のため、必要にかられて行動を起こす。動物的なもの。
  • モチベーション 2.0: 「これをすればかくかくの報酬がある」「これをしなければかくかくの処罰がある」という予測に基づいて行動を起こす。アメとムチによりコントロールされる。
  • モチベーション 3.0: 自らの内的な価値観に基づいて行動を起こす。

著者はこの本の中で、これまでの社会活動、とくに企業が労働者をマネジメントする仕組みはモチベーション 2.0に基いて進められてきたが、最近の世界的な情勢や研究を鑑みると、これが期待どおりに働かなかったり、逆効果であったりすることが分かってきたのだという。そこでモチベーション 2.0をモチベーション 3.0にアップグレードすべきだ、という話。 ちなみに 2.0 とか 3.0 というのはモチベーションに関する考え方を OS になぞらえていて、OS みたいな抽象的なものが喩えとして使われるのか、となんだか面白かった。

この主張のひとつの証左として、「幼稚園の迎えに遅刻する親に罰金を課すようにしたところ、かえって遅刻が増えてしまった」という面白いエピソードがある(そういえば NHK でもこの話やってた、と思いながら読んだ)。また、内発的動機づけによる行為に対して外発的な動機をあたえるとモチベーションが低下するという話は、この本では名前が出ていなかったけれど「アンダーマイニング効果」と呼ばれる現象らしい。

内発的な動機のためになにができるか

まあ上に書いたことは知ってた、ような話で、いわゆるマネージャ的な肩書を持っている自分としては、じゃあ内発的な動機づけとそこから来る行動のためになにができるか、というのが気になるところだ。筆者によるとこれには三つの柱がある:

  • 自律性
  • マスタリー(熟達)
  • 目的

自律性は自己決定。「何をするのか」「いつするのか」「どのようにするのか」「誰とするのか」を自ら考え、決定し、実行できるようにすることが、個々人のパフォーマンスを高めることにつながる。

マスタリー(熟達)は何か価値あることに上達したいという欲求。これは苦痛でもあり終わりない戦いでもあるが、これは自己充足的で、それ自体報酬となるプロセスでもある。

正直このふたつはインターネットでオープンソース活動なり個人活動をしている開発者(エンジニアやデザイナ)なら実感としてすでに知っているようなことだろう。現在の職場もそういった背景を持っている人びとが多く、この本で時代遅れとされているような、マネージャが部下の時間を時間やタスクを監視し、完全に管理しようとするような働きかたとは程遠い(と思う)。

そこでおそらく最も重要なのが三番目の目的。これは外的な報酬や処罰とは次元を異にする、社会的価値だとか人生の意味だとかいった、いわば高邁な目標だ。そして個人活動では達成できないことを、会社やチームで働くことで達成する意義はここにこそあらわれるはずだ。お給金のため……ももろんなのだけれど、それだけじゃない チームを率い、メンバーの生産性を最大化する責務があるマネージャは特にここに重心をおき、組織とチームの存在理由を十分に浸透させる仕事をする必要があるだろう。そうすれば自律したメンバーのアウトプットと会社の望む方向とのズレも最小化できるはずだ。

どうしてもやらなければならない仕事は

それでも組織が生存するために、つまらないルーチンワークをお願いしなければならないこともある。申し訳ない話だけれどそれでもやっぱりある。そういうときはどうすべきか? という話もこの本には載っていて、こういうことだった:

  • その仕事が必要であることを論理的に示し、
  • その仕事が退屈であることを認め、
  • その進め方にある程度裁量を与える。

また創造的な仕事でも環境が許さず自律・マスタリー・目的を構築できない場合にも、以下のように気をつけるとよい:

  • 仕事が終わったあとに、「思いがけない報酬」を与える。
  • 具体的な報酬よりも、賞賛やフィードバックを。

その他面白かった話

  • アトラシアンのフェデックス・デーの話がおもしろかった。24時間でなにか作って出す(デリバーする)、というもの。今は ShipIt Day と呼ばれてるのだろうか。
  • 偉大な仕事をした人物は、一文で表せるもの。自分を一文で表現すると何か? と問いかけてみる。
  • (子供に)自分で目標を立て、自分で成績表をつけさせてみると、内発的動機づけにつながる。

モチベーション3.0 持続する「やる気!」をいかに引き出すか

モチベーション3.0 持続する「やる気!」をいかに引き出すか

blogsync ではてなブログのエントリをローカルと同期する

http://hatenablog.com/images/theme/og-image-1500.gif

こんにちは、はてなチーフアプリケーションエンジニアの id:motemen です。今日ははてなエンジニアアドベントカレンダー2014 の 22 日目として、はてなブログのエントリをローカルと同期するツール blogsync を紹介します。昨日は id:tarao による gulpで依存関係を考慮した自動コンパイル でした。

はてなブログは CMS としても非常に便利に使えるウェブサービスで、実際Mackerel のヘルプもはてなブログで運用しています。ドキュメントのようなコンテンツはいろいろな人が触り、頻繁に書き換えるので変更履歴を残したいところ、また慣れたエディタで編集したいものですが、はてなブログ単体にそういう機能は当然ありません。そこで個人的に はてなブログ AtomPub を用いて、はてなブログのエントリをローカルのファイルシステムに保存するツールを Go で書いて、使ってみています。いったんファイルに保存してしまえば、Git なり Dropbox なりで編集履歴を残しておくのは容易です。

インストール方法

go get github.com/motemen/blogsync

使い方

設定

まず初めに設定ファイルを書きます。ホームディレクトリ以下の .config/blogsync/config.yaml に、以下のような YAML を置いてください。

motemen.hatenablog.com:
  username: motemen
  password: <API KEY>
default:
  local_root: /Users/motemen/Dropbox/Blog

各項目の意味は次のとおり:

  • キー(例: motemen.hatenablog.com): ブログのドメイン。はてなブログのダッシュボードからブログの設定画面などを開いたとき、URL に含まれる文字列です。技術的には AtomPub API における「ブログID」になります。
    • "default" という名前のキーは特別で、すべてのブログの項目のデフォルト値として扱われます。
  • <blog>.username: そのブログに投稿するはてなユーザの ID。
  • <blog>.password: そのブログに投稿するための API キー。
  • <blog>.local_root: ブログのエントリを格納するパスのルート。

エントリをダウンロードする(blogsync pull

設定が完了したら、以下のコマンドを実行すると当該のブログに投稿しているエントリがその URL ローカルに保存されます。

% blogsync pull <blog>

この際保存されるファイルのパスは、エントリの URL ベースにしたものとなります。blogsync pull motemen.hatenablog.com した結果だとこんな感じになります(分かりやすいように少し省略しています):

/Users/motemen/Dropbox/Blog/motemen.hatenablog.com/
└── entry
    ├── 2014
    │   ├── 05
    │   │   ├── 12
    │   │   │   └── gulp,_TypeScript,_Browserify_で_Chrome_拡張を書く.md
    │   │   └── 14
    │   │       └── datetime-sh.md
    │   ├── 06
    │   │   ├── 01
    │   │   │   └── introducing-ghq.md
    │   │   ├── 03
    │   │   │   └── git-hub-sync-repo-info.md
…

以降は blogsync pull すると、ブログエントリとローカルのファイルをつき合わせ、新しいエントリのみダウンロードされるようになります。

ファイルのフォーマット

エントリのファイルはエントリのメタデータではじまり、空行ののち本文が続く、というフォーマットです:

- Title:   まだmechanizeで消耗してるの? WebDriverで銀行をスクレイピング(ProtractorとWebdriverIOを例に)
- Date:    2014-10-01T08:30:00+09:00
- URL:     http://motemen.hatenablog.com/entry/2014/10/01/scrape-by-protractor-webdriverio
- EditURL: https://blog.hatena.ne.jp/motemen/motemen.hatenablog.com/atom/entry/8454420450066634133

今日はスクレイピングの話をします。…

今のところメタデータの内容は以下の5つ。(ハイフンではじめているのは、Markdown として解釈したとき見栄えがよいようにです)

  • Title: エントリのタイトル。
  • Date: ブログに表示されるエントリの投稿日時。2006-01-02T15:04:05-07:00 といったフォーマットを期待しています。
  • URL: エントリの URL。これは自動的に与えられ、書き換えても効果はありません。
  • EditURL: エントリを一意に区別する URL。名前のとおり、AtomPub の編集用の URL です。
  • Draft: この値が "yes" のとき、下書きとして扱われます。

メタデータをまったく指定しない場合、1行目から本文がはじまるものとみなされます。

エントリを更新する(blogsync push

ひとたびエントリをダウンロードしたら、そのファイルを編集することで記事を更新できます。

% blogsync push <path/to/file>

このエントリの下書きを書いている今なら、こんな感じですね:

% blogsync push ~/Dropbox/blog/motemen.hatenablog.com/entry/2014/12/22/blogsync.md
       GET ---> https://blog.hatena.ne.jp/motemen/motemen.hatenablog.com/atom/entry/8454420450077731341
       200 <--- https://blog.hatena.ne.jp/motemen/motemen.hatenablog.com/atom/entry/8454420450077731341
       PUT ---> https://blog.hatena.ne.jp/motemen/motemen.hatenablog.com/atom/entry/8454420450077731341
       200 <--- https://blog.hatena.ne.jp/motemen/motemen.hatenablog.com/atom/entry/8454420450077731341
     store /Users/motemen/Dropbox/blog/motemen.hatenablog.com/entry/2014/12/22/blogsync.md

ファイルがリモートの記事よりも新しくない場合は、更新リクエストは行われません。

エントリを投稿する(blogsync post

まだはてなブログ側に存在しない記事を投稿する場合は、投稿用のコマンドで記事を投稿します。

% blogsync post <blog> < <path/to/file>

blogsync post は標準入力からエントリの内容を受けとって投稿し、投稿されたエントリに対応するファイルをダウンロードします。その後は新しく作成されたファイルを編集し、push することでエントリの編集を続けられます。

このコマンドでは --title=<TITLE>--draft という引数によって記事タイトルや下書き状態の指定を行えるのでこんな風に雑に、ターミナルから書き始めることもできます…

% blogsync post --draft --title=blogsync motemen.hatenablog.com
さてかきはじめるか…
^D

おわりに

すでに便利な blogsync ですが、常用するにはまだ足りていない点がいくつかあります。

  • 記事 URL を任意に指定できない
  • 予約投稿できない

などなど。もし興味を持った方は、リポジトリを覗いてみてください。

blogsync の開発にあたっては、別チームでありながらはてなブログチームに投げたプルリクエストが小気味よくマージされてリリースされる様子を眺めることができました。はてなでは日々リリースされるプロダクトが大好きなエンジニアを募集しています。

明日のアドベントカレンダーは id:y_uuki です。

Git の特定リビジョンのツリーをファイルシステムのように扱う Go の実装

要件

以下のようなことができればよいものとします。

パスを与えられたとき、

  • そのパスが示すファイルの内容 (open, read)
  • そのパスが示すディレクトリの内容 (opendir, readdir)
  • そのパスが示すファイルまたはディレクトリの情報 (stat)
    • パーミッション
    • 最終更新時刻
    • ファイルサイズ

を得られる。

Git のファイルツリー構造を読む

Git に登場し、われわれが対象とすることができる概念は「オブジェクト」という名前のもとに抽象化されています。通常、直接操作することがあるのはコミットとタグだけですが、残りのツリー(tree)とブロブ(blob)をここでは取り扱うことになります。

ここでは詳しく解説できませんが、簡単に言えばツリーがディレクトリに対応し、ブロブがファイル(の内容)に対応します。コミットオブジェクトは時刻やコミットメッセージの他に、ひとつのツリーオブジェクトへの参照を持っています。適当なリポジトリで git cat-file で様子をうかがってみます。

% git cat-file -p HEAD
tree cfe3cb534d9e626f3cff23583e6cb666b0ebb997 # コミット "HEAD" が指すツリーID
parent 0f1a7c509da2ed307f3a9fcb9ea0907fb8fe3601
author motemen <motemen@gmail.com> 1418313657 +0900
committer motemen <motemen@gmail.com> 1418313657 +0900

init

HEAD が指すツリーが見つかったので、これをさらにたどってみます。

% git cat-file -p cfe3cb534d9e626f3cff23583e6cb666b0ebb997
100644 blob daf913b1b347aae6de6f48d599bc89ef8c8693d6    .gitignore
100644 blob ba5d35222be3983257723ddcd0bf8fcf3ee90cdf    LICENSE
100644 blob 0613648f84ecb1ab9ca6182b46f74539f227ec72    README.md
040000 tree e90529ca501f5001c0706065aebe735bede754be    git

ルートディレクトリの中身らしきものが見えてきました。LICENSE と git に対応するオブジェクトをそれぞれ覗いてみると:

# LICENSE
% git cat-file -p ba5d35222be3983257723ddcd0bf8fcf3ee90cdf
The MIT License (MIT)
…


# git/
% git cat-file -p e90529ca501f5001c0706065aebe735bede754be
100644 blob a9ecb54af0fcb66c64cb9ca814d22887c6dfe115    git.go
100644 blob 1516b582b5010469d02f0d7995d426489ce0cc1d    git_test.go

blob がファイルの内容であり、tree がディレクトリに対応することがわかると思います。古いコミットに対しておなじ操作を行ってみると別のツリーオブジェクトをたどることになり、古いファイルの内容にアクセスできます。ワークスペースの内容が変更されコミットされるたびに、新しいツリーオブジェクトが生成され、新しいコミットはそのツリーオブジェクトを指します。

これで、以下のようにすれば特定リビジョン(コミット)におけるリポジトリの内容を取得できそうだと想像できます:

  • 当該のコミットが指すツリーを得る。
  • ツリーをたどり、深い階層のブロブやツリーを得る。

Git を用いた具体的な実装

git コマンドを通じて、ツリーとブロブによる擬似ファイルシステムを実装することにします。その前にこのオブジェクトの指定方法を知っておくと便利です: <rev>:<path>git cat-file -p HEAD:README.md のようにして使います。特定のリビジョン rev におけるファイルまたはディレクトリ path に対応するブロブ、ツリーを指す記法といったところです。リポジトリルートを指したいときは HEAD: のように指定します。./ などとすると現在のディレクトリからの相対パスになることに注意。

1. ファイルの内容を取得する

ファイルに対応するブロブの SHA1 がわかっていれば、以下のコマンドで内容を取得できます。

% git cat-file blob ba5d35222be3983257723ddcd0bf8fcf3ee90cdf

先に登場した git cat-file -p でもいいですが、上のコマンドであればブロブでないオブジェクトを表示しようとすると失敗してくれます。

2. ディレクトリの内容を取得する および 3. ファイルやディレクトリの情報を取得する

git ls-tree を使います。上で使ったように git cat-file でも同じような出力は得られますが、ツリー的オブジェクトでないものを引数に与えたときエラーになってくれるので、ls-tree のほうがよいでしょう。

ls-tree はそのままだと現在のディレクトリを使用するので、--full-tree で無視させます。また、-l オプションでブロブのサイズも取得しておきます。プログラムから使用するときは -z オプションで NUL 区切りにしておくのがよいです。

% git ls-tree --full-tree -l HEAD:git
100644 blob a9ecb54af0fcb66c64cb9ca814d22887c6dfe115    6385    git.go
100644 blob 1516b582b5010469d02f0d7995d426489ce0cc1d     867    git_test.go

モードの読み方

ls-tree の出力の最初の列がオブジェクトのモードを表しています。これに関して直接的なドキュメントはないみたいだけど、git-fast-importなんかを見るとなんとなく分かると思います。

実際のところ、以下の5パターンしかありません。

  • 100644: 標準ファイル
  • 100755: 標準ファイル(実行可能)
  • 040000: ディレクトリ
  • 120000: シンボリックリンク
  • 160000: Gitlink。git-submodule で使います

Gitはファイルのパーミッションに関しては、実行ビットしか意識していません。

最終更新時刻

これには git log を使えば良さそうです。rev を指定することで、当該リビジョン以前の最後の変更を探し出してきます。

% git log -1 --pretty=format:%aD <rev> git/git.go
Fri, 12 Dec 2014 01:00:57 +0900

Go による実装

以上を踏まえた実装が motemen/go-vcs-fs です。

godoc/vfs.FileSystem という便利なインターフェースがあり、これを実装するようなコードになっています。じつは冒頭にあげた要件もこれがベースになっています。

sourcegraph/go-vcs

どうぞご利用ください……と言おうと思ってたんですが、sourcegraph/go-vcs というほとんど同じ先行実装がありました……機能が豊富であり、インターフェースが同じなのはもちろん、シンボリックリンクの扱いが TODO になってる点も一緒だったので、こちらを使うのがよさそうです。

こんな風に使います:

import (
    "sourcegraph.com/sourcegraph/go-vcs/vcs"
    _ "sourcegraph.com/sourcegraph/go-vcs/vcs/gitcmd"
)

...

repo, err := vcs.Open("git", ".")

commit, err := repo.ResolveRevision(rev)

fs, err := repo.FileSystem(commit)

sourcegraph.com/sourcegraph/go-vcs/vcs/gitcmd をインポートすることで、vcs.Open("git", ...) が使えるようになり、sourcegraph.com/sourcegraph/go-vcs/vcs/git のほうだと libgit2 を使った実装になるみたいです。同様に Mercurial にも対応してるのが便利そうですね。

はてなで一緒に働きませんか?