arclamp 鈴木さんの「アジャイルがダメだと思う7つの理由」を読んでの感想と反論を。
1.全体スケジュールにコミットできない
アジャイルはタイムボックス型(一定期間で棚卸しをして、それを繰り返す)のマネジメントをする。だから、全体としての計画は立てられない。「だって、最初に全ての機能を洗い出せないでしょ」というのは分かる、分かるけど全体の計画は立てないといけない。経営者は顧客やVCと全体の計画にコミットしなきゃいけないんだ。そのときに「やってみなきゃ分からない」なんて言えるわけでない。
アジャイル開発でも全体スケジュールにコミットすることは可能です。スケジュールが遅れだしたら鈴木さんがお金を追加投入して開発メンバーを増やせばいい。(あまりおすすめできない悪手だけど。優先度の低い機能を開発スコープから落とすか、期間を延ばす方がより理想的な手段だと思う。短い期間であれば残業を増やすとかでもいいでしょう)
今までは一括請負契約で開発会社だけに負わせていたリスクを、発注側も負う必要が出てきただけなんじゃないかなあ。
2.アーキテクチャ上の無駄が生じる
ソフトウェアの構造や構成は工程が進むほどに修正しにくくなり、ずっと残る。だから、アーキテクチャ設計は慎重に全体を考えながらやらなきゃいけない。でも、アジャイルだから全体が決まらないとか言って、なんとなく流行の技術を使って作ってないか?そうやって作られた物は負債になる。
たとえばtwitterはRuby On Raisで初期構築したシステムをScalaに置き換えた。その無駄を超えるだけの成功が見えているからいいさ。でも、大抵の場合はそうじゃない。近視眼的なアーキテクチャが足かせとなり、生産性が落ちて、エンジニアのモチベーションを下げる。
もちろん、うまくアーキテクチャを組めているアジャイルチームだってある。でも、それはメンバーに優秀なアーキテクトがいただけのことでしょ。プロセスがどうあれ、アーキテクチャはちゃんと考えるべきなんだ。
開発を始める前にアーキテクチャを決めるのはアジャイル開発でも一般的に行われていると思います。ただし、そのアーキテクチャが正しくなかった時に備えて事前アーキテクチャ設計は必要最低限であるべきです。
4.変化ヲ抱擁スルために固定化している
変化に適応するというのは全ての開発チームが取り組むべき課題だ。
現在のソフトウェア工学は不十分なので構造的に柔軟性を得ることはとても難しい。テーブル構成をがしがし変えてもコードが付いてくれば良いけど、そんなことはできないから、アジャイルはプロセスを柔軟にして変化に適応すると考えた。これは悪くないアイデアだ。
でも、完璧じゃない。大抵の場合は要員を固定化するという前提が付くのです。ロールベースでないとノウハウが人に貯まり、どんどん引き離せなくなる。そして、同じ事を繰り返すメンバーはモチベーションが下がり、いつか辞めることになるのだ。
要員の固定化はまあ困りますよね。 弊社では、開発メンバーを少しずつ入れ替えることで要員の固定化に対応しており、うまく回っているように見えます。なので、要員の固定化をもってアジャイルがダメだというのには同意できません。
7つの理由のうちの3, 5, 6, 7はアジャイル開発とあまり関係ないので割愛しますね。
今回の鈴木さんのエントリは、あるアジャイル開発の現場でのアーキテクトという立場からのアジャイルに対して抱いた印象として興味深く読ませていただきました。
開発側へリスクを負わせようとした発注側とそれに対してディフェンシブになってしまった開発側、というよく見かける構図になってしまったのが敗因なのかなあ。
インストールに必要なので事前にcmakeをインストールしておく。
brew install cmake
シンボリックリンクを張って /Developer 経由でヘッダファイルなどを参照できるようにする。
cd /
sudo ln -s /Applications/Xcode.app/Contents/Developer
以下の手順でOpenCVフレームワークをビルドする。masterブランチだとビルドに失敗したので、2.4ブランチでビルドを行った。
cd ~/src
git clone https://github.com/Itseez/opencv.git
cd opencv/
git checkout 2.4
cd ..
python opencv/ios/build_framework.py ios
新規のiOSプロジェクトを作成し、そこへ先ほどビルドしたOpenCVフレームワークを追加する。
まずは新規のiOSプロジェクトを作成し、画面左のプロジェクトナビゲータの「Frameworks」を右クリックし、「Add Files to “プロジェクト名”…」を選択し、~/src/ios/opencv2.framework を選んで「Add」ボタンを押す。
次に、${プロジェクト名}-Prefix.pch へ以下のコードを追加。
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#endif
OpenCVの処理はC++で記述する必要があるので、OpenCVの処理を行うソースファイルの拡張子を「*.m」から「*.mm」へ変更しておく。
OpenCVによる画像編集のサンプルとして、カラーの画像をモノクロにしてみる。
OpenCVで画像を操作するには事前にUIImageオブジェクトをMatオブジェクトへ変換する必要がある。
まずは、UIImageとMatを相互変換するcvMatFromUIImage:メソッドとUIImageFromCVMat:メソッドをViewController.mmへ定義する。
- (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
CGFloat cols = image.size.width;
CGFloat rows = image.size.height;
cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels
CGContextRef contextRef = CGBitmapContextCreate(cvMat.data, // Pointer to data
cols, // Width of bitmap
rows, // Height of bitmap
8, // Bits per component
cvMat.step[0], // Bytes per row
colorSpace, // Colorspace
kCGImageAlphaNoneSkipLast |
kCGBitmapByteOrderDefault); // Bitmap info flags
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
CGContextRelease(contextRef);
CGColorSpaceRelease(colorSpace);
return cvMat;
}
-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
CGColorSpaceRef colorSpace;
if (cvMat.elemSize() == 1) {
colorSpace = CGColorSpaceCreateDeviceGray();
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// Creating CGImage from cv::Mat
CGImageRef imageRef = CGImageCreate(cvMat.cols, //width
cvMat.rows, //height
8, //bits per component
8 * cvMat.elemSize(), //bits per pixel
cvMat.step[0], //bytesPerRow
colorSpace, //colorspace
kCGImageAlphaNone|kCGBitmapByteOrderDefault,// bitmap info
provider, //CGDataProviderRef
NULL, //decode
false, //should interpolate
kCGRenderingIntentDefault //intent
);
// Getting UIImage from CGImage
UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return finalImage;
}
写真をモノクロにしてみる。
- (IBAction)doDidPress:(id)sender {
UIImage *image = [UIImage imageNamed:@"neko_tsubaki_blue_car.jpg"];
// 写真をモノクロにする
cv::Mat srcMat = [self cvMatFromUIImage:image];
cv::Mat greyMat;
cv::cvtColor(srcMat, greyMat, CV_BGR2GRAY);
UIImage *greyImage = [self UIImageFromCVMat:greyMat];
self.imageView.image = greyImage;
}
こんな感じ。

