jsdoc-to-assert を試す

2016年8月23日 0 開発 , , ,

JavaScript の型チェックといえば FlowTypeScript だが、前者は型の定義ファイルが必要で、後者は言語自体を拡張しているため導入コストが少々、高い。もっと手軽に

  • 追加の外部ファイルは不要
  • JavaScript の組み込み型ぐらいをチェックできればよい

ぐらいのものがあれば、と探してみたら azu/jsdoc-to-assert がそんな感じなので試してみる。

2016/8/24
「省略可能な引数」と「null 許容型」の項を追記。

jsdoc-to-assert

jsdoc-to-assert は JSDoc 形式で書かれた関数のコメントを元に型チェックする。開発の経緯や詳細については JSDocをランタイムassertに変換するBabelプラグインを書いた | Web Scratch を参照のこと。おこなわれる処理は単純で、

  • JSDoc の @param から引数の型と名前を取得
  • 関数の冒頭に引数の型チェックを console.assert 形式で埋め込む

というもの。例えば

/**
 * Output log.
 *
 * @param {String} message Message text.
 */
function func( message ) {
  console.log( message );
}

というコードを jsdoc-to-assert に渡すと

/**
 * Output log.
 *
 * @param {String} message Message text.
 */
function func(message) {
  console.assert(typeof message === "string", 'Invalid JSDoc: typeof message === "string"');

  console.log(message);
}

に変換される。型チェックが偽ならば、その情報を assert として出力する。Firefox や Chrome の開発者ツールであれば関数の呼び出しと assert 箇所をコンソールから確認できるので、不正な値を指定した処理を修正するためのヒントになる。

環境構築と注意点

jsdoc-to-assert は本体と Babel plugin/preset 版が提供されている。plugin が Babel 用の機能拡張で、preset はそれをセットでパッケージ化したものになる。

いまのところ preset には babel-plugin-jsdoc-to-assert しか含まれていないので、どちらを選んでも機能差はないはず。とはいえ preset にしておけば依存が増えた時にも対応されるだろうから、今回は babel-preset-jsdoc-to-assert を採用。

以上を踏まえて package.json を定義。

{
  "babel": {
    "passPerPreset": true,
    "presets": [
      "es2015"
    ],
    "env": {
      "development": {
        "presets": [
          "power-assert",
          "jsdoc-to-assert"
        ]
      }
    }
  },
  "browserify": {
    "transform": [
      "babelify"
    ]
  },
  "scripts": {
    "test": "mocha --compilers js:babel-register test/**/*.test.js",
    "build:": "browserify ./src/js/App.js -d | exorcist ./src/assets/bundle.js.map > ./src/assets/bundle.js",
    "watch": "watchify ./src/js/App.js -v -o \"exorcist ./src/assets/bundle.js.map > ./src/assets/bundle.js\" -d",
    "release": "cross-env NODE_ENV=production browserify ./src/js/App.js | uglifyjs -c warnings=false -m > ./dist/bundle.js"
  },
  "devDependencies": {
    "babel-preset-es2015": "^6.13.2",
    "babel-preset-jsdoc-to-assert": "^3.0.2",
    "babel-preset-power-assert": "^1.0.0",
    "babel-register": "^6.11.6",
    "babelify": "^7.3.0",
    "browserify": "^13.1.0",
    "cross-env": "^2.0.0",
    "exorcist": "^0.4.0",
    "mocha": "^3.0.2",
    "npm-run-all": "^3.0.0",
    "power-assert": "^1.4.1",
    "uglify-js": "^2.7.3",
    "watchify": "^3.7.0"
  }
}

Babel の設定は .babelrc に書いてもよい。というか、そのほうが一般的だと思う。私はなるべく package.json にプロジェクト設定を集約したい派なのでこちらへ定義している。Browserify 設定も同様。

Babel 関連の npm をまとめる。

npm 用途
babel-preset-es2015 ES2015 変換用プリセット。
babel-preset-jsdoc-to-assert jsdoc-to-assert を利用するためのプリセット。
babel-preset-power-assert 単体テストで assert を power-assert に置換するためのプリセット。
babel-register Babel 変換時、import/require を補足するためのモジュール。assert の power-assert 置換などに必要。
babelify Browserify 用 Babel プラグイン。

今回は開発用のコードと単体テストの両方で jsdoc-to-assert を試したいので power-assert 関連も利用する。JavaScript 全体の bundle 化用に Browserify を使いたいので、Babel 本体は babelify を採用。Browserify が不要なら babel-cli を選んでもよい。

Babel の設定について。

