@ledsun blog

無味の味は佳境に入らざればすなわち知れず

Jenkinsでherokuに配置

目標

Jenkisを使ってherokuへの配置を自動化します。

前提

  1. JenkinsはCloudbeesを使う*1
  2. Jenkinsのコードの取得は既に出来ている*2

準備

herokuのアカウントページSSH KeysにCloudBees Public Keyを登録します。

配置

要はシェルでgit push herokuします。 ただしJenkinsはソースコードを特殊なブランチにcheckoutします。pushするブランチを明示的に作成します。

【Jenkins】JenkinsでHerokuにデプロイすると「Everything up-to-date」と表示される - 量子的ぷろぐらま を参考にし、次のスクリプトを設定します。

git checkout -b temp
git push git@heroku.com:myapp.git temp:master
git checkout master
git branch -D temp

git pushのリモートブランチにはgitリポジトリを直接指定出来ます。

*1:Cloudebeesの設定方法はCloudbeesを使ってJenkinsを用意するを見てください。

*2:ここまでの設定はGitlabの更新を契機にJenkinsでnode.jsアプリケーションをテストを見てください。

Gitlabの更新を契機にJenkinsでnode.jsアプリケーションをテスト

はじめに

前提条件

  1. ソース管理はgitlab
  2. JenkinsはCloudbeesを使う*1
  3. テストはfresby.jsで書いてる

目標

gitlabでトピックブランチをmasterにマージしたら自動的にテストする

gitlabの設定

秘密鍵

Jenkinsがgit cloneできるように秘密鍵を登録します。

  1. Jenkinsで新規プロジェクトを作成
  2. (Cloudbeesの場合)Jenkinsの秘密鍵が用意されている
  3. gitlabの自分のアカウントに追加

pullするだけなので、自分のアカウントをそのまま使います。 複数人で開発するプロジェクトでは、Jenkins用ユーザを用意した方がいいかもしれません。

webhook

  1. gitlabのプロジェクトのSettingsのHooksを設定
  2. Push eventsイベントを選択
  3. 次のフォーマットのURLを登録
http://< jenkins host >/git/notifyCommit?url=git@< gitlab host >:< repository owner >/< repository name >.git

例えば

http://ledsun.ci.cloudbees.com/git/notifyCommit?url=git@gitlab.com:ledsun/sampleproject.git

参考:YUKASHIKADO Inc. 開発ブログ • Jenkins と GitLab と rbenvでプライベートなRuby開発環境を構築する

Jenkinsの設定

plugin

gitlabと連携するためのプラグインをインストールします。

  1. Jenkins > Jenkinsの管理 > プラグインの管理
  2. Gitlab Hook Pluginをインストール

Jenkinsのプロジェクト設定

Jenkinsのダッシュボードからプロジェクトを作成し設定します。

ソースコード管理

  1. git を選ぶ
  2. git urlを指定。例えばgit@gitlab.com:ledsun/sampleproject.git
  3. masterブランチを設定

ブランチを指定すると、masterにマージしたときだけビルドを実行します。 指定しないと、トピックブランチをプッシュした時もビルドが実行します。

ビルド・トリガ

  1. SCMをポーリングにチェックを入れる

ビルド

シェルの実行に次のスクリプトを指定します。

curl -s -o use-node https://repository-cloudbees.forge.cloudbees.com/distributions/ci-addons/node/use-node
NODE_VERSION=0.10.26 . ./use-node
npm install
npm install jasmine-node@1.14.2
npm install forever
export PATH=$PATH:node_modules/jasmine-node/bin/:node_modules/forever/bin
forever start --no-colors app.js
sleep 2s
jasmine-node spec/dev/ --junitreport
forever stop --no-colors app.js

注意点

  1. Cloudbeesでnodeを使うにはおまじないが必要
  2. nodeコマンドはローカルにイスントールしてパスを通す
  3. jasmine-nodeでfresby.jsのテストを実行するときはバージョン1.14.2を指定する
  4. テスト終了後にプロセスをnodeアプリケーション終了するためにforeverを使う
  5. nodeアプリケーションの起動に時間がかかるのでsleepで待つ

nodeを動かす

CloudBees DEV@cloud (Jenkins as a Service) Documentation のサンプルを参考にします。 サンプルはnode.jsのバージョンが古いのでなるべく最新の10.26を指定します。

curl -s -o use-node https://repository-cloudbees.forge.cloudbees.com/distributions/ci-addons/node/use-node
NODE_VERSION=0.10.26 . ./use-node

これでnpmコマンドも入ります。

nodeコマンドをインストール

Cloudbeesでは、nodeコマンドはHow do I get a grunt task working with a Cloudbees Jenkins build - Stack Overflow のようにローカルにインストールしてPATHを設定して使います。

  • forever
  • jasmine-node

をインストールします。

npm install jasmine-node@1.14.2
npm install forever
export PATH=$PATH:node_modules/jasmine-node/bin/:node_modules/forever/bin

nodeアプリケーションの開始

終了しなくても一度の実行ではテストが成功します。 何度か実行すると失敗します。

Jenkinsでnodeのアドオンをビルドして、それを使ったhttpサーバアプリケーションを起動する - 猫ぱーんち!を参考にし foreverを使って明示的に起動・終了します。

Jenkinsのコンソールはカラー表示に対応していないので--no-colorオプションをつけます。

forever start --no-colors app.js

foreverを使うとアプリケーションの起動完了前に次のコマンドが実行されます。 起動完了を待つためにスリープを入れます。

sleep 2s

テストの実行

frisby.jsのテストはjasmine-nodeコマンドで実行します。 jasmine-nodeの最新版(1.14.3)は上手く動きません。1つ前のバージョン1.14.2を使います。

また、Jekinsでテスト結果を集計するために--junitreportオプションをつけます*2

jasmine-node spec/dev/ --junitreport

nodeアプリケーションの終了

forever stop --no-colors app.js

ビルド後の処理

