投稿日 2010年8月25日 水曜日 カテゴリ Wiki, ソフトウェア開発, ツール 投稿者 syojiコメントは受け付けていません。 

ciklone(サイクロン)のリリース前に、自分たちが実際にプロジェクトで利用して、 ciklone(サイクロン)を改善してきました。 サイクロン開発プロジェクトの最後の6ヶ月間にソフトウェアのライフサイクルをくり返します。すべての機能を使いながら、「BTS/SCM/プロジェクト管理に必要なことができ、効率的か」、を試験してきました。

開発しながら、実際の現場で利用することで、バグがどんどん見つかったし、使いにくい点、改善すべきことがよく見えます。自分たちが作ったシステムを自分たちのプロジェクトで使う最大のメリットだと思います。

よくいわれる「ドッグフードを食べる」ことをやったことで、BTSやバージョン管理に必要な機能と必要ではない機能を切り分けることができ、開発の現場に必要なツールとして、 ciklone(サイクロン)がリリースできました。

「ドッグフードを食べる」とは、 自社で作ったソフトウェアを実際の業務で利用して、評価することです。有名なのはマイクロソフトのドッグフードです。また、Google ケータイも同様です。

シリコンバレー101[Googleケータイ販売で、Googleは何を変えたいのか?]

Googleでは、常に新しい製品や技術を実験しており、短期間でフィードバックと提案を集めるために社員にテストを依頼している。われわれは、これをドッグフーディング(“eating your own dogfood”から)と呼んでいる。

この「ドッグフードを食べる」経験は、お客様からのフィードバックと同じくらい重要なモノになったと感じています。 ベータ版が完成した頃から、特定のお客様にもご利用頂きました。 ドッグフードを食べ、お客様からのフィードバック経験によって、ciklone(サイクロン)はチームにとって、すばらしいソフトウェアを開発するためのプラットフォームとして十分に機能しました。

オープングルーヴでは、ciklone(サイクロン)を最大限に活用して頂き、他社の利用方法を紹介することで、みなさんのチームのソフトウェア開発を効率化とコストダウンしていく支援をさせて頂きます。

オープングルーヴでは、Wikiサイトを使って、実証済みのガイドとベストプラクティス(と考えられる)を提案していきます。

Wiki.ciklone.com

Wiki.ciklone.com

投稿日 2010年5月6日 木曜日 カテゴリ ZABBIX, ツール 投稿者 sugimotoコメント(0) » 

tt_graphic

sugimotoです。

Webサービスの運用ではサーバーやサービスの監視が欠かせないと思います。 オープングルーヴでも突然のサーバー停止や、サービス停止、定期処理の失敗などに備え、監視システムを使って障害発生時にはメール通知するようにしています。

GW中に出かける途中で、監視システムとして運用しているZABBIXから警告メールが飛んできました。

パソコンを持たずに出かけていて、長距離バスに乗っていたんですが、iPhoneにインストールしていたTouchTermを使いました。

TouchTermはiPhoneアプリとして動作するSSHターミナルです。 アプリケーション内で作成したSSHキーのパブリックキーをあらかじめサーバーにおいておくことで、サーバーにSSH経由でログインすることができます。

iPhoneの画面とキーでも十分にサーバー上のログや状況の確認が可能で、問題の箇所をすぐに特定、対応することができました。

iPhoneの画面とキーを使っての操作のため、長時間操作や大きな設定変更は難しいですが、緊急時の一時的な対応には十分使えるツールです。

ソフトウェアエンジニアのためのバグトラッキングシステム : Ciklone

ソフトウェアエンジニアのためのバグトラッキングシステム

ソフトウェアエンジニアのためのバグトラッキングシステム

投稿日 2010年3月16日 火曜日 カテゴリ Wiki, プロジェクト管理 投稿者 syojiコメントは受け付けていません。 

社内での仕事術 #1

社内での仕事術について紹介したいと思います。 ソフトウェア開発は一つ一つの案件をプロジェクトとしてすすめるため、はじめにプロジェクトチームを作ります。

プロジェクトチームは提案内容や得意分野により主担当がかわります。 プロジェクトでは複数のメンバーが集まって仕事をすることになるため、情報共有はとても重要。

オープングルーヴではどのようにしているのか、ツールを使った情報共有#1として「Wikiを仕事で使う」について書きたいと思います。

Wikiとは「ウィキ」と言いますが、ウェブブラウザを使ってページの作成・編集ができるコンテンツサービスです。一人でメモ書きとして利用している方もいると思いますが、けっこう書込のためにページを開く。という動作が面倒になりがちということはあると思います。

弊社で利用する場合は、複数のメンバーでプロジェクトを進める上でガリガリ書き込みます。これはあるルールを決めているためですが、そのルールのおかげでWikiがなくてはならいツールになっています。

ルール : プロジェクトの成果物(中間成果物)はWikiで一元管理

