2011/10/28

CI(継続的インテグレーション)超入門:Jenkinsのススメ

「CI(継続的インテグレーション)超入門:Jenkinsのススメ」
に行ってきた話

内容的には、
  • CIって何?
  • jenkinsいいよー
  • 商用CIツールの宣伝
といった感じでした。

CIの説明

ソフトウェアの調理方法を。

ビルドって何?ということで、そういやあまり考えたことがなかったな
ビルド自体はには2つの意味があることを。
  • 実行可能なソフトウェアを作ること
  • 実行可能なソフトウェア自体
の意味があるんだね。

「ビルドをビルドする」は
「料理を料理する」と同じ意味合いと言われてあぁって思った。

本題ですが

提供する側が必要なことってことで

「いつでも同じビルドを届けることができることが重要」

確かに、「あの時のビルドの環境作ってほしいんだけど」
ってよく言われるので、よく分かる。
背景を説明すると、リリースと開発が平行してて

リリースした製品でxxxなバグが出たから、再現環境作りたいんだけど

とか。

そんなときに、再現可能なビルドをしとくことが重要になると。
再現可能ってのはどういうことかというと
  • いつでも誰でもビルドできる
  • ユーザーに届けたものと同じものをビルドできる

これを実現するために
必要なことが、全部とはいわないが一般的に

  • ソース
  • レシピ
  • 手順書
  • スクリプト
  • ビルドマシン
  • コンパイラ
  • ビルドツール各種
とか。

話は変わって開発環境の話ですが

開発者はサンドボックスでビルドすることが望ましい

じゃあビルドはどこでやるの?というと

ビルド専用マシンを立てましょう ということに。

んで、ビルドマシンが使うソースはどこに? というと

リポジトリが必要でしょう。
  • サンドボックス
  • リポジトリ
  • ビルドマシン

この3つを連携させて動作させると

サンドボックス」で開発して
リポジトリ」にコミットして
ビルドマシン」がビルドする

これを繰り返していくことがCIですね。
キレイな環境で作ってあげることで
安定したビルドが出来上がると。

ただ実際に開発していくと、普通ソフトは壊れます。
実行時バグとか、他の機能に干渉するとか原因はいっぱいあるけど

壊すタイミングは何かってことになると
コミットしたときにビルドの成果物が壊れるんですね。

これを避けるための、コミットポリシー

大体コミットするときのお作法として決められているような内容ですが
  • 自分のところでビルドしてエラーが無いことを確認する
  • バディビルドを実施する
  • コードレビュー
とか。

バディビルドをビルドサーバーにやらせるっていいなぁと思った。
実際にビルドサーバーがビルドするんだから、同じ環境で試しにビルドして
結果を教えてくれるなら、それが言いに決まってる。

まえに、名古屋のイケメンが話していた

自分用のブランチにコミットすると、ビルドサーバーがビルドしてくれて
OKなら、メインのリポジトリにコミットしてくれるっていう仕組みが
この流れと同じだと思うんだけど、環境構築大変そうだなぁと思ったり。

最後に、リリーストレインの話。

駆け込みコミットはお控えください
ビルドは定刻通りに発車しております。

これがすべてか。

変なタイミングのコミットは、本当に問題の元。
落ち着いて、良いリズムの開発を心がけたいものです。

まぁ一日一回のビルドだときついと思うので
デイリービルドするなら、プレビルド的な
コミットを監視してるやつも一緒に立てて上げるのがいいよなぁと思う。

jenkinsの話

なんでCIするのって話から。

インテグレーションを頻繁に行って、結合作業時に「え!?」ってならないように。

最近のCIの定義でいくと
ビルドを頻繁に行うことで、退行をできるだけ早く検出する
コードの品質検査を定期的に。

どっちにもいえることで

「機械ができることは機会にやらせればいいじゃない」

と。
僕も、ものすごくそう思います。

利点とか

リグレッションは、そのまま直訳で「後退、退化」なんですね。
うちの会社では、デグレとか読んでるソレです。
テストの場合は、前に出来たことが出来なくなることを見つけるので
リグレッションの方がしっくりくるかなぁとか思ったり。

リグレッションが早く見つかる
常にテストを書けている場合にかぎらず
成果物が常時できているので、コミットフックのビルドをかけるだけでも
結構違うと思う。

頻繁に成果物が生成されることで、ここからxxxの現象が出てるって
確認しやすくなります。

頻繁にリリースできるため、バグを早い段階で見つけることが出来
「あーそんな変更あったね(笑)」
を減らすことができます。