Junitテスト結果の集計にreports/*.xmlを指定します。 Jenkinsのダッシュボードに成功・失敗したテスト件数が表示されるようになります。

確認

Jenkinsの設定が上手く言っているか確認するために、ビルドの実行をしてみましょう。

20140718追記

jasmine-nodeの1.14.5がリリースされ、バージョン指定が不要になりました。

*1:Cloudebeesの設定方法はCloudbeesを使ってJenkinsを用意するを見てください。

*2:2014年7月8日時点では、--junitreportオプションを付けるとテストが正しく実行されません。1.14.4で解消されるそうです

Cloudbeesを使ってJenkinsを用意する

Jenkinsを使うだけならサーバーもインストール不要なPaasを使うのが便利です。 今回はCloudBeesを使います。*1

Cloudbeesの用意

  1. CloudBeesへSign Up
  2. githubアカウントでログイン*2

Jenkinsの用意

  1. Buildsボタンを押す
  2. 10分待て言われるので待つ

しばらくしてBuildsボタンを押すとJenkinsのダッシュボードが表示されます。

*1:OpenShift Onlineを使うのもいいと思います。

*2:GoogleアカウントでもOKです。

component.js(1.0.0-rc5)を試す

component.js

component.jsはnpmやbowerを使った依存する外部ライブラリ(JavaScript,CSSともを含む)の解決と、 unglifyやcssminで行う自作ファイルを適切に組み合わせるビルドを1つのツールでやってしまおうとする 意欲的なツールです。

Getting Started

使用感はGetting Startedを眺めてもらうのが 分かりやすいです*1

流れ

component jsを試してみた。 - 日頃の行いと大体一緒です。 たまたまバグを踏んだので、その部分を補足します。

  1. npmでinstall
  2. githubの設定
  3. index.html,css,jsとcomponent.jsonの記述
  4. component buildの実行

install

npm install -g component@1.0.0-rc5

1.0.0はstableではありません。バージョンを指定しないと0.19.9が入ります。 結構大きいです。

github APIの設定

component1.0.0github APIを使って依存ライブラリを取得します。 github APIの設定が必要です。

https://github.com/settings/applications#personal-access-tokens にてアクセストークンを作成します。 権限はrepoとuserだけで動きました。もっと減らせるかもしれません。

アクセストークンの動作確認

curl -u <token>:x-oauth-basic https://api.github.com/user

.netrcにトークンを追記

machine api.github.com
  login <token>
  password x-oauth-basic

ファイルを準備

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Getting Started with Component</title>
    <link rel="stylesheet" href="build/build.css">
  </head>
  <body>
    <h1>Getting Started with Component</h1>
    <p class="blink">Woo!</p>
    <script src="build/build.js"></script>
  </body>
</html>

index.js

var blink = document.querySelector('.blink');

setInterval(function () {
  blink.style.visibility = getComputedStyle(blink).visibility === 'hidden'
    ? 'visible'
    : 'hidden';
}, 30

index.css

* {
  box-sizing: border-box;
}

component.json

{
  "name": "getting-started-with-component",
  "dependencies": {
    "necolas/normalize.css": "^3.0.0"
  },
  "scripts": ["index.js"],
  "styles": ["index.css"]
}

実行

component buildコマンドで依存ライブラリの取得とビルドが走るはずですが・・・

component build
   installed : necolas/normalize.css@3.0.1 in 1148ms
       build : resolved in 4903ms
       build : files in 7ms
       build : build/build.js in 9ms - 1kb

       error : Cannot call method 'process' of undefined

期待しないエラーが表示されます*2。 しかしエラーの詳細が分かりません。

こういう場合は、component guideにあたります。 Trouble shooting に従いDEBUG変数を指定して再度実行します。

DEBUG=component* component build
  component-resolver remote not set - defaulting to remotes's defaults +0ms
  component-resolver:locals resolving local at "/Users/ledsun/SandBox/JavaScript/component" +6ms
  component-resolver resolving "getting-started-with-component" +4ms
  component-resolver remaining dependencies: 1 +4ms
  component-resolver remaining semver: 0 +0ms
  component-resolver finished resolving locals +0ms
  component-resolver finished resolving dependencies (1) +0ms
  component-resolver:semver resolving semver necolas/normalize.css@^3.0.0 +1ms
  component-resolver:dependencies resolving dependency necolas/normalize.css@3.0.1 +2ms
  component-resolver:dependencies searching ["local","github","bitbucket"] for necolas/normalize.css@3.0.1 +1ms
  component-resolver:dependencies found necolas/normalize.css@3.0.1 from remote "local" +1ms
  component-resolver resolving "necolas/normalize.css" +0ms
  component-resolver remaining dependencies: 0 +0ms
  component-resolver remaining semver: 1 +1ms
  component-resolver:semver resolved semver necolas/normalize.css@^3.0.0 -> necolas/normalize.css@3.0.1 +0ms
  component-resolver finished resolving semver +0ms
  component-resolver finished resolving dependencies(2) +1ms
  component-downloader "/Users/shigerunakajima/SandBox/JavaScript/component/components/necolas/normalize.css/3.0.1" exists, skipping downloading. +0ms
  component-resolver finished installing dependencies +0ms
       build : resolved in 22ms
       build : files in 6ms
       build : build/build.js in 9ms - 1kb
  component-consoler TypeError: Cannot call method 'process' of undefined
    at /usr/local/lib/node_modules/component/node_modules/component-build/node_modules/builder-autoprefixer/index.js:21:49
    at Object.read (/usr/local/lib/node_modules/component/node_modules/component-build/node_modules/component-builder/node_modules/component-manifest/build/index.js:436:49)
    at Styles.autoprefixer (/usr/local/lib/node_modules/component/node_modules/component-build/node_modules/builder-autoprefixer/index.js:16:10)
    at next (/usr/local/lib/node_modules/component/node_modules/co/index.js:99:21)
    at /usr/local/lib/node_modules/component/node_modules/co/index.js:102:18
    at /usr/local/lib/node_modules/component/node_modules/component-build/node_modules/component-builder/build/plugins/url-rewriter.js:438:7
    at /usr/local/lib/node_modules/component/node_modules/component-build/node_modules/component-builder/node_modules/component-manifest/build/index.js:446:7
    at fs.js:266:14
    at /usr/local/lib/node_modules/component/node_modules/component-build/node_modules/component-builder/node_modules/graceful-fs/graceful-fs.js:104:5
    at /usr/local/lib/node_modules/component/node_modules/component-resolver/node_modules/graceful-fs/graceful-fs.js:104:5 +12ms

       error : Cannot call method 'process' of undefined

/usr/local/lib/node_modules/component/node_modules/component-build/node_modules/builder-autoprefixer/index.js:21:49 で、例外が起きているのが分かります。

このファイルを見るとauto変数が初期化されていないのが原因ぽい。 builder-autoprefixerコミットログを見るとそれっぽい修正が入っています。 直すと・・・

component build                                                                                              (git)-[master]
       build : resolved in 17ms
       build : files in 66ms
       build : build/build.js in 69ms - 1kb
       build : build/build.css in 69ms - 7kb

無事ビルドが通りました。パチパチ。

結果

build.js

/**
 * Require the module at `name`.
 *
 * @param {String} name
 * @return {Object} exports
 * @api public
 */