Wikiのメリット

  • 会議をしながら議事録(画面を見ながらみんなで確認)
  • 顧客向けの議事録
  • 顧客との打合せで出来る議事録はWikiをPDF化やメールで共有
  • 設計書(技術メモ、検討メモ)、技術方式に対する他チームのコメント
  • 開発環境構築メモなど、開発に関わる情報源
  • 世代管理機能、ページ単位で履歴を確認し、追加された・削除されたがカラー表示される(下図)
Wikiによる備忘録・ルール

Wikiによる備忘録・ルール

Wikiの世代管理(履歴)

Wikiの世代管理(履歴)

などなど、メールでも同様のことはできますが、プロジェクトのポータルとして利用でき、大切な文書が埋もれない・・・(メールは探すのが大変) プロジェクトメンバーで使うことで、ガリガリ更新され、すぐにWikiが必須の情報共有ツールになりました。

デメリットは…

しかし、良い面ばかりではないです。新しく加わったメンバーなどにとっては使ったことがないツールのため、慣れるまで大変かも知れません。 「Wikiを知らない人や初めて使う人にとって慣れ親しんだインターフェースではない。」ということです。ファイル名がない、特殊なフォーマット・・・

Wikiを仕事で使いたい人へ セキュアで、1プロジェクト無料のcikloneを評価してください。
投稿日 2008年7月18日 金曜日 カテゴリ capistrano 投稿者 sugimotoコメント(0) » 

ウェブサービスを運営していると必ず、「定期的にやるべきこと」ってありますよね。

  • 古くなったログの処理
  • 負荷のかかる処理をバックグラウンドで定期的に
  • 外部データの取得

たいていの場合、手動でやるわけにはいかないので、シェルスクリプトなんかをcronで実行するわけですが、サービスの拡大に伴って、サーバ数が増えた場合、どうするんでしょう?

全てのサーバーにcronを設定すると、全てのサーバーで同じ設定になっていることを確認しなければなりません。 cronの設定自体を自動化すれば、全てのサーバーで同じ設定になっていることが保証されますが。。。

そんなわけで、Ruby on Rails のdeplyツールとして使われているCapistranoを使って、cronをCapistranoサーバーから一括実行することにしてみました。

以下の手順では、ssh接続などの設定は省いています。
(実はこの設定でかなり苦労したところではありますが。。)

1. Capistranoのインストール

gemでインストールします。

> gem install capistrano
> cap --version
Capistrano v2.4.0

2. capfileの作成

capfile はCapistranoの「レシピ」と呼ばれていて、Capistranoが実行するタスクの内容を記述します。Rubyをベースにした独自言語ですが、Rubyを知っている人にとってはほとんど同じです。

role :app, "app-server1", "app-server2"

desc "テスト用のタスク"
task "test_for_cron", :roles => :app do
  run "sh /path/to/cron/script.sh"
end

3. cronの設定

cronの設定時には、-f オプションを使って、capfileの場所を明示的に示しましょう。

> crontab -e
0 12 * * * cap -f /path/to/capfile test_for_cron > /dev/null 2> /var/log/capistrano/error.log

12時になったら、2つのアプリケーションサーバー

  • app-server1
  • app-server2

でスクリプト /path/to/cron/script.sh が実行されるようになりました。

なお、/path/to/cron/script.sh は各サーバー内に存在する必要があります。 サーバーが増えたら、role に足すだけで、そのサーバーでも実行されることになります。

ただ実行するだけでは面白くないので、実行したらメールを送ります。

4. メール送信用タスクの作成

メールはlocalhost で送りますが、Capistranoっぽく、localhostにリモート接続してみます。 (というか、どこにも接続せずにtaskを実行する方法が分からんだけ?)

desc "cronを実行したらメールを送信"
task "send_email", :host => "localhost" do
  fn = "/tmp/filename.txt"
  addresses = "email@address.com"
  time = Time.now.strftime("%Y/%m/%d %H:%M:%S")
  put @result.join("\n"), fn, :mode => 400
  run "mail -s 'cron has done on #{time}' #{addresses} < #{fn}"
  run "rm -f #{fn}"
end

5. cron用のタスクで結果を取得するよう変更

先ほど作成したタスクを修正して、実行結果をメール送信用タスクに渡せるようにします。

複数のサーバーで実行されることを想定して、結果は配列で渡します。

desc "テスト用のタスク"
task "test_for_cron", :roles => :app do
  @result = []
  @result.push capture("sh /path/to/cron/script.sh")
end

6. タスク実行後にメール送信タスクをhookする

Capistranoには start, finish, before, after などのtriggerがあり、task実行の前後などに特定のタスクを実行することができます。 capflieのtaskの前に以下の設定を追加します。

on :finish, :send_email, :except => [:send_email]

これで、タスクの実行後に標準出力をメールで送信してくれます。

参考

投稿日 2008年5月19日 月曜日 カテゴリ CMS, MODx 投稿者 sugimotoコメント(0) » 

MODxのsnippetを使うと、コンテンツを管理、表示するための便利な機能を作ることができます。

snippetはMODx本体と同じように、PHPで書きます。zeera document searchのページで使う、「次のページへ」のリンクを作るsnippetを作りました。