{
  "babel": {
    "passPerPreset": true,
    "presets": [
      "es2015"
    ],
    "env": {
      "development": {
        "presets": [
          "power-assert",
          "jsdoc-to-assert"
        ]
      }
    }
  }
}

冒頭の passPerPreset が重要で、これを true にするとプラグイン単位で変換をハンドリング可能となる。プラグインの実行順で変換結果に問題が起きたときに指定するオプションである。

jsdoc-to-assert を試したとき Object の property として定義された関数などが無視されたので報告したところ、修正報告にこの設定を有効にしてほしいと回答されていたので、そのようにしている。

もうひとつ、jsdoc-to-assert の処理が開発版でだけ動作するように envdevelopment 側に定義している。こうすると NODE_ENV=production のときは除外される。npm-scripts の

{
  "scripts": {
    "release": "cross-env NODE_ENV=production browserify ./src/js/App.js | uglifyjs -c warnings=false -m > ./dist/bundle.js"
  },
}

がその指定。通常、npm-scripts から NODE_ENV は操作できないので cross-env を利用した。この方法は React をリリース用ビルドして余計な開発用コードを除去するなどでも必要なので、個人的に定石となっている。

実行してみる

package.json の定義と型チェック用の JavaScript を用意したら、実行してみよう。

$ npm run build

出力された bundle.js をテキスト エディタで開いてみると、console.assert が挿入されることを確認できるはず。わざと間違った型を指定子したスクリプトを実装して、それを読み込んだ HTML を Web ブラウザの開発者ツールで見てみると

jsdoc-to-assert による assert

こんな感じで assert が出力されている。なお、jsdoc-to-assert v2.4.2 時点では以下の関数が assert 対象となる。

  • キーワード function で定義される通常の関数
  • Object の property として定義された関数
  • ES2015 class の constructor
  • ES2015 class のメソッド
  • ES2015 class の static メソッド
  • ES2015 class の property setter

ES2015 の Arrow Function は対応していないようだが、これはコールバックなどで局所的に使用されることが多く、それに対して JSDoc を書く機会は少ないだろうし、なくてもさほど困らない。

また、型チェックの対象となるのは JavaScript の組み込み型になる。ES2015 class を定義し、それを型として JSDoc の param に指定しても無視される。

もうひとつ注意。jsdoc-to-assert は param が正しく書かれていることを前提としている。よって変数名が間違っていると常に assert がヒットすることになるので、気をつけること。引数の名前を修正するとき JSDoc に反映し忘れるとこの問題に遭遇する。というか、した。

単体テストの assert

単体テストの対象となるコードに jsdoc-to-assert が適用されるとどうなるのか。私の利用している mocha + power-assert の組み合わせで試してみた。

describe( 'Valid', () => {
  describe( 'Sample', () => {
    it( 'constructor', () => {
      const sample = new Sample( 'message' );
      assert( sample.message === 'message' );
    } );
  } );
} );

のような正常系と

describe( 'Invalid', () => {
  /** @test {Sample} */
  describe( 'Sample', () => {
    /** @test {Sample#constructor} */
    it( 'constructor', () => {
      const sample = new Sample( 7 );
      assert( sample.message === 'message' );
    } );
  } );
} );

という異常系を実装してテストを走らせてみる。

$ npm test

> using-jsdoc-to-assert@1.0.0 test .../jsdoc-to-assert
> mocha --compilers js:babel-register test/**/*.test.js



  Valid
    Sample
      ✓ constructor

  Invalid
    Sample
      1) constructor


  1 passing (2ms)
  1 failing

  1) Invalid Sample constructor:

      AssertionError: Invalid JSDoc: typeof message === "string"
      + expected - actual

      -false
      +true

      at Console.assert (console.js:95:23)
      at new Sample (Sample.js:52:26)
      at Context.<anonymous> (Sample.test.js:96:22)

jsdoc-to-assert の埋め込むものは console.assert なので mocha 上の扱いが気になっていたが、普通に failing となった。power-assert のように値の詳細は表示されないものの、位置はわかるので型を修正するヒントとしては十分である。

余談だが、単体テストで実行される関数がどのように変換されたかを知りたい場合は

describe( 'Invalid', () => {
  it( 'logStrStatic', () => {
    Sample.logStrStatic( 'message' );
    console.log( String( Sample.logStrStatic ) );
  } );
} );

のように関数自体を String で文字列化して console.log するとよい。出力結果は

function logStrStatic(message) {
      console.assert(typeof message === "string", 'Invalid JSDoc: typeof message === "string"');

      console.log(message);
    }

となる。この例だと jsdoc-to-assert により埋め込まれたコードを確認できる。