function require(name) {
  var module = require.modules[name];
  if (!module) throw new Error('failed to require "' + name + '"');

  if (!('exports' in module) && typeof module.definition === 'function') {
    module.client = module.component = true;
    module.definition.call(this, module.exports = {}, module);
    delete module.definition;
  }

  return module.exports;
}

/**
 * Registered modules.
 */

require.modules = {};

/**
 * Register module at `name` with callback `definition`.
 *
 * @param {String} name
 * @param {Function} definition
 * @api private
 */

require.register = function (name, definition) {
  require.modules[name] = {
    definition: definition
  };
};

/**
 * Define a module's exports immediately with `exports`.
 *
 * @param {String} name
 * @param {Generic} exports
 * @api private
 */

require.define = function (name, exports) {
  require.modules[name] = {
    exports: exports
  };
};
require.register("getting-started-with-component", function (exports, module) {
var blink = document.querySelector('.blink');

setInterval(function () {
  blink.style.visibility = getComputedStyle(blink).visibility === 'hidden'    ? 'visible'
    : 'hidden';
}, 300);

});

require("getting-started-with-component")

require関数の定義が追加されています。

build.css

抜粋。外部ライブラリのcssファイルと結合された上で、こんな感じでベンダープレフィックスが追加されます。

/**
 * Address differences between Firefox and other browsers.
 */

hr {
  -moz-box-sizing: content-box;
  -webkit-box-sizing: content-box;
  box-sizing: content-box;
  height: 0;
}

元のファイル

/**
 * Address differences between Firefox and other browsers.
 */

hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  height: 0;
}

*1:このリポジトリには他にも参考になる文章がたくさんあるので困ったときに見ると解決策が見つかります

*2:修正済みのbuilder-autoprefixer 1.0.2がリリースされています。今はこのエラーは起きません

ZeroClipboard

ZeroClipboardを紹介します。

ZeroClipboardとは

ブラウザからクリップボードに書き込むためのJavaScirptライブラリ。

githubで使われています。

f:id:ledsun:20140617142453p:plain

なぜZeroClipboard?

クリップボード操作はブラウザ依存(InternetExplorer専用)です。 ZeroClipboardはFlashを使ってクロスブラウザを実現しています。

どうやって使う?

  1. 公式サイトからzipファイルをダウンロード
  2. 解凍
  3. helloworld的なhtmlが公式サイトにあるので作成します。
<html>
  <body>
    <div id="d_clip_button" class="clip_button" data-clipboard-text="Copy Me!" title="Click to copy." style="border:1px solid black; padding:20px;">Copy To Clipboard</div>

    <script type="text/javascript" src="ZeroClipboard.js"></script>
    <script type="text/javascript">
      var client = new ZeroClipboard( document.getElementById('d_clip_button') );
    </script>
  </body>
</html>
  1. http-serverで動かす

ブラウザでローカルファイルとして開いても動きません。 http-serverの使い方はこちら*1

原理

指定したDOMの上に透明なFlashを配置してクリック操作をフックします。 そのため元のボタンのhoverやclickを指定するには調整が必要です。 イベントとCSSクラスが用意されています。 公式ドキュメントを参照してください。

またJavaScriptで元のボタンをclickしても動きません。

*1:公式サイトにはデモを試すにはgh-pagesブランチをcloneしろって書いてあるけど・・・・無い。

設定なし、コマンド一つで起動できる簡易httpサーバー http-server

nodeapps/http-serverを紹介します。

これはなに?

設定なし、コマンド一つで起動できる簡易httpサーバーです。Node.jsで実行します。

どうやって使うの?

インストール

brew install node
npm install http-server -g

起動

http-server .

ブラウザで http://localhost:8080/ を開いてみてください。 引数で指定したディレクトリをrootとして起動します。デフォルトポートは8080です。

どんなときに使うの?

次のライブラリは組み込んだhtmlファイルを直接ブラウザで開いても動きません。

HTTPサーバに配置して動かします。http-serverを使えば、現在のディレクトリで動かせます。

git diffを美しく

diff-hightlightを使ってdiffを見やすくします。

前提

Mac です。

gitインストール

brew install git

diff-highlightにパスを通す

ln -s /usr/local/share/git-core/contrib/diff-highlight/diff-highlight /usr/local/bin/diff-highlight

~/.gitconfigに追記

[pager]
        log = diff-highlight | less
        show = diff-highlight | less
        diff = diff-highlight | less

感想

  • 普段はSourceTree使っている。必要なかった
  • 2年更新されてないのに標準に取り込まれないのはどういう事情なのだろう?

PresentationとかDomainとかSeparateとか

Presentation Domain Separationとは?

Presentation Domain Separation。 プログラムをプレゼンテーションロジックとドメインロジックに分けること

Separate Domain from Presentationとは?

Separate Domain from PresentationPDSを導入するリファクタリングの名前

Separated Presentationとは?