modx オブジェクトの関数

modx オブジェクトはMODxがページを表示するときに必ず作成されるオブジェクトです。 DocumentParserクラスから作られます。DocumentParserクラスはドキュメントを表示する機能を提供するクラスですが、それ以外にも

  • データベースから任意のドキュメントを取ってくる
  • ドキュメントのパラメータの取得
  • プレースホルダーの作成

などのAPIを提供しています。たとえば、次のようなAPIがあります。

  • getParentIds($id, $height, $parent) : 親ドキュメントのIDをrecursiveに取得
  • getChildIds($id, $height, $children) : 子ドキュメントのIDをrecursiveに取得
  • getDocument($id, $fields, $published, $deleted) : ドキュメントを取得してarrayで返します。
  • getDocuments($id, $fields, $published, $deleted) : $id に一括取得するドキュメントのIDをarrayで指定します。
  • getActiveChildren($id, $sort, $dir, $fields) : 子ドキュメントを一括取得します。
  • makeURL($id, $alias, $args, $scheme) : フレンドリーURLを返します。
  • getPlaceholder($name) : プレースホルダーに設定された値を取得。
  • setPlaceholder($name, $value) : プレースホルダーを作ります。

snippetの作成

管理画面で、[リソース]->[リソース管理]->[スニペット]->[新規作成]で、snippetを作成します。

今回は[次へ]リンクを作成するためのDocumentPager snippetを作りました。

親IDが同じドキュメントのうち、指定したソート順で次(前)のドキュメントをプレースホルダーにします。

[[DocumentPager? &sortBy='id' &sortDir='ASC' &returnId='xx']]
[+dpPrev+] [+dpNext+]

パラメータ

  • sortBy: 次、前のドキュメントを探す順番
  • sortDir: 次、前のドキュメントを探す順番
  • returnId: 最初と最後のドキュメントで[戻る]リンクの先ドキュメントID

作られるPlaceholder

  • [+dpPrevId+]: 前のドキュメントのID
  • [+dpPrevPagetitle+]: 前のドキュメントのページタイトル
  • [+dpPrevAlias+]: 前のドキュメントのAlias
  • [+dpPrevLink+]: 前のドキュメントへのリンク
  • [+dpNextId+]: 次のドキュメントのID
  • [+dpNextPagetitle+]: 次のドキュメントのページタイトル
  • [+dpNextAlias+]: 次のドキュメントのAlias
  • [+dpNextLink+]: 次のドキュメントのリンク

snippet

< ?php
class DocumentPager {
    var $id, $sort, $dir, $return;
    function DocumentPager($sort = "id", $dir = "ASC", $returnId = null) {
        global $modx;
        $this->id = $modx->documentIdentifier;
        $this->sort = "id";
        $this->dir = "ASC";
        $this->returnId = $return;
        $this->getDocuments();
        $this->return = $modx->getDocument($returnId);
        $this->setPlaceholders();
    }
    function getDocuments() {
        global $modx;
        $parent = $modx->getParent();
        $docs = $modx->getActiveChildren($parent['id'], $this->sort, $this->dir);
        $found = false;
        for ($i = 0; $i < count($docs); $i++) {
            if ($this->id == $docs[$i]['id']) {
                $this->next = $docs[$i + 1];
                break;
            }
            $this->prev = $docs[$i];
        }
    }
    function setPlaceholders() {
        global $modx;
        if ($this->prev != null) {
            $modx->setPlaceholder('dpPrevId', $this->prev['id']);
            $modx->setPlaceholder('dpPrevPagetitle', $this->prev['pagetitle']);
            $modx->setPlaceholder('dpPrevAlias', $this->prev['alias']);
            $prevLink = "<a href='". $modx->makeUrl($this->prev['id']) ."'>« ". $this->prev['pagetitle'] ."</a>";
            $modx->setPlaceholder('dpPrev', $prevLink);
        } elseif ($this->return) {
            $prevLink = "<a href='". $modx->makeUrl($this->return['id']) ."'>« ". $this->return['pagetitle'] ."に戻る</a>";
            $modx->setPlaceholder('dpPrev', $prevLink);
        }
        if ($this->next != null) {
            $modx->setPlaceholder('dpNextId', $this->next['id']);
            $modx->setPlaceholder('dpNextPagetitle', $this->next['pagetitle']);
            $modx->setPlaceholder('dpNextAlias', $this->next['alias']);
            $nextLink = "<a href='". $modx->makeUrl($this->next['id']) ."'>". $this->next['pagetitle'] ." »</a>";
            $modx->setPlaceholder('dpNext', $nextLink);
        } elseif ($this->return) {
            $nextLink = "<a href='". $modx->makeUrl($this->return['id']) ."'>". $this->return['pagetitle'] ."に戻る »</a>";
            $modx->setPlaceholder('dpNext', $nextLink);
        }
    }
}
new DocumentPager($sortBy, $sortDir, $returnId);
?>

参考

次ページへ »