省略可能な引数

関数を設計する際、オプション扱いの設定などを省略可能にすることがある。例えば

/**
 * 指定されたファイルにデータを書き込みます。
 *
 * @param {String} path 書き込み対象となるファイルのパス情報。
 * @param {Buffer} data 書き込むデータ。
 * @param {String} mode 書き込みモード。省略時はファイルを新規作成します。
 *
 * @return {Boolean} 成功時は true。
 */
function writeFile( path, data, mode = 'w' ) {
}

という関数があったとする。引数のうち pathdata は必須だが mode は省略可能で、その場合はデフォルトの動作をする仕様。これをそのまま jsdoc-to-assert すると mode の型チェックが挿入されるため、省略したときに assert されてしまう。

Use JSDoc: @param の Optional parameters and default values を読むと省略可能な引数は {String=} のように記述するようだ。しかし jsdoc-to-assert では型として認識されず assert が生成されない。

よって型の列挙を代替案とする。

/**
 * 指定されたファイルにデータを書き込みます。
 *
 * @param {String} path 書き込み対象となるファイルのパス情報。
 * @param {Buffer} data 書き込むデータ。
 * @param {String|undefined} mode 書き込みモード。省略時はファイルを新規作成します。
 *
 * @return {Boolean} 成功時は true。
 */
function writeFile( path, data, mode = 'w' ) {
}

mode の型を String から String|undefined に変更することで、mode が undefined にもなり得ることを示す。これを jsdoc-to-assert すると

function writeFile( path, data, mode ) {
  // ...略
  console.assert(typeof mode === "string" || typeof undefined === "undefined" || mode instanceof undefined, 'Invalid JSDoc: typeof mode === "string" || (\ntypeof undefined === "undefined" || mode instanceof undefined\n)');
}

というコードが生成される。param の型に undefined が登場すると assert 内で常に true となる箇所が埋め込まれる。そのため評価がここに達すれば常に assert を通るわけだ。結果として省略可能な引数の指定に使える。

後続に到達できない instanceof が登場することから、もしかするとバグなのかもしれない。なお undefined のかわりに null を指定したときも同様の assert が生成される。

null の型は object なので、これを列挙される型として判定するなら value !== null のように値そのものが null であることを調べなくてはいけない。

null 許容型

省略可能な引数の扱いを調べていて、そういえば JSDoc 的に null 許容型はどういう扱いなんだろう?とググッてみたら Use JSDoc: @type に説明されていた。

Number 型があるとして、それを null 許容型にするなら {?Number}、許容しないなら {!Number} とする。試しにこれを jsdoc-to-assert に渡してみたら

/**
 * Output log.
 *
 * @param {String|?String} message Message text.
 * @param {String|!String} message2 Message text.
 */
function func(message, message2) {
  console.assert(typeof message === "string" || message == null || typeof message === "string", 'Invalid JSDoc: typeof message === "string" || (message == null || typeof message === "string")');
  console.assert(typeof message2 === "string" || message2 != null && typeof message2 === "string", 'Invalid JSDoc: typeof message2 === "string" || (message2 != null && typeof message2 === "string")');

  console.log(message);
}

というコードが生成された値をちゃんと null 判定しており、しかも許容と非許容も区別されている。判定の演算子を ===!== にしないのは null 判定においてを厳密に型チェックする意味がないからだろうか。

なお、null 許容だけだと型チェックが抜けるので、本来の型も列挙しておいたほうがよい。

あと ESDoc もこの記法に対応していた。null 許容と非許容を適切に判定し、元の型へのリンクを生成しつつ Attribute 欄に nullable: false/true を記述してくれる。

この情報を知れたのは収穫だった。

まとめ

JavaScritp 組み込み型に限定されるが、JSDoc さえキッチリ書いておけば、それだけで型チェックできるというのは便利ではなかろうか。独自の型を使用するような場所ではダック タイピングすればよく、その場合は assert ではなくユーザー向けコードでも明示的にインターフェースのチェックとエラー処理を実行したくなるだろう。

そういう意味でも今の私なら jsdoc-to-assert ぐらいで実用十分である。

今回の記事で作成したサンプル プロジェクトを以下に公開した。実際に動かして、assert がどのように出力されるか、コンパイルされたコードはどうなっているかを確認できる。

Electron を試す 8 – electron-prebuilt のパッケージ名変更と Browserify

2016年8月18日 0 開発 , ,

electron-packager の更新履歴をみていたら v7.5.0Add support for the new electron package name by zeke という PR に対応していた。内容を読むと electron-prebuilt のパッケージ名が electron に変更されたようだ。electron-prebuilt の README にも注記されている。