Separated PresentationMVCPDSを強化したパターン。MVCにObserverパターンを適用する

Before:

  1. ControllerはModelを変更する
  2. Controllerは「変更したModelに対応するプレゼンテーションを更新するViewのインタフェース」を呼び出す
  3. Viewは指定されたプレゼンテーションを更新する

After:

  1. ViewはModelの変更を監視する
  2. ControllerはModelを変更する
  3. Modelは変更をObserverに通知する
  4. Viewは変更されたModelに対応したプレゼンテーションを更新する

Controllerの処理が簡単になります。 ControllerがViewを呼び出す処理は残ります。 Modelを更新せずに見た目だけを変更する(警告ダイアログを表示など)場合は、ControllerがViewを呼び出します。

なぜ大抵の単体テストがクソなのか?

James O Coplien のWhy Most Unit Testing is wasteより

最後のまとめを和訳

ツッコミ大歓迎。

Keep regression tests around for up to a year — but most of those will be 
system-level tests rather than unit tests. 

回帰テストを一年間続けよう。ただしテストのほとんどは単体テストではなくシステムテストになる

Keep unit tests that test key algorithms for which there is a broad, formal, 
independent oracle of correctness, and for which there is ascribable business 
value. 

ビジネス価値をもたらすアルゴリズム単体テストを維持しよう

Except for the preceding case, if X has business value and you can text X 
with either a system test or a unit test, use a system test — context is 
everything. 

あるビジネス価値を保証するのに単体テストシステムテストどちらも選べるならシステムテストを選ぼう

Design a test with more care than you design the code. 

テストの設計はコードの設計より念入りに行おう

Turn most unit tests into assertions. 

単体テストは(契約としての)アサーションに置き換えよう

Throw away tests that haven’t failed in a year. 

一年間失敗しなかった単体テストは捨てよう

Testing can’t replace good development: a high test failure rate suggests 
you should shorten development intervals, perhaps radically, and make 
sure your architecture and design regimens have teeth 

テストは良い開発の代わりにはならない

If you find that individual functions being tested are trivial, double-check
 the way you incentivize developers’ performance. Rewarding coverage or
 other meaningless metrics can lead to rapid architecture decay. 

カバレッジやらの意味の無いメトリックスにこだわるとアーキテクチャはおかしくなる

Be humble about what tests can achieve. Tests don’t improve quality: 
developers do. 

テストの出来ることを過信しない。テストで品質は向上しない、開発者だけがそれをなす

免責

これは個人の翻訳です。内容、正確性を保証するものではありません。

Jim Coplien情報

著書

組織パターン 新装版 マルチパラダイムデザイン

Homebrewの設定

HomebrewMacのイケテルっぽいパッケージ管理ツールコマンドラインでアプリケーションのインストールや削除が出来ます。

Homebrew自身の設定

インストール

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

環境変数PATHの設定

環境変数PATHを設定しコマンドの探索順序を変更します。 ~/.bash_profile~/.zprofileに以下の行を記述します。*1

export PATH=/usr/local/bin:$PATH

この設定をする理由

Homebrewはコマンドを/usr/local/binにインストールします。 このディレクトリはOS標準の/usr/binより優先順位が低い。

例えばHomebrewでrubyをインストールしても、OS標準のrubyが先に見つかり実行されます。

アップデート

brew update

設定確認

インストール状態をチェックするコマンドがあります。

brew doctor

Mac OSX に Homebrew をインストールする - asa nisi masaを参考に対処します。

パッケージのインストール

例えば

brew install zsh 
brew install node

cask

Homebrewはバイナリパッケージで配布されているアプリケーションをインストールできません。 Cask拡張を使います。

インストール

brew install caskroom/cask/brew-cask

パッケージのインストール

brew cask install sublime-text 
brew cask install dropbox 

*1:ログイン時に一回だけ設定されればいいのでprofileを使います。参考.bash_profileと.bashrcの違いは

プログラミングのためにその1

このエントリの意図

下のが、おもしろかったので真似します。

興味を持ったものを動かしてみる

Twitterはてなブックマークで新しいライブラリが話題になって、 APIや説明記事を読んでいるだけだと興味を維持できない。 動かしてみて新しい発見があるとやる気が維持できる。

直近ではReactJS動かしてみた。 JSXはいいんだけどメソッド名React.renderComponentがあまりしっくりこなかった。

動くサンプルをgithubにあげる

記憶力が無いので、頻繁に使わないコードはすぐ書き方を忘れてしまう。 Githubリポジトリを用意して、動いたサンプルコードを登録してる。 「書いた記憶はあるんだけど、細かいところ覚えてない」と思った時に見てる。

Githubな理由はいろんな環境から見える確率が高いから。 日立グループのようにGithubが見れない人は違うリポジトリを使うと良いと思う。

手順を説明する

単に動かすだけだと、なんで動いてるのかよくわからないし、 次に、もう少し違うことをやろうとしたら、できるかわからなくて不安。 説明する記事を書いてみる。

例えば

テディベア効果で疑問点が明確になって理解が深まる。

記事を書くのは面倒くさいので、枝葉を捨てて自分が気に入っているところに注力する気になれる。

定期的にプライベートのコードを書く

仕事でしかコードを書かないと、 学んだことを仕事のコードで試したくなる。 再利用しないのに汎用的に書いて無駄な工数使うとか。 でも色々試してみたい。

プライベートのコードを書く時間を確保して遊びのコードを書くと、ジレンマを解消できる。 自分の場合は通勤時間を使っている。

新しいライブラリも使い放題。 新しい記法も使い放題。 読めないと言われる筋合いがない! 「こいつはすげぇぜ!」ってコードが書けるようになったら仕事のコードに適用する。

起動とテストの自動化

通勤電車だとコードを書く時間が一回20分ぐらい。 アプリケーションの起動や動作確認に時間を使いたくない。

grunt

で起動して、ファイルを変更したらテストが走るぐらいまで用意しておくと、コードを書くことに集中できる。

新しい技法にチャレンジする

プログラミングするなら、知らない技法を使えるようになりたい。 通勤はプログラミング時間になったので、お風呂で技術書を読む。