見える化
jenkinsさんは、情報収集が得意なので、各種情報が
jenkinsに集まり、ダッシュボードをみれば現在の状況
各種ビルドをみれば変更点やテスト状況など、みることができます。

属人制を減らす。
ビルドサーバーさえあればビルドできるはずなので
急にチームから人が消える
病気で倒れる
など、あったとしても大丈夫と。
あと、ビルドサーバーが開発のためのお手本環境になるので
各メンバーの開発環境を統一化するのにも一役かってると思います。
前に自分用Jenkinsいれた!って呟いたら、突っ込まれたのを思い出しますw

追跡可能性
バイナリが作られた原因(なんのコミット?)が分かる。
リグレッションの原因分析の手助けに使える。

かるくまとめ
CIは、手間を減らす というよりは
「人が人のやるべきことをやるために」
機械にできることは、できるだけ機械にやらせるのが良いと。

Jenkinsさんの紹介ですが

いいところをざっと。
  • 導入設定が簡単
  • 各種OSパッケージ
  • プラグインによる拡張機能
  • 450個以上のプラグインで多用な環境に
  • プラグインを自作することもでき、さらに柔軟な環境へ
ビルド以外に出来ることの紹介
  • FindBugs 静的解析かな?
  • コードカバレッジの計算と表示
  • チケットシステムとの連携
  • 分散ビルド
チケットシステムとの連携は、コミットログから飛ぶぐらいかな
って思ってたけど、jenkins自体がチケットに書き込みに行ったりするんですね。

プレテステドコミットとかは、難しそうだなぁって話してたら
かおるんに、TFSなら簡単に出来るよって言われたり。
流れてきには、
  1. 開発者が自分のトランクにコミットする
  2. jenkinsがそれを見つけて、ビルドとテスト
  3. OKならjenkinsからメインのリポジトリにコミットする
これならメインのリポジトリを破壊する可能性がグッと減りますね。

どの出来ることもそうだけど、機械には得意な事と不得意なことがあるので
得意なことはいっぱいやってもらって、あまったことだけ人間がやるのが
ベストだなぁと思います。

最後に、「具体的にはじめるには?」ってことで
  • 千里の道も一歩から、
  • ビルドの自動化
  • テストの自動化
  • コンポーネントを一つづつ
  • 自動化の島をたくさん作って大陸へ進化
コツコツが大切なんだなぁと改めて。
(苦手だ・・・)

最後の話は、未発表製品なので割愛。
静的解析ツールの話でした。

ディスカッションですが
全体的にまだ何もいれてない!ってところと
まぁそれなりに〜ってところが半々って感じだったのかな
(一番前に座ってたのであまり会場がみえなかった)

どの質問に関しても、共通して言えることは

Small start Small win

の繰り返し。

こんてぃにゅあす うぃん

です。

何事も、出来そうだなぁとおもったことをとりあえずやってみると
なんか見えると思います。

以上、勉強会レポでした。

勉強会後、
@heroweenさんとか、@shinyaa31さんとか、
@711fumiさんとか、@cyobichiさんとオムライス食べました。

いろんな話が出たけど、共通して言えることは
勉強会大好き と 開発者が楽しいって思えることを出来ないと
人は集まらないし離れていくってことでした。

さて、締めの言葉は、

「俺には前しかみえない(腹肉的な意味で)」

やっぱりこれですね。

ではでは。

boost.statechart を使ってみた。

まず普通に使ってみた。

例題は、
letsboost::statechart

ここを参考にさせていただきました、いつもお世話になってますw

まずシンプルな奴。

#include <iostream>

// boost
#include <boost/statechart/state_machine.hpp> // ステートマシン
#include <boost/statechart/simple_state.hpp> // ステート
#include <boost/statechart/event.hpp> // イベント
#include <boost/statechart/transition.hpp> // 遷移
#include <boost/statechart/in_state_reaction.hpp> // イベントアクション
#include <boost/mpl/list.hpp> // イベントテーブル

using namespace boost;