というわけで、このシリーズで作成したサンプルも名所変更に対応することにしたのだが Browserify 絡みで問題が起きたため、その内容と対策を記録しておく。

シリーズまとめ
Electron を試す

electron-prebuilt のパッケージ名変更

electron-prebuilt は Electron アプリの実行環境になる。従来、これをインストールするには

$ npm i -D electron-prebuilt

としていたのだが、v1.3.1 からパッケージ名が electron に変更されたので、以降は

$ npm i -D electron

とする。と、ここまでなら名前が短く分かりやすいので歓迎したいのだが…

Browserify のビルド問題と対策

Electron 本体に依存する機能は v1.0 から electron というパッケージ名で提供される。この辺の話は Electron を試す 7 – Electron v1.0 対応でも触れている。v1.0 より前は機能単位でパッケージ名を分けていたのだが、electron 配下へ統合された。

これを参照する場合、

const Electron = require( 'electron' );

とするか、ES2015 Modules であれば

import Electron from 'electron';

のようになるだろう。

通常はこれでよいのだが Browserify を利用して require/import を解決している場合は問題が起きる。electron-prebuilt のパッケージ名が electron に変更されたことで、electron に対する require/import が electron-prebuilt の実体を参照しようとしておかしくなるのだ。Browserify によるビルド結果は

module.exports = path.join(__dirname, fs.readFileSync(path.join(__dirname, 'path.txt'), 'utf-8'))

のようになる。そして electron-prebuilt からアプリを起動すると以下の実行時エラーが発生。

Error: ENOENT: no such file or directory, open '.../electron-starter/src/path.txt'
    at Error (native)
    at Object.fs.openSync (fs.js:640:18)
    at Object.module.(anonymous function) [as openSync] (ELECTRON_ASAR.js:167:20)
    at Object.fs.readFileSync (fs.js:508:33)
    at Object.fs.readFileSync (ELECTRON_ASAR.js:500:29)
    at Object.global.1.fs (.../electron-starter/src/main.js:5:42)
    at s (.../electron-starter/src/main.js:1:333)
    at .../electron-starter/src/main.js:1:384
    at Object.global.5.../common/Constants.js (.../electron-starter/src/main.js:231:17)
    at s (.../electron-starter/src/main.js:1:333)

この問題を回避する方法はふたつ。

  1. electron-prebuilt を旧名称で npm install する
  2. electron-prebuilt に対する require/import 参照を Browserify の対象外とする

方法 1 は electron-prebuilt 的に deprecated とされている。将来、旧名称が廃止される可能性もあって危険だ。よって正攻法の 2 を採用する。

substack/node-browserify の Usage を読むと --exclude オプションにパッケージ名を指定することで bundle ( 参照解決 ) の対象外となるようだ。

–exclude, -u Omit a file from the output bundle. Files can be globs.

というわけで npm-scripts で

{
  "scripts": {
    "build:js-main": "browserify -t [ babelify ] ./src/js/main/Main.js --im --no-detect-globals --node -d | exorcist ./src/main.js.map > ./src/main.js"
  }
}

となっていたものを

{
  "scripts": {
    "build:js-main": "browserify -t [ babelify ] ./src/js/main/Main.js --exclude electron --im --no-detect-globals --node -d | exorcist ./src/main.js.map > ./src/main.js"
  }
}

に修正してビルドしたところ、アプリが正常に起動 & 動作することを確認できた。

Renderer プロセスについて

Renderer プロセスについて。私がこちらで electron を参照する場合、

const Electron = window.require( 'electron' );

のように window 経由で require を利用しているため Browserify の対象外なのだが、念のためこちらをビルドするときも --exclude electron している。なぜ Main/Renderer で参照方法を分けているのかは、

  • Main
    • electron の他にも fs など dependency にないパッケージを参照する可能性が高い
    • そのため --im オプションで dependency に見つからない参照を無視している
    • 今回の問題は electron が見つかるようになってしまったことで発生した
  • Renderer
    • dependency に存在するパッケージのみで構成
    • Web ブラウザ用の JavaScript ビルドと同じ思想で参照解決
    • --im オプションを利用しないので electron を直に require できない
    • よって window.require 経由で参照して Browserify の介在を回避

という理由から。この辺の話は Electron を試す – 開発環境の構築でも触れたが、今回の問題にも関わっているので改めて書き出してみた。

ちなみに Renderer が利用する electron 由来の機能も ipRenderer に限定している。プロセス間通信は Web アプリにおける Client–Server Model を踏襲し、

  • Clinet ( Renderer ) が必要に応じて Server ( Main )Request ( ipcRenderer )
  • ServerRequest の結果を ClientResponce ( sender.send )
  • Server は必要に応じて ClientPush Notification ( ipcMain )