最近は「JavaScriptで学ぶ関数型プログラミング

JavaScriptで学ぶ関数型プログラミング

下手くそな関数合成でも誰にも迷惑かけないのでやり放題。

TDD の Death and Rebirth まごころを君に

我が輩のTDD体験を語る

背景

ここ最近のTDDに関する話の噛み合なさっぷりよ・・・

大きな疑問

業務でxUnit使ったテストファーストやってる人はいるの?

TDDは死んでないと言うても、業務でxUnit使ったテストファーストやってる人はいるの? じゃあ、自分はどうなのか?自分語りをする。

前提

ここで話すTDD

XPの第一版にはそれ以外の要素は書いてなかった。 俺はそう刷り込まれた。 一番大事だと思う要素から話す。

「これからインスピレーションを得たもっといい方法があるよ」って話は喜んで聞きます。

我が輩の経験

3プロジェクト

  1. C言語のSDPパーサ
  2. C言語のOBEX通信モジュール
  3. C言語ツール

言語の性質は意図してません。たまたま経歴がそうだっただけです。

xUnitを使ったテストファーストの効用

よく言われる通りの効用がある

  • バグが減る
  • 設計が洗練される
  • 設計の練習になる

バグが減る

リリースまでに使う回数が増えるのでバグが減る。 手で動作確認するのに比べると100倍ぐらい多い回数実行できる。 たくさん実行すると、マルチスレッドのデッドロックを体験できることもある。

設計が洗練される

テストクラスはクライアントクラス以外のテスト対象クラスを使う視点になる。 簡易的な仕様変更になる。

下記の仕様に依存している暗黙の前提条件に気付くきっかけになる。

  • 呼び出し順
  • 初期値
  • 入力値の範囲

暗黙の前提条件を減らすと、他のプロジェクトに持って行きやすいポータブルなコードになる。

ただ、この効果は機能追加による現実の仕様変更に劣る。 テストコードは予想外の機能を追加しない。 現実の仕様変更より実践的であることはない。

設計の練習になる

プログラミング初心者の設計の練習によい。

オブジェクト指向入門の4.3 機能的トップダウンアプローチに

機能的トップダウン設計を検討した結果. この手法は重要なソフトウェアシステムの開発には
不向きであることが明らかになった. 小規模のプログラムや独立したアルゴリズムについては
トップダウン設計はなお設計の模範として有用である. また, 系統的に問題を解決できるので, 
初級プログラミングコースの教育法として役に立つことも確かである.

そして

トップダウン法では全てのシステムが最も抽象的なレベルでその第1の機能を記述できるもの
とされている. (中略)現実のシステムには頂点など存在しない。

ともある。

TDDでは

  1. テストコードで第1の機能を指定できる
  2. 機能的トップダウンアプローチが可能
  3. 系統的に問題を解決できる
  4. プログラミング初級者でも処理の設計がしやすい

この性質を利用してTDDの三角測量がなりたつ。

そしてレッド/グリーン/リファクタリングの黄金の回転をまわしながらオブジェクトを見つけ出す。 機能的なアプローチからオブジェクト指向にたどり着ける。

ただし、TDDの三角測量だけでデザインパターンを活用するオブジェクト指向設計にたどりつくのは困難。 慣れてきたらオブジェクト指向のこころを読もう。 (※これは個人の経験に基づく感想であり、効果を保証するものではありません。)

xUnitを使ったテストファーストの前提条件

第1の機能がないと始められない。

事前の設計が必要。 ERモデリングだけでは不十分。 C言語ならモジュール構成。 OO言語なら、クラス単位まで行かないまでもクラス群単位のおおざっぱなオブジェクト構成が必要。 インタフェースの名前と入出力が必要。

三角測量と逆向きになるのでTDD BootCampから実案件への移行には苦労するのではないかと思う。

もうやってない

もう6年やってない。

予算1000万の業務用WEBアプリの場合、オブジェクトの構成が決まらない。 レイヤーはプレゼンテーション、サービス、DAOぐらいで大体固定。 しかし、オブジェクト単位の仕様は画面仕様依存で決まらない。

仮に事前に設計してテストコードを書いても、 画面を見せながら仕様検討・変更する。 機能が変わるとテストコードの修正がコストになる。

画面の機能から作り始めるとユニットテストを書くマインドに切り替えるコストが高すぎる。 それよりユーザのマインドに切り替えた方が以下の良い気付きが得られる。

  • この画面は必要か?
  • 画面のレイアウトから機能が読み取りやすいか?
  • 画面を複数に分けた方がよいか?

画面の機能が8割固まるまでは手で回帰テストを回している。 この時はどの機能がどのプロダクトコードに依存しているか把握している。 どの機能を動かして確認すればよいかわかっているので、 手でやってもそれなりの効率で回帰テストが回せる。

xUnitを使った「後追い自動テスト」はやってる

ある程度機能が固まってから後追いでテストコードを書くことはある。

数年前に作ったライブラリに機能追加するときはテストコードがあるととても心強い。 例えば Alhambra というライブラリのテストはMSTestで書いている。

この手のライブラリをメンテナンスしたい時は、ライブラリのメンテナンスをしたいのではなくライブラリを使用しているプログラムを修正したい。 ライブラリの修正や動作確認に時間を使いたくない。 自動テストがあれば2年ぶりの機能追加でも、テストを実行するだけで既存機能を壊してないとわかる。

ただし、これはxUnitでEnd-to-Endの自動回帰テストを書いている。 実際のデータベースを使って実行している。 ユニットテストとしては悪いと言われているデータベースに依存したテスト。

※これは上記ライブラリの使用を推奨するものではありません。 EntityFrameworkでもLINQtoSQLでも好きなものを使ってください。

自動回帰テスト

作れるならEnd-to-Endの機能テストを自動化した方がうれしい。 テストコードが無駄にならずに内部構成を修正できる。

xUnitレベルのテストではレイヤーの追加・削除やデザインパターンの適用などで オブジェクトの構成を変更すると、かなりのテストコードを捨てたり直したたりすることになる。