namespace gate
{


namespace events
{
// イベント
class InsertedCoin : public statechart::event< InsertedCoin > {};
class PassedThrough : public statechart::event< PassedThrough > {};
};

// 初期ステートのみを前方宣言
namespace states { class Locked; }
// ステートマシン < マシン, 初期ステート >
class Gate : public statechart::state_machine< Gate, states::Locked >
{
// メインスレッド

// イベントハンドラ
};


namespace states
{
class Locked;
class Unlocked;

// ロック状態 ステート < ステート, マシン >
class Locked
: public statechart::simple_state< states::Locked, Gate >
{
public:
// イベントを引数に取る
void alarm( const events::PassedThrough& event )
{
std::cout << "ウウウウウウウウウ ウウウウウウウウウウウウ" << std::endl;
}

// イベントテーブル
typedef boost::mpl::list<
// 遷移
// InsertedCoinイベントで、Unlockedへ遷移
statechart::transition < events::InsertedCoin, states::Unlocked >,

// リアクション
// 通過イベントで、アラームを発生 ステートは維持。
statechart::in_state_reaction<
events::PassedThrough, Locked, &Locked::alarm >
> reactions;
};

// ロック状態 ステート < ステート, マシン >
class Unlocked
: public statechart::simple_state< states::Unlocked, Gate >
{
public:
void thankYou( const events::InsertedCoin& event )
{
std::cout << "ありがとう・・・ありがとう・・・" << std::endl;
}

// イベントテーブル
typedef boost::mpl::list<
// InsertedCoinイベントで、Unlockedへ遷移
statechart::transition < events::PassedThrough, states::Locked >,

// リアクション
// コイン挿入イベントで、感謝を。ステートは維持
statechart::in_state_reaction<
events::InsertedCoin, Unlocked, &Unlocked::thankYou >
> reactions;

};

} // namespace states

} // namespace gate


int main()
{
gate::Gate gate;

// ステートマシンの初期化
gate.initiate();

// とりあえずイベントを発生させてみる
gate.process_event( gate::events::InsertedCoin() );
gate.process_event( gate::events::PassedThrough() );
gate.process_event( gate::events::PassedThrough() );
gate.process_event( gate::events::InsertedCoin() );
gate.process_event( gate::events::InsertedCoin() );

return 0;
}


内容は、そのまんまで、使ってるクラスは、大きくわけて三種類
  • イベント
  • ステートマシン
  • ステート
ステートを使ってるので当たり前だけど
ステートマシン が ステート を イベントをトリガに遷移していく動作をさせます。

イベント
boost::statechart::event テンプレート
イベントクラスがこのテンプレートクラスを継承する
テンプレートには、イベントクラスを渡す。

イベントハンドラの登録
boost::statechart::in_state_reaction テンプレート
イベントクラス、イベントハンドラを持っているクラス、イベントハンドラ を設定する。
後述するイベントテーブルに渡すことで、イベント発生時に、イベントハンドラが呼び出せる

イベントをトリガに別ステートへ遷移する
boost::statechart::transition テンプレート
イベントと遷移先のステートを設定すると、イベント発生時に指定したクラスに遷移する

ステートマシン
ステートマシン
boost::statechart::state_machine テンプレート
ステートマシンクラスが、このテンプレートクラスを継承する
テンプレートには、ステートマシンクラスと、初期ステートを設定する

ステート
ステート
boost::statechart::simple_state テンプレート
ステートクラスが、このテンプレートクラスを継承する
テンプレートには、ステートクラスと、ステートマシンを渡す。

イベントテーブル
boost::mpl::list テンプレート
ここに、イベントハンドラや、状態遷移 を登録する。

といったところです。

んで、いろいろ考えて、自分なりにごにょごにょやったのがこっち。
最終的にステート使う人が、書く量を減らせるようにというのを目標にしたんですが
いまはソースが一枚になってるので、結構ごちゃごちゃしてます。

イベントは分かる人が実装して、普通使う人が
ステートだけを実装していくような用途を想定しています。

/**
* @file statechart.cpp
* @author riskrisk
* @date Fri Oct 28 17:31:30 2011
*
* @brief statechart sample
*
* commandline : g++ -lboost_thread -lpthread statechart.cpp -o statechart.bin
*/

#include <iostream>
#include <queue>

// boost
#include <boost/thread.hpp>
#include <boost/bind.hpp>

#include <boost/any.hpp>

#include <boost/statechart/state_machine.hpp> // ステートマシン
#include <boost/statechart/simple_state.hpp> // ステート
#include <boost/statechart/event.hpp> // イベント
#include <boost/statechart/transition.hpp> // 遷移
#include <boost/statechart/in_state_reaction.hpp> // イベントアクション
#include <boost/mpl/list.hpp> // イベントテーブル

using namespace boost;