という感じで設計している。

akabekobeko/examples-electron には対応を反映済み。各プロジェクトの npm-scripts で --exclude electron を削除してみれば、今回の問題とおかしくなったビルド結果を確認できる。

Redmine 運用について 3/3 – 普及の施策と課題

2016年7月29日 0 開発

Redmine 運用について書いてみるシリーズ 3/3。

最終回は Redmine に馴染みないユーザー、例えば工程管理やバグ表を Excel で運用していたとか、そういう管理職や社員に対して Redmine を普及させるための施策や課題などを書く。

これまでの内容と重複する部分もあるが、本記事ではルールよりも事例や留意したことに重きを置く。

シリーズまとめ
Redmine 運用について

現状分析

前職はソフトハウスであり、一部に Trac による TDD を経験していたプロジェクトもあったので Redmine 導入はスムーズに行われた。TDD に親しみのない社員も BTS として利用しはじめて、Excel ベースのバグ表を置き換えながら慣れていった。

一方、現職は私が移籍する前に Redmine を導入していたものの、数名が実験的に利用しているような状況で、業務は主に上長の手による Excel シートで管理されていた。

シートには部署に属する社員と担当業務がずらっと並んでいる。2 週間ごとにおこなわれる定例で聞いた話を紙にメモ、その後に自席に戻ってそれを Excel に反映させるのだという。

これを Redmine 管理へ置き換えることを提案することにした。まずは現状分析するために二ヶ月ほど定例に参加してみた。定例の規模は 7 人で 1 時間、参加した回数は 5 回ぐらい。その中で気になった点をまとめる

  • 同じ課題が何度も繰り返されている
    • あの問題どうなりました、確認中です、ではまた次回に、という感じ
    • 手元のメモで数をカウントしたら 3 回のものもある
  • 進捗報告は基本的に作業の開始と終了のみ
    • 〜に着手しました、終了しました
    • 中間報告は率ではなく問題の有無となる
    • 期限までに終了すれば経緯を問わない
    • 担当者が問題と感じたものだけが問題として扱われる
    • 良くいえば裁量主義、悪くいえば放置放任
  • 議事録がない
    • 定例の記録は各人のメモに委ねられている
    • 業務管理 Excel シートは一種の議事録ともいえる
  • 2 〜 3 人で細かな業務の議論が突発的にはじまる
    • それで大丈夫ですか?あれはどうなっています?という感じで開始
    • 細かくメモを取るタイプだと報告から問題に気づき、それを指摘することが多い
    • 議論の記録が保証されないためか、この場で結論せねば、という雰囲気
    • これが数回あると、定例が 2 時間に及ぶこともある
    • 議論に関係ない社員は終了を待ち続けるか、雑談をはじめる
    • 私は眠気が抑えきれず、船を漕いでいる

業務管理はもとより定例にも多くの問題がある。これをもとに Redmine で対策する方法を考える。

部署単位の業務管理

定例で私の番が回ってきたとき、現状分析で気になった点を発表した。そのうえで、これらの問題が既に導入されている Redmine で解決可能なことを説明。口頭のみでスライドなどは用意しなかったが、当時を思い出しながら問題と対策をまとめる。

  • 同じ課題が何度も繰り返されている
    • 課題を Redmine のチケットとして管理する
    • チケットには課題の内容、担当、期日、対応状況を設定できる
    • 個人のメモや記憶に頼らず、常に確認可能
    • チケットならば任意のタイミングで着手できる
    • 定例を待たずとも議論可能
  • 進捗報告は基本的に作業の開始と終了のみ
    • 途中経過も重要
    • 作業の見積もりと実際にかかった時間は計測されているべき
    • そうしないと効率化できない
    • 定例の合間となる 2 週間、質問するまで手が空いていることもわからない
    • チケットで期日、作業時間、進捗率を設定することでリアルタイムに現状報告される
    • 定例を待たずとも把握可能
    • 作業が発生したら定例の場でチケット化する
    • 理想はチケット一覧を眺めながら数値で現状確認すること
  • 議事録がない
    • 議事録ドリブンを提案
    • テキストで残して全員が読めることの重要性を説明
    • 定例の前に Redmine Wiki に議題を書き、読んできてもらう
    • 定例では Wiki の内容をテキスト エディタでプレビュー表示
    • 当時は Sublime Text + timonwong/OmniMarkupPreviewer
    • 現在は Atom + textile-preview を利用
    • 議題に対する発言や決定事項をエディタで編集
    • メモしてほしいことがあれば発言する、というルールを提示
    • 定例が終了したときには議事録が完成、Wiki に反映して共有できている
  • 2 〜 3 人で細かな業務の議論が突発的にはじまる
    • 議事録ドリブンにより大きな問題は議題として登録済み
    • 小〜中規模の問題はなるべく当事者間で議論、できればチケットで
    • テキストでやりとりしないと、理解の齟齬が起きやすいことを説明
    • 口頭では相手の発言を精査引用するのが難しい、博覧強記を求められる
    • すべてを把握するより必要な情報がある場所を管理するほうが簡単