結論

「xUnit使ったテストファースト」は死んだ。

あとは知らん

参考文献

XPエクストリーム・プログラミング入門―ソフトウェア開発の究極の手法オブジェクト指向入門オブジェクト指向のこころ

オブジェクト指向設計とは

オブジェクト指向という言葉には

  1. オブジェクト指向分析(OOA)
  2. オブジェクト指向設計(OOD)
  3. オブジェクト指向プログラミング(OOP)

の三つの意味があります。 オブジェクト指向初心者泣かせです。

ここではオブジェクト指向設計を説明します。

ソフトウェアの設計

ソフトウェアの設計には二つの側面があります。

  1. 作成するソフトウェアの共通部分を探し出しモジュール化する
  2. 作成するソフトウェアが将来変更される部分を抽象化し変更しやすくする

一つ目のモジュール化は構造化設計からある手法です。 オブジェクト指向設計で特に取り上げる点はありません。 ここでは二つ目の将来の変更のために抽象化することに重点を当てます。

オブジェクト指向設計

オブジェクト指向設計とは多態を実装する部分を決めることです。

多態とはオブジェクト指向言語を活用した次のものです。

  1. 変更可能な点に抽象クラス*1オブジェクト指向言語の機能)を定義する
  2. 呼び出し元のオブジェクトは抽象クラスのインタフェースを使って呼び出し、実装オブジェクトを隠す(カプセル化

抽象クラスのインタフェースにしたがっている限り実装オブジェクトを変更しても、呼び出し元オブジェクトを変更する必要が無くなります。これを実装クラスが「形を変える」ところから、「多態(ポリモフィズム)」と呼びます。

  • 呼び出し元オブジェクトと実装オブジェクトの依存関係を切り離すことができる
  • 抽象クラスと実装クラスを二つ定義するため実装の手間は増える

どこに抽象クラスを定義するか慎重に選ぶ必要があります。 そこで必要なのがオブジェクト指向設計です。*2

どうやって決めるのか?

多態を実装する部分ををどうやって決めていくのでしょうか?

オブジェクト指向設計は次の手順で進んでいきます

  1. 抽象的なオブジェクトに分ける
  2. オブジェクトを具体化する

2の具体化の作業を進めるときに変更されそうなオブジェクトを選び出します。

抽象的なオブジェクトに分ける

オブジェクト指向では作成するソフトウェアを「オブジェクト」という単位に分けます。 手続き型との違いは、状態と振る舞いを組として扱うことです。

オブジェクトは以下の性質を持ちます。

  • アプリケーションの領域を構成する要素
  • 状態(値)を持つ
  • 振る舞いを持つ

オブジェクトを具体化する

オブジェクトを具体化するとき、オブジェクトを見る観点を変えていきます。

UML モデリングのエッセンス *3 ではオブジェクトモデリングには三つの観点があると書かれています。

  1. 概念
  2. 仕様
  3. 実装

観点のレベルが下がるにつれ具体的なオブジェクトになります。

f:id:ledsun:20130515184917p:plain

概念的観点 - 概念オブジェクト

UML モデリングのエッセンス」によると

概念的観点を利用する場合、当のドメインにおける概念を表すダイアグラムを作成します。これら
の概念は、それを実装するクラスに自然に結びつきますが、直接な対応関係が存在しないこともま
まあります。実際、概念モデルは、それを実装するソフトウェアをほとんど、あるいはまったく無
視して作成されるべきです。そのため、概念的観点から書かれたダイアグラムは言語に依存しない
と考えることできます(CookとDanielsはこれを本質的観点と呼びました)。

意味が分かりません*4。個人的に概念的オブジェクトは以下のようなものと考えています。

  • 変更すれば対応できる責任*5の範囲を表現する
  • ソースコード上はクラスやクラス群(パッケージや名前空間)の「名前」で表現する

仕様的観点 - 仕様オブジェクト

  • 概念オブジェクトで定義した責任を実現する必要十分なインタフェース仕様です。
  • ソースコード上は抽象クラス*6のメソッドとプロパティで表現します。

実装的観点 - 実装オブジェクト

  • 責任を実現する具体的な処理です。
  • ソースコード上は実装クラスに定義する処理で表現します。

具体例を考える

もう少し具体的な例を考えてみましょう。

概念レベルでは、将来を含めて対応する責任を決める

同じ名前のオブジェクトでもアプリケーションによって責任が異なります。 「社員」という概念オブジェクトを例にします。

  1. 勤怠管理システム。正社員オブジェクトやパートタイマーオブジェクトが含まれ始業時間や就業時間を返すことを責任とします。「掃除のおばちゃん」オブジェクトは含まれません。
  2. 入室管理システム。「掃除のおばちゃん」オブジェクトも含まれ、入室可能時間を返すことが責任になります。

概念レベルの難しさはどこまで具体的に決めればいいかわからない点にあります。 当初勤怠管理システムとして構築されたシステムが、その後の機能追加で入室管理システムの機能も持つかもしれません。

  • 曖昧に決めると、仕様レベルを決めるときに考慮する責任が増えすぎる
  • 将来の可能性をしらみつぶしすると、いつまでも決められない

これに対するコツは、これは「対応しない」責任を決めることです。 上記の例であれば「勤怠管理システムなのでパートタイマーに対応することはあっても、掃除のおばちゃんに対応することはないだろう。」と決めます。間違えるかもしれませんが決めます。*7

仕様レベルでは、責任を実現するインタフェースを決める。

概念で対応すると決めた責任に「必要十分」なインタフェース仕様を目指します。

  • 将来変更がないオブジェクトは抽象クラスが無いとシンプルで振る舞いがわかりやすく保守性が高い
  • 将来変更されるオブジェクトに抽象クラスを定義しておけばクライアントクラスを変更せずに、実装クラスを修正・変更できる

これをオープン・クローズド・プリンシパル開放閉鎖原則*8といいます。

これを決める過程がオブジェクト指向設計です。 *9 *10

実装オブジェクトでは、今回のリリースで対応する責任を実装する。

今回のリリースで対応する責任を満たすなるべく小さく簡単な実装を目指します。 将来の変更は既に仕様オブジェクトで吸収しています。 YAGNI*11を実現できます。

これをアグニマーヤーと呼びます(うそです)。

良いオブジェクト指向設計とは?

オブジェクト指向設計が上手く行っているか見分けることはできるでしょうか?

コメントとドキュメント

良いオブジェクト指向設計では、各抽象クラスのコメントに「どういう時に実装クラスを変更するべきか」が書けます。 変更手順を書いて、変更理由に対して変更されるクラスが一つであればオープン・クローズド・プリンシパル開放閉鎖原則)が実現できています。 クラスが変更される理由が一つであればシングル・レスポンシビリティ・プリンシパル(単一責任原則)が実現できています。