これまでは、ワード単位での前後移動がきっとあるんだろうなあと思いながらもチマチマとCtrl-fとCtrl-bで前後移動してたんだけど、スリーアウトどころかサウザンドアウトぐらいになったのでいい加減調べた。
ワード単位での移動は「forward-word」と「backward-word」でそれぞれ「ESC f」と「ESC b」に割り当てられているみたい。Macだとoption fとoption-bでも行けるんだけど我が家のoptionキーはずいぶんと押しづらい所にあるので「Ctrl + ]」と「Ctrl + [」に設定することにする。(MacじゃなければAlt-fとAlt-bで行けたんだけどなあ)
ということで.zshrcに以下を追記。bindkey -e などの後ろに置かないといけないので注意すること。
# Ctrl + o、Ctrl + i でワード単位で前後に移動
# ^[ で始まる他のキーバインドがあると動作がひっかかるようになるので
# 事前にすべて削除しておく
bindkey -rp "^["
bindkey "^[" backward-word
bindkey "^]" forward-word
何年かぶりにEclipseを使っているのだけど、生のキーバインドがアレすぎるので以下の設定を施してEmacs風のキーバインドにしたった。
Eclipseの設定画面から「General - Keys」を選択して以下をセットする。
Line Start: ⌃A
Line End: ⌃E
Previous Column: ⌃B
Next Column: ⌃F
Join Lines: ⌃J
Delete to End of Line: ⌃K
Line Down: ⌃N
Line Up: ⌃P
Delete Previous Word: ⌃W
Delete Next: ⌃D
Delete Previous: ⌃H
♪ 銀のマックに 唇よせて だまって見ている 虹色のグルグル マックは何にも 言わないけれど マックの気持ちは よく分かる マック可愛いや 可愛いやマック
日本でITというのは、「今ある仕事をコンピュータシステムに乗せること」だ。私はそれを商売にしているから余計実感するが、そんなことをしても何も変わらない。便利になることと余計面倒になることが半々でトータルでは大して変わりがない。
ITが本当に力を発揮するのは、「先にコンピュータシステムを作って、その回りに人を配置して仕事にする」時だ。コンビニチェーンもDELLもアマゾンもそういうやり方でITを使っている。
そういう使い方をするとITは本当に力を発揮する。日本がこれから競争していくのは、そういうタイプのITだ。身の回りにある「なんちゃってIT」でこれを判断してはいけないと思う。
”先日はNSOperationQueueを使って非同期処理を行ったけど、今回はGCDのディスパッチキューを使って非同期処理を行ってみる。
まずはディスパッチキューを取得し、
// ディスパッチキューを取得
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
取得したディスパッチキューと非同期にしたい処理をブロックにしたものを引数にdispatch_async関数を呼び出す。
// 非同期処理を呼び出す
dispatch_async(queue, ^{
NSLog(@"=> Operate!!");
});
これだけで非同期に処理を呼び出すことができます。
// ディスパッチキューを取得
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 非同期処理を呼び出す
dispatch_async(queue, ^{
NSLog(@"=> Operate!!");
});
iOSアプリケーションで非同期に処理を行うためにはオペレーションキューを利用します。
ファイルのダウンロードのような完了するまでに時間のかかる処理をメインスレッドで実行してしまうと、処理が完了するまで画面が固まってしまいユーザビリティがが著しく低下してしまいます。これを避けるため、完了までに時間のかかる処理はメインスレッドとは別のスレッドを作成して非同期に処理を行う必要があります。iOSにはいくつかの非同期処理を行う方法がありますが、ここではオペレーションキューを使った方法を解説します。
非同期に処理を行うためには、非同期に行いたい処理をオペレーション(NSOperation)として定義し、そのオペレーションをオペレーションキュー(NSOperationQueue)へ追加してやります。追加されたオペレーションはオペレーションキューにより直ちに別スレッドで実行されます。
以下はオペレーションキューのサンプルです。ブロックを用いてNSOperationのサブクラスであるNSBlockOperationを生成し、それをNSOperationQueueへと追加することで非同期処理を呼び出しています。
// オペレーションキューを作成
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// オペレーションを作成
// (NSBlockOperationはNSOperationのサブクラス)
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"=> Operate!!");
}];
// オペレーションをオペレーションキューへ追加
[queue addOperation:operation];
// オペレーションキューをリリース
[queue release];
NSOperationQueueのaddOperationWithBlock:メソッドを使うことでもう少し短く書くことができます。
// オペレーションキューを作成
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// オペレーションとなるブロックをオペレーションキューへ追加
[queue addOperationWithBlock:^{
NSLog(@"=> Operate Again!!");
}];
// オペレーションキューをリリース
[queue release];
UIの更新処理等メインスレッドから呼び出す必要のあるメソッドをバックグラウンドのスレッドから呼びたい場合は、NSOperationQueueクラスのmainQueueメソッドを呼ぶことでメインスレッドに対応づけられたオペレーションキューを取得します。
次のサンプルでは画面上のテキストフィールドの更新を行うためにメインキューを利用しています。
// オペレーションキューを作成
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// ウェブページを取得し...
NSURL *url = [NSURL URLWithString:@"http://example.com/"];
NSString *response = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:nil];
// 取得したデータを画面へ表示
// (UIの更新はメインスレッドから行う必要がある)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.textView.text = response;
}];
}];
// オペレーションキューをリリース
[queue release];
UIScrollViewの使い方がよく分かってなかったので、練習がてらにこんな感じの画像ビューアをUIScrollViewで作ってみた。