こんな感じの話をした。私は既に自身の業務に関係する社員と Redmine でやりとりしていたので、そのチケットを見せたりもした。

結果、まずは部署内の業務管理で Redmine を活用してみようということになった。上長の Excel シート管理はその後もしばらくは続いていたようだが、ここ 1 年ぐらい見ていないので移行できたのだろう。

対策のうち議事録ドリブンは採用、業務のチケット化については少しずつ浸透している。オフィスを歩いているときに見える PC の画面で、チケット編集している様子をみることが増えた。

全社的な展開

現職の企業は社員数 20 名ほどの小さな会社である。そのため、全社的な展開といっても大企業の 1 部署程度なのだが、横展開の手法という意味では規模に関係なく汎用性がある話といえるかもしれない。

個人、部署レベルでは Redmine による業務単位を取り入れたので、これを全社へ展開することにした。管理職には前述の提案を繰り返すことで賛同を得られたのだが、具体的な運用がピンと来ないとのことで、勉強会を希望された。

勉強会を開催するにあたり、実運用しているプロジェクトをサンプルとした。私はソフトウェア開発を担当しているのだが、この業務は目的のはっきりしたチケットが多いため、サンプルにふさわしい。

ある機能の開発着手、進捗更新、解決報告して検証、終了という流れを見せた。またそれをソフトウェア開発とな異なる業務、たしか書類作成だったかに当てはめて、同じように進捗管理する手順を説明。

概ね納得していただけたようで、全社的に積極利用する運びとなった。社員に対する説明は Wiki
に掲載した運用ルールやチャット、必要ならば個別に対応する。既に Redmine が導入済みということもあり、基本操作については特に解説しない。

かわりに

を購入して会社の本棚へ公開した。

私はこれの第 2 版を持っているが、基本機能だけでなく呉服屋の管理事例など運用面の話も面白く、かなりオススメの書籍である。Redmine の更新ペースには追従できていないが、激変というほどでもないので入門には十分だと思う。

管理職の説明会には質問も多く挙げられた。それらの内、特筆すべきものをまとめる。

プロジェクトはどういう単位で作成すればよい?

この話は前回を参照のこと。

現在の〜という業務をチケット化するとしたら?

  • 実際にチケットを作成しながら説明
  • バージョンや親チケットなどは慣れないと分かりにくいので割愛
  • トラッカーで業務の大まなか分類を指定
    • ソフトウェア開発でなくても「実務 = 機能」、「問題への取り組み = バグ」、「顧客対応 = サポート」のように分類可能
  • 題名に業務内容を指定
    • 〜業務とするより、その業務で達成したいことや成果物で考えるとチケット化しやすい
    • 〜する、〜を作成する、など
  • 説明に業務の背景や補足情報を記述
  • ステータス新規進行中終了だけで管理する
    • 慣れたら解決フィードバックも利用して終了を判断するタイミングを設ける
  • 優先度は慣れるまで設定しなくてよい
  • 担当者は業務を依頼する社員を指定
    • チケット上で対話する場合は、その内容をコメントしたうえで担当者を相手に変更
    • 相手が返答したら相手が自分に担当を戻す
    • これで対話が成立
  • 開始日はチケットに着手した日を指定
    • 業務の見積もりに関わる、管理職として非常に重要な設定
    • デフォルトはチケット作成日
    • すぐに着手しないなら空欄にするとよい
  • 期日は業務の締め切りとなる日を指定
    • 業務の見積もりに関わる、管理職として非常に重要な設定
    • デフォルトは空欄
    • これを設定するなら開始日も厳密に指定すること
    • 開始日と期日を指定することで正確なガントチャートを表示できる
  • 予定工数は時間単位で指定
    • 業務の見積もりに関わる、管理職として非常に重要な設定
    • 1 日 = 8、半日 = 4、1 時間 = 1、30 分 = 0.5 ぐらいの単位で入力
    • ざっくりでよい
    • あわせて時間管理を有効にし、実作業の時間も記録すると効率化や見積もり精度の向上に役立つ
  • 進捗率
    • 業務の現状把握に関わる、管理職として非常に重要な設定
    • 0 からはじめて 10% 単位で更新
    • ステータスを解決終了にするときは 100% にする