オープンソース

gitのメンテナの濱野さんは曰く*12

特にオープンソースが他と違うのは「後で拡張してもらえる基礎ができている」

変更点が明確でなければ協力者は集まりません。 参加者が多いオープンソースプロダクトは良いオブジェクト指向設計ができていると言えるでしょう。

まとめ

オブジェクト指向設計では拡張できる場所を抽象クラスで表現する

*13

ただし、変更の仕方はクラスでは書けないのでコメントやドキュメントで書きます。

オブジェクト指向設計は遠い世界の偉い人が決めたものではなく、 現場のプログラマが日々ソフトウェア開発を行う中で積み重ねられた工夫から発見されたものです。 「よりきれいなソースコードを書く」努力がそこへ至る最短経路です。

参考文献

ここまで読んでよくわからなかったら「オブジェクト指向のこころ」を読んでください。おすすめです。

オブジェクト指向のこころ

*1:JavaC#では抽象クラスの代わりにインタフェースを使ったほうが設計意図が明確なります。

*2:DIコンテナを使えば、すべてのクラスを抽象クラスで定義しDIコンテナで実装クラスを設定することは可能です。しかし、初期の実装コストは大きくなり、どの実装オブジェクトが使われているか意識しながらデバッグする必要があり保守性が下がります。設計が効果を挙げているとはいえません。どこを変更可能にするか決める必要があります。

*3:第2版まで、第3版には書かれていません

*4:「CookとDanielsはこれを本質的観点と呼びました」と書かれていますが、引用元文献読むとだいぶ違うことが書いてあってびっくりします。マーチン・ファウラーは人のアイデアにもう一捻りして中二的な名前をつけるのがすごく上手いです。

*5:責任て単語を見るたびに「オブジェクトの責任」ってなんだよ意味分からねえ!と思うのですが、Oxford Dictionariesによると英語の「Responsiblity」の意味は「誰かの承認を得ることなく判断、行動できる能力や機会」だそうです。

*6:JavaC#ではインタフェースを使います。Rubyには抽象クラスの文法はなく代わりにDucktypingをサポートしています。ですが、Ruby 1.9.3 デザインパターン速攻習得 ストラテジ[Strategy][Design Pattern] - 酒と泪とRubyとRailsとのFormatterクラスのように実装は可能です。やる意味があるかどうかはソースコードで表現したいか規約で表現したいかチーム毎に異なると思います。

*7:何を責任にするかは、KKD(間と経験と度胸)で決めるしかありません。この段階で厳密性を求め時間をかけても後で修正することが多いです。設計・実装を進めると作るアプリケーションへの理解が深まり新しいオブジェクトを発見できるようになります。新しいオブジェクトを見つけたら「手戻り」を恐れずに概念レベルのオブジェクトを見直しましょう。最終的にそれが「手戻り」を減らします。また、いつでも戻れるつもりで次の段階へ進みましょう。ソフトウェアの設計とは概念と実装の間を行ったり来たりしながらアプリケーションと扱う領域を理解していく作業です。経験を積めば最初から正しくオブジェクトを見つけられる確率は上がります。しかし100%にはなりません。ソフトウェアはのコピーコストは0(!)です。二度同じソフトウェアを作ることはありません。

*8:Bertrand Meyerが提唱した設計の良し悪しを測る原則。「ソフトウェアの構成要素は、拡張に対して開いていて、修正に対して閉じていなければならない。」という何を言っているのか分からないと思うが、俺にも何を言っているのかわからない、言葉遊びが過ぎる原則です。

*9:実際は「将来の変更を予測しない」戦略も可能です。最初のアプリケーションをすべて実装クラスで実装します。仕様変更により修正する際に「修正が入った箇所は将来も変更される可能性が高い」と仮定し抽象クラスを追加します。ただしクラス構成を変更する高度なリファクタリング技術が必要です。

*10:もしリファクタリング技術が無いのであれば、一回設計・実装した後に再び概念レベルから設計しなおせば、一度目の開発で得た知識に基づいたより適切なオブジェクトモデルを得ることができます。

*11:エクストリーム・プログラミングで提唱された原則。「You ain't gonna need it」の略、直訳すると「それは必要にならない」。実際に必要になるまで実装しないことで、使われないソースコードを作るムダ、使われないソースコードをテストするムダ、複雑なソースコードを保守するムダ、リリースが遅くなる時間のムダを減らそうとする考え方です。

*12:Linus君がボクを後継者に指名した理由より

*13:厳密には型付けの強いクラスベースオブジェクト指向言語での話です。RubyのようなDucktypingをサポートした動的型付け言語やJavaScriptのようなプロトタイプベースオブジェクト指向言語の場合はもう少し議論が必要だと思います。

JavaScriptのreduce関数が便利、けど動きが理解しづらいのでサンプルを列挙

JavaScriptのreduce関数がとても便利なのです。

たとえば

ユニークはこんな感じ

var unique = function(array) {
    return array.reduce(function(a, b) {
        if (a.indexOf(b) === -1) {
            a.push(b);
        }
        return a;
    }, []);
};

関数の外部に結果を格納する変数を作らなくていいのが良いです。 *1

メジャーな例

集計

reduce関数は結構有用っていうお話 - あと味 に合計、最大値、最小値、平均の例があります。