namespace gate
{

// イベント
namespace events
{
// コイン挿入
// イベント( event< Event > )
class InsertedCoin : public statechart::event< InsertedCoin > {};
// イベントハンドラ
class InsertedCoinHandler
{
public:
// イベントハンドラ( onXxx( const Event& ) )
virtual void onInsertCoin( const InsertedCoin& event ) = 0;
// ハンドラ登録( in_state_reaction< Event, HandlerClass, EventHandler > )
typedef statechart::in_state_reaction< InsertedCoin,
InsertedCoinHandler,
&InsertedCoinHandler::onInsertCoin > EventHandler;
};
// 遷移( transition< Event, TransitionState > )
template< class TARGET_STATE >
class InsertedCoinTransition
: public statechart::transition< InsertedCoin, TARGET_STATE >
{};

// 通過
// イベント( event< Event > )
class PassedThrough : public statechart::event< PassedThrough > {};
// イベントハンドラ
class PassedThroughHandler
{
public:
// イベントハンドラ( onXxx( const Event& ) )
virtual void onPassThrough( const PassedThrough& event ) = 0;
// ハンドラ登録( in_state_reaction< Event, HandlerClass, EventHandler > )
typedef statechart::in_state_reaction< PassedThrough,
PassedThroughHandler,
&PassedThroughHandler::onPassThrough > EventHandler;
};
// 遷移( transition< Event, TransitionState > )
template< class TARGET_STATE >
class PassedThroughTransition
: public statechart::transition< PassedThrough, TARGET_STATE >
{};

// イベントのシリアライズ用
typedef boost::shared_ptr< statechart::event_base > EventPtr;
};

// 初期ステートのみを宣言
namespace states { class Locked; }
// ステートマシン( state_machine < StateMachine, InitialState > )
class Gate : public statechart::state_machine< Gate, states::Locked >
{
private:

bool isExit_; // スレッドの終了
std::queue< events::EventPtr > events_; // イベントのシリアライズ

public:
// コンストラクタ
Gate()
: isExit_( false )
{}

// スレッドの終了要求
void requestExit()
{
isExit_ = true;
}

// メインスレッド
void run() {

std::cout << "machine entry" << std::endl;

// ステートマシンの初期化
initiate();

// メインループ
while( !isExit_ ) {
// イベントハンドラ
eventHandler();

// 登録イベントが無い場合は、少し待ってみる
if( isNoEvent() ) {
usleep( 100 * 1000 ); // 100msec
}
}

std::cout << "machine exit" << std::endl;
}

// イベントが無い場合に真を返す
bool isNoEvent() { return events_.empty(); }

// イベントの追加
void addEvent( events::EventPtr event ) {
events_.push( event );
}

private:

// イベントハンドラ( とても適当 )
void eventHandler() {
if( !isNoEvent() ) {
process_event( *( events_.front() ) );
events_.pop();
}
}
};

// ステート
namespace states
{
class Locked;
class Unlocked;

// ロック状態( simple_state< State, Machine > )
class Locked
: public statechart::simple_state< Locked, Gate >
, public events::PassedThroughHandler
{
public:

// イベントハンドラ
// 通過
virtual void onPassThrough( const events::PassedThrough& event )
{
// アラームを鳴らそう
std::cout << "ウウウウウウウウウ ウウウウウウウウウウウウ" << std::endl;
}

// イベントテーブル
typedef mpl::list<

// 遷移
// InsertedCoinイベント -> Unlockedステート
events::InsertedCoinTransition< Unlocked >,

// イベントハンドラ
// PassedThroughイベント
events::PassedThroughHandler::EventHandler
> reactions;
};

// アンロック状態( simple_state< State, Machine > )
class Unlocked
: public statechart::simple_state< Unlocked, Gate >
, public events::InsertedCoinHandler
{
public:

// イベントハンドラ
// コイン挿入
virtual void onInsertCoin( const events::InsertedCoin& event )
{
std::cout << "ありがとう・・・ありがとう・・・" << std::endl;
}

// イベントテーブル
typedef mpl::list<
// PassedThroughイベント ->、Unlockedステート
events::PassedThroughTransition< Locked >,

// イベントハンドラコール
// InsertedCoinイベント
events::InsertedCoinHandler::EventHandler
> reactions;
};

} // namespace states

} // namespace gate


int main()
{
gate::Gate gate;

// ざっくりとイベントを登録
gate.addEvent( gate::events::EventPtr( new gate::events::InsertedCoin ) );
gate.addEvent( gate::events::EventPtr( new gate::events::PassedThrough ) );
gate.addEvent( gate::events::EventPtr( new gate::events::PassedThrough ) );
gate.addEvent( gate::events::EventPtr( new gate::events::InsertedCoin ) );
gate.addEvent( gate::events::EventPtr( new gate::events::InsertedCoin ) );


std::cout << "start" << std::endl;

// ステートマシンを起動
boost::thread gateThread( boost::bind( &gate::Gate::run, &gate ) );

// ちょっとだけ待っておく
sleep( 1 );

// 終了要求
gate.requestExit();
// 終了待ち
gateThread.join();

std::cout << "end" << std::endl;

return 0;
}

// EOF