業務の資料はなるべく Wiki に書くべきか?

  • なるべく Wiki がよい
  • Wiki は基本的な装飾とページ階層化、リンク機能があるので大抵の資料は書けるはず
  • チケットや Wiki のリンク略記も便利
  • 情報を Redmine に集約することで、検索の価値が高まる
  • 編集履歴も自動保存されるため、簡易なテキスト専用リポジトリとしても有用
  • 他社とのやりとりや印刷を考慮するなら Microsoft Office も可

Redmine に関するお知らせ

Redmine に関する情報を社員に告知したい時がある。例えば

  • Redmine 本体のメンテナンス
  • Tips
  • FAQ

など。これらを通知するため、以下の方法を採用している。

チャット

現職ではチャットワークを利用しており、Redmine という専用のグループを設置。メンバーは社員全員。例えば

  • Redmine の更新
  • サーバー更新に由来する再起動
  • 影響の大きな設定変更
  • 運用ルールの改訂

について通知している。突然利用できないとか動きが急に変わったといった問題に遭遇すると不安を感じるさせることになる。これを避けるため、事前通知は重要である。

また常時、質疑応答を受け付けている。その内容を FAQ にすれば社としての Redmine 知見を貯められる。ただ、あまり質問されることはなくて私が流した Tips に対する反応とか、そんな感じのやりとりが多い。

Wiki

Redmine の運用ルールや Tips、FAQ を貯める場所として利用。

チャットと同様に Redmine 専用プロジェクトを設置しており、Redmine に関することはその Wiki に記述。ここを読んでもらえれば Redmine はバッチリ!という状態を目指している。

Redmine の推進にあたり心がけていること

Redmine の推進にあたり心がけていることをまとめる。

古い管理手法を尊重する

業務やバグ管理に Excel シートを利用することについて、Redmine や GitHub issue を経験していると旧態依然に見えることだろう。

複数人の同時編集が難しい、履歴が取りにくい、セル管理の破綻しやすさなど、Web ベースの TDD/BTS に比べるとメリットなど皆無に感じられる。

それでも、紙でないだけマシである。また、紙であったとしても管理されているだけで価値がある。業務や開発において、最もよくないのは管理されていないことだ。

非効率、または無駄なルールが多くても、管理されているのであれば状況は可視化されている。つまりは改善の対象となりえる。管理されていない場合、業務で起きていることの分析・分類から始めなくてはいけない。

以上を踏まえ、Redmine や GitHub issue による管理を提案する場合は古い手法の管理者を尊重しよう。よくぞ管理してくれていました、と。

この記事でも挙げた Excel シートによる業務管理では社員単位の作業分担が記録されていた。ということはつまりチケットの題名と説明、担当者に流用できる。あなたの管理してきた情報は価値あるもので、それをより便利なツールに移行するだけという点を強調する。あまり過剰にすると褒め殺しっぽくなるので注意。

Redmine に限らず新しいツールや手法を提案するときは、いつもこのようにしている。対立を避けながら賛同を得るための方法としてなかなかよいと考えているのだけど、どうだろう?

とはいえ私は凡人なので、旧態をけなす誘惑に苛まれることがある。

Redmine 以外だと例えば Subversion から Git、jQuery から React へ移行したときには古巣がみすぼらしく感じられたものだ。それをけなすことで簡単に自身の成長を実感できる。いままで悩まされていたことを愚痴るのは、抗いがたい快楽をともなう。

この行為の問題は、新しい手法への敵対者を増やし賛同者を減らす効果にある。古巣に残らざるを得ない人にとって、そこをけなされることは耐え難い苦痛である。移行を検討できる人は対立を知ることで、それを見送るかもしれない。まだ議論は尽くされておらず新しもの好きが騒いでいるだけ、と。

この誘惑に負けてやらかした場合はなるべく深く悩んで、そのことを忘れないように心がけている。枕に顔を埋めてジタバタするわけだ。ああ、やらかしてしまった!という感じで。

活況を演出する

大昔に在籍していたプロジェクトで、知見を貯めるとかソフトウェアのリリース ページ作成などの目的で Wiki の採用が提案された。提案者は私ではないのだが、初めて触れる Wiki の機能性に感心してすぐさま賛同した。

そして運用がはじまった。しかし一部の賛同者が利用するだけで一向に広まらない。折にふれて Wiki ありますよ〜すばらしいですよ〜と宣伝してみるのだが、反応は冷ややかである。