ユニーク

How to get an array of unique values from an array containing duplicates in JavaScript? - Stack Overflow には重複除去の例はここからパクリました。

ほかにも例を挙げていきます。

ジャグ配列をシリアライズ

配列の配列を結合できます。

[[0, 1, 2], [3,4]].reduce(function(a, b) {
    return a.concat(b);
}, []); // [0, 1, 2, 3, 4]

複数の配列を結合したいときに、配列の配列に入れておけば一撃でひとつの配列にまとめられます。 その後、同様にreduce関数でユニークすれば、重複を気にせずに値を集められるので便利です。

key-value配列からオブジェクトを作る

key-valueの配列をオブジェクトに展開

[{key: 0, value: 'abc'}, {key: 1, value: 'XYZ'}].reduce(function(a, b) {
    a[b.key] = b.value;
    return a;
}, {}); // {0: "abc", 1: "XYZ"}

「サーバから受け取るデータはオブジェクトの配列。だけど、keyが重複しないことは分かっているので クライアントサイドではオブジェクトして扱いたい」時に便利です。

戻すのはちょっと面倒

var hash = {0: "abc", 1: "XYZ"};
Object.keys(hash)
    .map(function(key) {
        return { key: key, value: hash[key]};
    }); // [{key: 0, value: 'abc'}, {key: 1, value: 'XYZ'}]

オブジェクトの値を列挙する関数があるとうれしいです。

カウント

配列に含まれる要素を種類ごとに数えます。

['a', 'a', 'b'].reduce(function(a, b) {
    a[b] = a[b] ? a[b] + 1 : 1;
    return a;
}, {}); // {a: 2, b: 1}

じゃんじゃん値を入れておいてあとで集計したり数の多い種類順に並べ変えたりするときに便利です。

*1:結果を格納する変数をプライベートにしようとするとリビーリングモジュールパターンを使って、さらに関数の中に入れないといけないので。

『キック・アス ジャスティス・フォーエバー』感想 キック・アスがクズすぎてドン引き

注意:ネタバレ&酷評です。











【ネタばれ】「○ホバの証人です♡」 キック・アス/ジャスティス・フォーエバー/ユーザーレビュー - Yahoo!映画

青年はトレーニングによってムキムキの本当に強い男となってセックスまでこなす成長を見せて、
ボンクラで冴えない等身大の高校生に共感したい観客を置いてけぼり

がすべて。脚本がひど過ぎる。

キック・アッスがひたすらクズ

  1. 昔暴れたときの興奮が忘れられないのでヒーローを続ける
  2. ムキムキマッチョに成ったけど一人じゃ怖いので仲間とつるむ
  3. コスプレして夜の街をドヤ顔で練り歩いてグルーピーの女とやりまくり
  4. 15才の少女を仲間に引き込もうとしつこい勧誘を繰り返す

3年経った彼はもう完全なDQNです。

1の頃の「力は無くても勇気がある」姿は微塵もありません。 力が無くても勇気(と幸運)で「正義の味方」に成るから、現実の俺らにはできないから

さすがディオ!おれたちにできない事を平然とやってのけるッ
そこにシビれる!あこがれるゥ!

なのに・・・こいつは実世界によく居る空手やボクシング習って、街の不良のリーダーになって調子こいてるDQNです。 どうやって感情移入すれば? これは親父が殺されて当たり前。自業自得。前作だったらヒット・ガールに殺されるチンピラ案件です。

話の構図

話し全体の構図をまとめると

  1. 不良グループの抗争が激化
  2. コネで引退した殺し屋をカムバックさせようと画策
  3. 金のある敵対チームが先に本物の殺し屋を雇った
  4. 殺し屋さん(15才)、止むを得ずカムバック

やってることが完全に悪役。これをヒーローを自称してやるので馬鹿すぎ。 気づいてヒーローやめればいいのに、ラストはもっと強くなって本当のヒーローになるんだですよ。

抗争をつづけたいだけのDQNです。 こんな糞DQNにキスするヒット・ガールにも幻滅。 はいはい、モテルのはいつでもムキムキマッチョマンですよ。 さっさと捨てなさいこんなクズ。

オマージュ成分

最後なマザー・ファッカーとの決着が

これはヤムチャの分!(ドン!)
  1. 近親者を殺されて怒りに目覚めたムキムキマッチョマンが金持ちボンボンをボコボコにする
  2. 殺したくないのに相手主導で死んじゃう
  3. バラバラになったけど生きてる

どうみても孫悟空 vs フリーザです。 第3作でスーパーフリーザになるのがもう丸分かり。

あのね、ドラゴンボール孫悟空は「正義」なんて言ったことねーよ、バーカ。 「ジャスティス」連呼する映画で孫悟空オマージュやってんじゃねーよ。

if

「力のダークサイドに堕ちたヒット・ガールをキック・アスの勇気で目覚めさせる。」とかもっとベタな脚本にしてくれよ・・・。

あるいは・・・ 【ネタばれ】「帰ってきたヒットガール」 キック・アス/ジャスティス・フォーエバー/ユーザーレビュー - Yahoo!映画

スターズ&ストライプス大佐があっけなく退場するのももったいなさすぎ。まるで『スーパー!』
で「シャラップ、クラ~イム!!」と叫んで誰彼かまわず鉄拳制裁していたクリムゾンボルト
みたいに宗教に目覚めて「正義」を貫こうとする彼は、「キックアス」シリーズのテーマでもある
「ヒーローとは何か」という考察に大きなヒントを与えてくれるキャラになったはず

と、あるように、キック・アスの思惑以上にスターズ&ストライプス大佐が暴走して抗争が激化してく描写がもっと丁寧だったら また違ったかも。

結局は、同批評にあるように

マザーファッカーことクリス・ダミーコやトッドは、
正義の味方キックアスになれなかった俺たちだ

だから「オタクが、リア充とその彼女にボコボコにされるだけの映画」を見るのはやるせないんですよ!

3

これがすべて3に向けてのヒーローの悪堕ち展開だとしたら神脚本