まずはUIScrollViewのコンテント領域をセットする。viewDidLoadメソッドなどでUIScrollViewのcontentSizeに適当な値をセットする。今回のサンプルでは320x320の写真を5枚分貼りたかったので、コンテント領域は幅320x高さ1600とした。
- (void)viewDidLoad
{
...
// スクロールビューのcontentSizeの高さを画像5つ分の高さにする
self.scrollView.contentSize = CGSizeMake(320, 320 * 5);
...
}
次はURLより画像を読込み、読み込んだ画像をUIImageViewへセットしてUIScrollViewへaddSubview:する。これもviewDidLoadで処理を行う。
画像のダウンロードをメインスレッド上で行うと画面が固まってしまうため、ダウンロード処理がバックグランドで行う。逆にUIの更新処理メインスレッドから呼ばれる必要があるため、UIScrollViewへのUIImageViewの追加はメインスレッドで行う。
NSURL *url = [NSURL URLWithString:[_imageUrls objectAtIndex:i]];
// 非同期に画像をロードする
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
// UIの更新はメインスレッドで行う必要がある
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *view = [[UIImageView alloc] initWithFrame:CGRectMake(0, i * 320, 320, 320)];
view.image = [UIImage imageWithData:data];
[self.scrollView addSubview:view];
});
});
大まか流れはこんな感じ。UIScrollViewはすごく面倒な印象があったんだけど、今回行った作業は実質contentSizeに値をセットするだけと以外と簡単に使えたのでUIScrollViewへの親近感がすこし高まりました。
以下サンプルコード。
@implementation ViewController {
NSArray *_imageUrls;
}
@synthesize scrollView = _scrollView;
- (void)clearImages
{
for (id view in self.scrollView.subviews) {
[view removeFromSuperview];
}
}
- (IBAction)loadImages
{
[self clearImages];
for (int i = 0; i < _imageUrls.count; i++) {
NSURL *url = [NSURL URLWithString:[_imageUrls objectAtIndex:i]];
// 非同期に画像をロードする
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
// UIの更新はメインスレッドで行う必要がある
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *view = [[UIImageView alloc] initWithFrame:CGRectMake(0, i * 320, 320, 320)];
view.image = [UIImage imageWithData:data];
[self.scrollView addSubview:view];
});
});
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (!_imageUrls) {
// 画像URLを初期化
_imageUrls = [[NSArray alloc] initWithObjects:
@"http://distilleryimage4.s3.amazonaws.com/a0d2941e6bd611e181bd12313817987b_7.jpg",
@"http://distilleryimage8.s3.amazonaws.com/f1f2e06260d511e19896123138142014_7.jpg",
@"http://distilleryimage4.s3.amazonaws.com/b7611c7e5e1a11e19e4a12313813ffc0_7.jpg",
@"http://distilleryimage1.s3.amazonaws.com/8fcfda5a610911e1b9f1123138140926_7.jpg",
@"http://distilleryimage5.s3.amazonaws.com/2821cf2e60ed11e19e4a12313813ffc0_7.jpg",
nil];
}
// スクロールビューのcontentSizeの高さを画像5つ分の高さにする
self.scrollView.contentSize = CGSizeMake(320, 320 * 5);
// _imageUrlsをロード
[self loadImages];
}
...
こんなんでした。すっかり忘れてた。
for (id view in subviews) {
[view removeFromSuperview];
}
こんな感じで。NSDataの形式でダウンロードしてからUIImageに変換するする。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
// 画像はきめうち
NSURL *url = [NSURL URLWithString:@"http://img.yubin-nenga.jp/sozai/img/jp12t_et_0255.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
return cell;
}