そこで我々は考えた。

  • 使われないのは誰も使っていないからだ
  • この状態で書き込むのは新雪を踏むようなもので、気後れするもの仕方ない
  • 我々が書きまくろう
  • ジョークさえも許容しよう
  • 使ってよいという雰囲気を醸成するのが重要

というわけで、例えば海外チームのメンバーを紹介するのにヒッピー風とあれば、ヒッピーをリンクしてその説明を書いたりした。あと砂場というページを練習用に公開した。昔のことなので記憶が曖昧だが、Sandbox の和訳としてはじめから用意されていたページだったかもしれない。そこに冗談混じりのテキストのあることが重要だった。

しばらく、おそらく数ヶ月ぐらいかかった気がするけど次第に他のメンバーも書き込むようになった。ちょっとした技術的なテクニックとか考察なんかも充実してきて、ちゃんと分類したいということで PukiWiki へも移行した。他に内製の BTS もあったのだけど、そこに書かれないような情報は Wiki に蓄積されてゆき、当初の目標は達成された。

この経験から、あるシステムを普及させたい場合、活況であることの演出は非常に重要と考えるようになった。現職ではそのために以下の施策を実施している。

  • なんでもチケット化
    • ミーティングで議論が発生しそうな時は「それ、チケットで!」
    • なにか購入したいものがあるとき「稟議はチケットで!」
    • チケットが気軽に作成できて便利なものであることをアピール
    • 目的、担当者、期限のあるものなら大抵はチケット化できることをアピール
    • チケットに関わる機会を増やすことで自然と活況になる
  • チャットによる様々な通知
    • Redmine 自体の変化が活発であることを明示
    • 放置されていませんよ、ちゃんと生きていますよ、ということを演出
    • 空回りしている可能性も否めないが、続けることが大事
  • 様々なレポートの Wiki 記載
    • 現職ではセミナー参加を推奨している
    • 業務時間に出席することもあり、レポートを書くことが暗黙の義務となっている
    • レポートは Wiki に書かれ、その更新も通知している
    • これは筆者、読者の両方で関わっているが、かなり楽しい
    • アクセスログを見るに、読者数は多い
  • 議事録ドリブン
    • 私が司会者となるミーティングは基本的に議事録ドリブンで運用している
    • 議題と議事録は Wiki に書かれる
    • ミーティング中の発言が記録されて Wiki に反映される
    • 参加している感を強調
    • ジョークも記録しますよ、と言っているのだけどそれは遠慮されている
    • 司会と書記を兼ねるのは厳しいものがあり、タイピング速度の問題もあって、これをできる社員が 2 名しかいないのが難点

マメなサポート

これまでの内容と被る部分もあるのだけど、Redmine に関する質問や悩みは可能な限り迅速に対応している。基本はチャットだがテキストのやりとりが苦手な社員には口頭での相談も可。

ときには社員本人の席を訪れ、30 分ぐらいマンツーマンで Redmine に関する指導をすることもある。

普段、自分の利用している環境で直に説明すると高度な機能でも理解してもらいやすい。ついでにチケット化されていない業務を質問して、それをどのように Redmine 管理へ持ってゆくかを提案したりもする。

私のメイン業務はソフトウェア開発なので、こうしたサポートのコストは問題に感じられるかもしれない。しかし私は社全体で強くなることを目標としており、業務の効率化も立派なハックと考えている。そのために Redmine を提案したのだし、こうしたコストを捻出するためにメイン業務の見積もりはかなりの余裕を持たせている。

こうした考えに至ったのは、かつて在籍していたプロジェクトのリーダーの影響である。Wiki や議事録ドリブンもその一部で、その方はこうしたファシリテーションを非常に重視していた。実際、それを受ける側として効果を実感したこともあり、私もそれを踏襲している。

おわりに

つきあいの長い Redmine について以前からまとまった文章を書いてみたかったのだが、なかなかその気にはならなかった。書きはじめたら長くなるのは必然で、そのため腰が引けていた。そんな折、Redmineがいくら良くても会社の上司や経営者が見なければExcelがはびこってしまう事例: プログラマの思索とはてブの反応はよい刺激になった。

Excel 中心で管理したい上司をどう説得するか。説得を諦めて現実解として Excel ジェネレーターを実装するのか。

幸い私は理解ある環境に恵まれて Redmine 導入に成功したが、そうではない人や環境に対して知見の一部でも役立てば、という気持ちで記事にしてみた。

もし感想や Redmine 導入についての相談などがあれば、本記事のコメント欄や Twitter などでどうぞ。