PSP:Personal Software Process – Lesson7

Lesson3, Lesson5, Lesson6で開発したプログラムをもとに、Lesson7用の仕様を追加していきます。
これにより、自身の計測値さえあれば、開発に対する多少のブレを含めた上での見積時間と見積総行数を見積もることが出来ます。
※ なお、Lesson5, 6に関しては、クラスとして利用出来るよう変更してください。

Lesson7: 「線形回帰パラメータと予測区間を計算する」
● プログラム仕様スクリプト
ASGKIT PROG7を参照のこと

● 実行結果
線形回帰パラメータと予測区間を計算する

rxy: 0.954497
r2: 0.911064
tail area: 0.000018
β0: -22.552533
β1: 1.727932
yk: 644.429384
range: 230.001974
UPI: 874.431357
LPI: 414.42741

● ソースコード

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * PSP Lesson7: 
 * 線形回帰パラメータと予測区間を計算する
 * 
 */

public class Program7 {

	private int size;
	private double[] data1;
	private double[] data2;
	private double sumData1;
	private double sumData2;
	private double avgData1;
	private double avgData2;
	private double powData1;
	private double powData2;
	private double sumMulti;
	private double beta0;
	private double beta1;
	private double r;
	private double r2;
	private double y;
	private double tailArea;
	private double p;
	private double x;
	private double deviation;
	private double ran;
	private double range;
	private double upi;
	private double lpi;

	public static void main(String[] args) {

		Program7 pg7 = new Program7();

		try {
			File f = new File(args[0]);
			FileInputStream fi = new FileInputStream(f);
			BufferedReader br = new BufferedReader(new InputStreamReader(fi,
					"UTF-8"));

			// 分析対象となるデータ行を指定する
			int data1 = Integer.parseInt(args[1]);
			int data2 = Integer.parseInt(args[2]);

			// 分析対象のLOC
			long num = Long.parseLong(args[3]);

			LinkedList<Double> linkData1 = new LinkedList<Double>();
			LinkedList<Double> linkData2 = new LinkedList<Double>();
			String line;
			while ((line = br.readLine()) != null) {
				String[] strrec = line.split("t");
				linkData1.add(Double.parseDouble(strrec[data1 - 1]));
				linkData2.add(Double.parseDouble(strrec[data2 - 1]));
			}
			pg7.size = linkData1.size();

			pg7.data1 = new double[pg7.size];
			pg7.data2 = new double[pg7.size];

			// 合計値を計算する
			pg7.sumData1 = pg7.calcSum(linkData1);
			pg7.sumData2 = pg7.calcSum(linkData2);

			// 合計値から平均値を計算する
			pg7.avgData1 = pg7.calcAvg(pg7.sumData1);
			pg7.avgData2 = pg7.calcAvg(pg7.sumData2);

			// X, Yの二乗の合計値、X*Yの合計値を計算する
			pg7.powData1 = pg7.calcSumPow(linkData1);
			pg7.powData2 = pg7.calcSumPow(linkData2);
			pg7.sumMulti = pg7.calcSumMulti(linkData1, linkData2);

			// β1 を計算する
			pg7.beta1 = pg7.calcBeta1();

			// β0 を計算する
			pg7.beta0 = pg7.calcBeta0();

			// R, R2 を計算する
			pg7.r = pg7.calcR();
			pg7.r2 = pg7.calcR2();

			// tail areaを計算する
			pg7.tailArea = pg7.calcTail();

			// yを計算する
			pg7.y = pg7.calcY(num);

			// xを計算する
			pg7.x = pg7.calcX();

			// 標準偏差を計算する
			pg7.deviation = pg7.calcDeviation();

			// Rangeを前計算する
			pg7.ran = pg7.calcRan(num);

			// Rangeを計算する
			pg7.range = pg7.calcRange();

			// UPIを計算する
			pg7.upi = pg7.calcUPI();

			// LPIを計算する
			pg7.lpi = pg7.calcLPI();

			System.out.println("rxy: " + pg7.changeScale(pg7.r, 6));
			System.out.println("r2: " + pg7.changeScale(pg7.r2, 6));
			System.out.println("tail area: "
					+ new DecimalFormat("0.#######").format(pg7.changeScale(
							pg7.tailArea, 8)));
			System.out.println("β0: " + pg7.changeScale(pg7.beta0, 6));
			System.out.println("β1: " + pg7.changeScale(pg7.beta1, 6));
			System.out.println("yk: " + pg7.changeScale(pg7.y, 6));
			System.out.println("range: " + pg7.changeScale(pg7.range, 6));
			System.out.println("UPI: " + pg7.changeScale(pg7.upi, 6));
			System.out.println("LPI: " + pg7.changeScale(pg7.lpi, 6));

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 合計値を計算する
	 * 
	 */
	private Double calcSum(LinkedList<Double> linkArray) {

		double sum = 0;
		for (Iterator i = linkArray.iterator(); i.hasNext();) {
			sum = sum + Double.parseDouble(i.next().toString());
		}

		return sum;

	}

	/**
	 * 平均値を計算する
	 * 
	 */
	private Double calcAvg(Double sum) {

		return sum / size;

	}

	/**
	 * X, Yの2乗合計値を計算する
	 * 
	 */
	private Double calcSumPow(LinkedList<Double> linkArray) {

		double sum = 0;
		for (Iterator i = linkArray.iterator(); i.hasNext();) {
			sum = sum + Math.pow(Double.parseDouble(i.next().toString()), 2);
		}

		return sum;

	}

	/**
	 * X*Yの合計値を計算する
	 * 
	 */
	private Double calcSumMulti(LinkedList<Double> linkData1,
			LinkedList<Double> linkData2) {

		double sum = 0;
		int i = 0;
		while (linkData1.size() != 0 || linkData2.size() != 0) {
			data1[i] = linkData1.pop();
			data2[i] = linkData2.pop();

			sum = sum + (data1[i] * data2[i]);
			i++;
		}

		return sum;

	}

	/**
	 * β1 を計算する
	 * 
	 */
	private Double calcBeta1() {

		double num1 = (sumMulti - (size * avgData1 * avgData2));
		double num2 = (powData1 - (size * avgData1 * avgData1));

		return num1 / num2;

	}

	/**
	 * β0 を計算する
	 * 
	 */
	private Double calcBeta0() {

		return avgData2 - (beta1 * avgData1);

	}

	/**
	 * R を計算する
	 * 
	 */
	private Double calcR() {

		double num1 = (size * sumMulti) - (sumData1 * sumData2);

		double num21 = (size * powData1) - (sumData1 * sumData1);
		double num22 = (size * powData2) - (sumData2 * sumData2);

		return num1 / (Math.sqrt(num21 * num22));

	}

	/**
	 * R2 を計算する
	 * 
	 */
	private Double calcR2() {

		return r * r;

	}

	/**
	 * Tail Area を計算する
	 * 
	 */
	private Double calcTail() {

		double num1 = r * Math.sqrt(size - 2);
		double num2 = Math.sqrt(1 - r * r);

		Tdist tDist = new Tdist();
		tDist.start(num1 / num2, size - 2);
		p = tDist.getP();

		return (double) (1 - 2 * p);

	}

	/**
	 * Y を計算する
	 * 
	 */
	private Double calcY(long num) {

		return beta0 + beta1 * num;

	}

	/**
	 * PからXを計算する
	 * 
	 */
	private Double calcX() {

		RevTdist RevTdist = new RevTdist();
		RevTdist.start(0.0, size - 2, 0.35);
		double x = RevTdist.getX();

		return x;

	}

	/**
	 * 標準偏差を計算する
	 * 
	 */
	private Double calcDeviation() {

		double sum = 0;
		for (int i = 0; i < data1.length; i++) {
			sum += Math.pow((data2[i] - beta0 - beta1 * data1[i]), 2);
		}

		double dist = sum / (size - 2);
		double dev = Math.sqrt(dist);

		return dev;

	}

	/**
	 * Rangeを前計算する
	 * 
	 */
	private Double calcRan(long num) {

		double num1 = Math.pow(num - avgData1, 2);

		double num2 = 0;
		for (int i = 0; i < data1.length; i++) {
			num2 += Math.pow(data1[i] - avgData1, 2);
		}

		double sq = 1 + (double) 1 / size + (double) (num1 / num2);

		return Math.sqrt(sq);

	}

	/**
	 * Rangeを計算する
	 * 
	 */
	private Double calcRange() {

		return x * deviation * ran;

	}

	/**
	 * UPIを計算する
	 * 
	 */
	private Double calcUPI() {

		return y + range;

	}

	/**
	 * LPIを計算する
	 * 
	 */
	private Double calcLPI() {

		return y - range;

	}

	/**
	 * 四捨五入を行う
	 * 
	 */
	private double changeScale(double val, int i) {

		BigDecimal bd = new BigDecimal(val);
		return bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue();

	}

}

Perl, Python, PHPのベンチマークを計測しました

Perl, Python, PHPのベンチマークを計測しました。

=====
1. 環境 及び シナリオ

  1. 実行環境
  2. 実行環境は以下である。
    Platform: Linux(CentOS 5.6)  @sakura VPS
    Memory: 512 MB
    CPU: 仮想2Core

  3. プログラミング言語
  4. 実行・考察を行うプログラミング言語はLAMP構成として通常扱われるスクリプト言語の以下を使用する。
    ・Perl
    ・Python2.4
    ・PHP5.2

  5. 実施シナリオ
  6. No. 演算 概要
    1 i = i + 1 加算
    2 i = i – 1 減算
    3 i = i * i 乗算
    4 i = i / 3 除算
    5 i = i % 3 剰余
    6 s = “Hello,World!” 文字列代入
    7 i = len(string) 文字列数
    8 s = random() ランダム(指定なし)
    9 s = random(1, 100) ランダム(指定あり)
    10 IF (s = “Hello,World!) 文字列判断
    11 IF (i = 5) 数値判断
    12 IF (i = TRUE) Bool値判断
    13 IF文ネスト 条件分岐ネスト
    14 Switch文 ループ(Switch)
    15 While文 ループ(While)
    16 For文 ループ(For)
    17 Foreach文 ループ(Foreach)
    18 print “Hello, World!” 文字列出力
    19 関数呼び出し 関数
    20 File Open, Close オープン, クローズ
    21 File read 1行毎の読み込み
    22 File Write 1行毎の書き込み

    実行時のループ回数は、10000000回ループさせる。但し、実行に時間の掛かり過ぎるシナリオ等に関しては回数を減らす事とする。例外シナリオは以下である。
    No.17: 100000回 PHPにおいて例外が発生する為。
    No.18: 10000回 標準出力に文字列出力する為、処理
    対象のprint文の計測が行いづらい為。

  7. 実施前考察
  8. 本レポートで実行対象となるプログラミング言語はそれぞれ実績のある言語であり、Webサービス開発においてよく使用されている。私はPerl, PHPの経験はあるがPythonは今回が初めての使用となる。
    Perlは全体的に処理が速く、ファイル操作に長けているイメージがある為、全般的にPerlのパフォーマンスが良いと考えている。
    PHPは全体的に処理が遅いイメージがある。特にrandom()やファイル操作が遅いイメージがある。
    Pythonを使用するのは今回が初めてである為、あまりイメージが出来ないが、処理速度的にはPerlと同様の計測値となると予測する。

    よって、 ほぼ全シナリオでPerl < Python < PHP の順番で処理コストが高くなると考える。

  9. 実施方法
  10. 以下の要領で実施を行う。

    1. プログラミング言語毎に22個のプログラムを作成する。
    2. 実施シナリオ毎のアルゴリズムは同一とする。実行時間計測アルゴリズムに関しては後述する。
    3. 言語によっては命令が存在しないケースがある為、その場合は代替(同等)機能を使用する。No.16のPythonにおけるFor文等。
    4. Perlに関しては、ミリ秒単位を取得する組み込み関数がない為、Time::Hiresモジュールを使用する。
    5. シナリオ毎に10回実行し、実行結果として平均値を記録する。その際のコマンドは以下ように実行する。(以下はPythonでの例)
      # loop=1; while [ $loop -le 10 ]; do loop=`expr $loop + 1`; ./check1.py >> result/check1; done;
    6. eの実行結果から平均値を出力するスクリプトを実行し、平均値を得る。平均値出力スクリプトは以下である。
      ====
      #! /usr/bin/perl
      open(IN, $ARGV[0]);
      while (<IN>) {
      $total += $_;
      $count++;
      }
      close(IN);
      print $total / $count . “n”;
      ====
  11. 実行時間計測方法とサンプルコード
  12. 言語毎の実行時間計測は以下のように行う。

    以下では、各言語のシナリオ1のプログラムである。

    Perl
    ====
    #!/usr/bin/perl
    use Time::HiRes;

    my $starttime = Time::HiRes::time;
    for($i=0; $i<10000000; $i++) {
    }
    my $looptime = Time::HiRes::time – $starttime;
    my $starttime = Time::HiRes::time;

    my $j=0;
    for(my $i=0; $i<10000000; $i++) {
    $j = $j + 1;
    }
    print Time::HiRes::time – $starttime – $looptime . “n”;
    ====

    Python
    ====
    #!/usr/bin/python
    import time

    starttime = time.clock()
    for i in range(10000000):
    pass

    looptime = time.clock() – starttime
    starttime = time.clock()

    j = 0
    for i in range(10000000):
    j = j + 1

    print time.clock() – starttime – looptime
    ====

    PHP
    ====
    <?php
    $starttime = microtime(true);
    for($i = 0; $i<10000000; $i++) {
    }
    $looptime = microtime(true) – $starttime;
    $starttime = microtime(true);

    $j=0;
    for($i=0; $i<10000000; $i++) {
    $j = $j + 1;
    }
    echo microtime(true) – $starttime – $looptime . “n”;
    ?>
    ====

2. 実行

  1. 実行結果
  2. 以下が実行結果である。シナリオ毎に最も良いタイムに橙色にしている。また、特徴的な値となった結果は赤色とし、次項以降で考察を行う。なお、単位は全て”秒”である。

    演算 Perl Python PHP
    i = i + 1 0.8 1.04 0.44
    i = i – 1 0.81 1.13 0.45
    i = i * i 0.82 1.3 0.46
    i = i / 3 0.74 1.56 0.73
    i = i % 3 0.84 1.47 0.5
    s = “Hello,World!” 0.87 0.65 0.93
    i = len(string) 1.12 1.64 1.8
    s = random() 0.22 2.05 1.73
    s = random(1, 100) 0.95 27.65 2.86
    IF (s = “Hello,World!) 0.89 0.51 0.78
    IF (i = 5) 0.94 0.49 0.44
    IF (i = TRUE) 0.97 1.08 0.37
    IF文ネスト 0.60 2.05 1.67
    Switch文 251.03 2.81 2.01
    While文 1.13 1.64 0.77
    For文 0.67 1.18 0.91
    Foreach文 0.02 0.01 0.06
    print “Hello, World!” 1.2 0.47 0.96
    関数呼び出し 1.74 1.52 1.56
    File Open, Close 69.01 61.46 80.04
    File read 2 1.46 3.54
    File Write 2.02 4.94 23.18

3. 考察

実行前の考察では、全てのシナリオでPerlが優位だと考えていたが、結果は異なっていた。
この考察としてはPerlの時間計測用の関数において、ミリ秒単位で計測が出来なかった為、
Time::Hiresモジュールを使用した事が影響している可能性がある。
但し、Perlならではの優位性が示されているシナリオもある為、現状のまま考察を進める事とする。

  1. randint()
  2. Pythonのrandint()が非常に遅い結果となった。Perlと比較すると約27倍の差である。調査したところ、Perl, PHPのrand()は組み込み関数であるのに対し、Pythonのrandint()は標準モジュールでの提供であった。
    また、randint()よりもchoice()を使用した方が速いとの情報があった為、変更し実行計測したところ、
    27.65 → 12.36 と半分以下の実行結果となった。但し、Perl, PHPと比較するとまだ遅い結果であった。
    この事からC等で記述される組み込み関数はやはり速いということが分かった。

  3. switch文
  4. Perlのswitch文が異常な程、遅い結果となった。これはPerlには組み込みでswitch文がない為、標準モジュールのSwitchを使用した事が影響していると考えられる。最速であったPHPと比較するとその差は100倍である。
    3.1のrand()と同様に以下に組み込み機能が速いかを再認識させられた。

  5. File操作
  6. 実行前の考察よりPHPはファイル操作が不得意であると考えていたが、その通りの結果であった。特にfopen(), fclose(), fwrite()のパフォーマンスが悪かった。
    また、ファイル操作はPerlが非常に有利だと考えていたが、Pythonが健闘している。

  7. その他
  8. シナリオ毎の実行結果の色付けを確認すると、PHP列に橙色が多く付いている。PHPは全般的に処理が遅いと考えていたが、結果から見ると異なっていた。以前の経験ではPHPのrandom()は遅いと感じていたが、やはりPerlと比較すると倍以上の差が出ていた。(Pythonとの比較に関しては、3.1を参照の事)

  9. 最後に
  10. それぞれのプログラミング言語には高速化技術が存在する。例えば、Perl: mod_perl, Python: mod_wsgi, PHP: APC, eAcceralator等である。これらの技術を使用すれば、より良いパフォーマンスが出やすいが、その前段階として、言語毎の特徴を押さえた上でプログラミング言語の選択や処理ロジック, アルゴリズムを組んでいく必要がある。

=====

おわりです。

技術サイクルと情報アーキテクトの位置づけ

大学院で技術サイクルと情報アーキテクトの位置づけを事例と共に考察しました。
合っているかは分かりません。適当に流してください。。

1. 技術サイクルとは
技術または企業が進化・発展する為には技術サイクルが必要不可欠である。
具体的には以下の流れを繰り返すことで、成長していく。

  1. 技術の不連続性
  2. 1→2の変化には、エバンジェリストが必要

  3. 不安定期
  4. 2→3の変化には、市場創造者が必要

  5. ドミナントデザイン
  6. 3→4の変化には、マネジメントが必要

  7. 安定期
  8. 4→1の変化には、発明・発見者必要

本考察では、1: ”技術の不連続性”→2: ”不安定期”→3: ”ドミナントデザイン”までの2期で活躍をする、
エバンジェリストと市場創造者の動きに着目し、今後の技術者の在り方について考察する。

2. MySQL
考察時のモデル事例として、MySQLを取り上げる。
私が初めてMySQLを知ったのは2005年春でバージョンは4.0の頃であった。
それまでMS SQLServer, DB2といった商用データベース(以下、DB)を使用して、
アプリケーションを組んでいたが、この頃初めてオープンソースDBに触れた。
Webサービス構築を通して、すぐにMySQLの虜になってしまった。
その当時を振り返りながら、MySQLの進化・発展、取り巻く環境の変化を確認していく。

2.1. MySQLの収益源
MySQLはオープンソースDBである為、無償で使用可能である。(※厳密にはGPL Version2)
使用者側でMySQLを使いこなす事が出来れば、追加のコンサルティング費用等は不要である。
MySQL社では、各種サポート(問題発生時のサポートなど)やサービス(チューニングなど)を提供しており、
年間売上は$60million – 70million程である。(認知度に対して決して大きくない)

2.2. ある日本人MySQLコンサルタント
現在、日本語によるMySQLの本が多数出版されており、中には内部構造を詳細に解説した本や、
パフォーマンスチューニングのTIPSをふんだんに散りばめられた本もある。
2005年当時、英語の本は多数出ていたが、日本語によるMySQL本は3冊のみであった。
但し、内容的に古かったり薄かったりとコンテンツとしては充分とは言えない状況であった。
自分が担当していたWebサービスのパフォーマンス改善をしていたが、MySQL4.0→4.1→5.0→・・・と、
バージョンアップする毎にパフォーマンス改善や新機能が盛り込まれていった為、これらの情報収集には苦労していた。
そんな時「現場で使える MySQL」(著:松信 嘉範さん)という本が出版された。
今でこそ、この本よりも多くのTIPSが書かれた本は多くあるが、当時としてはこれ以上の本はなく、
何度も繰り返し読み、また実機で実践をした。
この頃は既にMySQLはWeb系サービスでかなり使用されており、
メジャーな存在であったが、まだまだ未完成なデータベースで
文字コードの問題やストレージエンジンのパフォーマンス問題などの問題があった。
この本はこういった問題を丁寧に解説+対応方針を示し、他エンジニアも含めて参考にしていた。

2.3. WebサービスとMySQL
今日の有名なWeb系サービスでMySQLを使用していないサービスはほぼ無いと思われる。
前述の2005,6年頃では、YahooやGoogleは大規模に使用をしていたし、
新鋭サービスとしてはYoutubeや日本ではmixiが大量のアクセス・データを
どのようなアーキテクチャで捌くかに注目が集まっていた。
これらのサービスの成功により、MySQLの認知度は一気に上がり、様々なサービスの中核となった。
LAMPがWebサービスのデファクトスタンダードとして広まったのもこの頃であった。
一方、エンタープライズ業界ではまだ浸透しきれておらず、現場で使用されるまでに至っていなかった。
現在はWeb・エンタープライズ共に、多くのMySQLが稼働しており、商用データベースを脅かす存在となっている。

2.4. SUNによる買収
2008年2月、SUNによりMySQLは買収された。
収益はサービス・サポートからで$60million – 70million程であったMySQLを何故買収したのか。
理由はMySQLを使用する多数のユーザの存在である。
これらユーザの大多数はSUN以外のサーバを使用しており、
買収をする事で自社のサーバへ誘導する事が狙いであったと考えられる。

2.5. SAPの画策
SAPとMySQLは2003年頃から提携して、次世代DBを開発してきた。
MySQLをベースとして、SAP用に機能を削ぎ落したDBである。
このDBではSAP DBと言われ、後にMAX DB, live cacheと言われるデータベースとなった。
SAPがMySQLと組んだ理由としては、Oracleを打ち負かす為だと考えられる。
OracleはSAPと競合するERPを保持している為、SAPとしてはOracle DBを使用する事は推奨しない。
また、TomorrowNowに関連した賠償問題もある。
現在、SAP ERPのバックエンドで稼働可能なDBとしてSQL Server, DB2, Oracle, MAXDBが選択肢としてあるのだが、
OracleはERPで競合となるソフトウェアを保持しており競合にあたる為、メリットが無い。
そこで、メリット/デメリットあるもののライセンス費用がかかる3社ではなく、
ライセンス費用をかけずにSAPに特化したDBを共同開発可能なMySQLを選択したと考えられる。
しかし、SAPは2007年9月頃にMySQL社との提携を解除し、次なる道を模索し始めた。
結果、2010年5月にSybase買収へと進んだと思われる。この買収の狙いはOracleへの対抗である事は明白である。
また、Sybaseはインメモリ技術を保持していた為、現状のアーキテクチャの流れに足していたとも言える。
その後、このインメモリを搭載したリアルタイム分析ツール HANAをリリースした。

2.6. Oracleによる買収
2009年4月、OracleはSUNを買収した。この買収は最近の流行りである垂直統合システム化への道である。
自社でハードウェア・ソフトウェア・データベース等を一貫して提供し、
自社製品によるエコシステムを作り上げて親和性を武器に顧客を囲い込む作戦である。
前段のSAPによるSybase買収もこの垂直統合システム化を狙ったエコシステム構築の1つの動きである。
またOracleにはSUN買収にもう1つ狙いがあった。それはMySQLの存在である。
Webアプリケーションを席巻するMySQLを手にすることで、エンタープライズ側のOracle DB、
Web側のMySQLを使い分けることが出来る。
Oracleには更にメリットがあった。ストレージエンジンのInnoDBである。
MySQLはDBの核となるストレージエンジンを入れ替える事が出来る。
デフォルトのストレージエンジンはMyISAMであったが、このエンジンはトランザクションをサポートしていない為、
トランザクションを伴う場合はInnoDBを使用する。
以前からMySQLの存在が邪魔であったOracleは、2005年10月にInnoDBを保有するInnobase社を買収した。
これにより、MySQLは自身のソフトウェアの中核ストレージエンジンを奪われる事となる。
但し、Oracle側で制限をしなかった為、今までと変わらずにInnoDBは使用可能であった。
但し、SUN買収後のバージョン5.5からはデフォルトストレージエンジンがMyISAM→InnoDBへと変更された。
Oracle社内では元々あったInnoDBチームと買収したMySQLチームを統合し、開発にスピード間が出てきたとの事である。

2.7. RDBMSへの刺客
2009年、RDBMSへの刺客としてkey-value型のデータベースが注目され始めた。
これはNoSQL(Not Only SQL)と言われる。
Key-value型の考え方は以前からあり、memcached等によりWebのアプリケーションサーバではよく使用されてきた。
情報が爆発的に増えていくなかで、Big Dataを効率的に処理する為にkey-value型データベース等のNoSQLに注目が集まっている。当初は、RDBMSが無くなる・・と言った意見もあったが、現状は適材適所でRDBMSとNoSQLを使い分けていくというのが流れである。
NoSQLの流れに対抗する為に、MySQLではバージョン5.6からmemcachedプロトコルを利用したNoSQLアクセスを提供する予定である。
NoSQLなDBが今後どのように発展していくかは分からないが、RDBMSと適材適所で使い分ける必要がある。

2.8. MySQLコアメンバの動き
冒頭で、日本人のMySQLコンサルタントの話をしたが、今度はMySQLのコアメンバの動きを確認する。
まず、MySQLの創設者であるMichael “Monty” Wideniusは、MySQLからforkしたMariaDBを開発し始めた。(Mariaは娘の名前)
このDBは、ストレージエンジン”Maria”をデフォルトエンジンとするDBで、MySQL6.0(リリースされず)で出荷される予定だった機能が盛り込まれている。データベース本体として、MySQLを打ち負かす可能性は低いが、ストレージエンジンの1つの選択肢として使用されていく可能性はある。
 次にMySQLの開発責任者であったBrian Akerは、MySQLからforkしたDrizzleを開発し始めた。
このDBはクラウド上での使用を想定しており、Webサービスのバックエンドで使用する時に不要と思われる機能を極限まで削ったDBである。
今後、Cassandra等のNoSQLのライバルとなってくる可能性がある。

3. 考察
以上が、ここ数年でMySQLに起こった事象である。
この個人や企業の様々なアクションを技術サイクルに当て込み考察する。

3.1. 技術の不連続性 (エバンジェリスト)
まず、日本人MySQLコンサルタントの松信嘉範さんである。
彼はMySQLがまだ日本に浸透したとは言えなかった時代にDBマガジン(少し前に廃刊)や
書籍でMySQLの良さを布教していった。
彼がいなければ、日本におけるMySQLの成功はなかったかもしれない。

3.2. 不安定期 (市場創造者)
市場創造者はいくつかありそうである。以下にまとめた。

  1. SUN
  2.  MySQLを使用しているたくさんのユーザを狙った買収を行い、
     MySQLユーザを自社のサーバへ誘導しようとした。

  3. SAP
  4.  Oracleに勝つ為に、自前のDBを開発しようとしていた。
     また、開発を取りやめ、Sybaseを買収し、最近の流行りの垂直統合化で自社のエコシステムを構築しようとしている。クラウドに関しては、流れに乗り遅れているように感じる。

  5. Oracle
  6.  SUNの買収、MySQLへ対抗する為にInnoBaseの買収と、無駄がないように感じる。
     周囲の競合ベンダを威嚇しながら、買収を繰り返し、垂直統合システムを完成させていっている。だが、これはベンダーロックインとなる為、懸念される可能性もある。(中立的な立場であるPublicSectorがMicrosoftを嫌がり、OpenOfficeへ入れ替えていっているように)

  7. MySQL
  8.  MySQLはWebで圧倒的な強さを見せた。
     これは、Web業界を対象として製品をプッシュしていったからではないだろうか。
     また、Webがオープンソースを利用していくという流れにも乗っていた。
     だが、その他のオープンソースDB御三家(MySQL, PostgreSQL, Firebird)と何が違ったのだろうか。
     特に機能としては、優っているとの見解もあるPostgreSQLは何が足りなかったのか興味がある。

3.3. ドミナントデザイン (マネジメント)
ここでの登場人物はいなかったので、割愛する。

3.4. 安定期 (発明・発見者)
2人のMySQLの開発者は、新たな道を歩み初めている。
MontyはMariaDBを始め、BrianAkerはDrizzleを進めている。
先日、どちらのDBも新バージョンをリリースした為、次のTermである技術の不連続性へ進んでいく。
彼らはMySQL時代にエバンジェリストを経験し、既に流れを作った経験を持っている。
これはまさに技術のサイクルでもある。

3.5. 1技術者として自分が出来る事、取るべき行動
ここ数年は今後を左右する技術の変遷期にあたると考えている。
まず、技術者が避けなければならない事として、自分のスキルが陳腐化することである。
バズワードのように、たくさんの技術が出てきて、1部はメジャーとなり、一部はすぐ消える。
また、既存の技術であっても消えていく事も充分ありえる。
よって、業界動向、技術サイクルを見極め、今どこのTermにいるのか、
エバンジェリストや市場創造者はどこに進もう・進ませようとしているかを冷静に判断しなければならない。
その上で自分がアーリーアダプターとなり、他者をリードしていけるように心がける。(そうなりたい)
以前はエンタープライズ業界がIT業界を牽引していた。
例えば、IBM、Microsoftといったベンダーが新しい技術を産み、市場を創造していった。
しかし、今はWeb業界がIT業界を牽引している。
例えば、GoogleがAndroidを開発し、携帯メーカーはそのOSを利用して製品を作る
(もはや、auのCMはAndroidの機能紹介となっており、au独自の機能はないように感じる)。
Twitterがブームとなったことにより、Salesforce.のchatterやSAPのマイクロブログツールへと連鎖した。
このことから、まずはWebに認められるか否かが、ポイントとなると考えられる。
そのWebを支えているのは技術者である。よって、Webに認められたければ、技術者へ訴え続ける必要がある。
エグゼクティブを対象としたカンファレンスはかりを開くのではなく、
技術者を対象とした技術カンファレンスを定期的に開き、技術者を味方につけることである。
技術者は次の技術サイクルを創出していく可能性を常に秘めている。

以上。

QRコード with ZXing

ZXing(”Zebra Crossing”)を使用したQRコード生成ツールを作成しました。
ZXingはGoogleがオープンソースで公開しているQRコード生成ライブラリです。
Androidでも使用されていますが、今回はローカル起動までとなります。
以下では、Antを使用してビルドしていますので、Antのセットアップをしておいてください。

1. ZXing セットアップ
まず、Google CodeからZXingをダウンロードします。
任意のディレクトリへ展開します。私は “C:Eclipse_programzxing-1.6” としました。
次に展開済みのフォルダ内にある READ MEファイルを開き、ビルド用のコマンドを確認します。

Please refer to the project page for more information:
http://code.google.com/p/zxing/
in particular:
http://code.google.com/p/zxing/wiki/GettingStarted

To get started, you can try building and running the command-line client;
you will need to have Apache's Ant tool installed to run this:

ant -f core/build.xml
ant -f javase/build.xml
java -cp javase/javase.jar:core/core.jar com.google.zxing.client.j2se.CommandLineRunner [URL]

コマンドプロンプトを開き、展開をしたディレクトリへ移動し、12,13行目のAntコマンドを実行します。

C:Eclipse_programzxing-1.6>ant -f core/build.xml
Buildfile: C:Eclipse_programzxing-1.6corebuild.xml

clean:
   [delete] Deleting directory C:Eclipse_programzxing-1.6corebuild
   [delete] Deleting: C:Eclipse_programzxing-1.6corecore.jar

build:

init:

compile:
    [mkdir] Created dir: C:Eclipse_programzxing-1.6corebuild
    [javac] C:Eclipse_programzxing-1.6corebuild.xml:36: warning: 'includeant
runtime' was not set, defaulting to build.sysclasspath=last; set to false for re
peatable builds
    [javac] Compiling 171 source files to C:Eclipse_programzxing-1.6corebuild
      [jar] Building jar: C:Eclipse_programzxing-1.6corecore.jar

BUILD SUCCESSFUL
Total time: 10 seconds
C:Eclipse_programzxing-1.6>ant -f javase/build.xml
Buildfile: C:Eclipse_programzxing-1.6javasebuild.xml

init:

build:
    [javac] C:Eclipse_programzxing-1.6javasebuild.xml:40: warning: 'includea
ntruntime' was not set, defaulting to build.sysclasspath=last; set to false for
repeatable builds

BUILD SUCCESSFUL
Total time: 0 seconds

最後にEclipseで使用する為に “C:Eclipse_programzxing-1.6corecore.jar” をライブラリ追加すればOKです。

2. 最終的なソースコード
何らかの文字列を入力すると、都度QRコードを生成するようになっています。
また、保存ボタン押下で生成済みのQRコードを保存します。
ちょっと怪しい部分もありますが、、ひとまず動くと思います。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;

public class Url2QR extends JFrame implements ActionListener {

	private static Url2QR frame;
	private final JTextField textField;
	private final JButton dispButton;
	private final JButton saveButton;
	private final JLabel label;
	private final JPanel panelTop;
	private final JPanel panelBottom;
	private final ImageIcon icon;
	private final BufferedImage bufImg;
	private static int SIZE = 4;

	public Url2QR() throws WriterException {
		super("QRコード生成");

		panelTop = new JPanel();
		panelBottom = new JPanel();

		textField = new JTextField(20);
		dispButton = new JButton("表示");
		saveButton = new JButton("保存");

		bufImg = barcodeWrite();
		icon = new ImageIcon(bufImg);
		label = new JLabel(icon);

		Container container = getContentPane();
		container.setLayout(new BorderLayout());

		container.add(textField, BorderLayout.CENTER);
		container.add(panelTop, BorderLayout.EAST);
		container.add(panelBottom, BorderLayout.SOUTH);
		panelTop.setLayout(new BorderLayout());
		panelTop.add(dispButton, BorderLayout.WEST);
		panelTop.add(saveButton, BorderLayout.EAST);
		panelBottom.setLayout(new BorderLayout());
		panelBottom.add(label, BorderLayout.WEST);

		dispButton.setActionCommand("display");
		dispButton.addActionListener(this);
		saveButton.setActionCommand("save");
		saveButton.addActionListener(this);
		saveButton.setEnabled(false);

		KeyListener keyListener = new KeyListener() {
			public void keyPressed(KeyEvent keyEvent) {
			}

			public void keyReleased(KeyEvent keyEvent) {
				repaintIcon();
			}

			public void keyTyped(KeyEvent keyEvent) {
			}
		};
		textField.addKeyListener(keyListener);
	}

	public void actionPerformed(ActionEvent e) {

		if (e.getActionCommand().equals("display")) {
			repaintIcon();
		} else if (e.getActionCommand().equals("save")) {
			saveQR();
		}

	}

	public void repaintIcon() {

		try {
			if (textField.getText().isEmpty()) {
				saveButton.setEnabled(false);
			} else {
				saveButton.setEnabled(true);
			}
			label.setIcon(new ImageIcon(barcodeWrite()));
		} catch (WriterException e) {
			e.printStackTrace();
		}
		label.setToolTipText(textField.getText());
		frame.pack();

	}

	public void saveQR() {

		JFileChooser filechooser = new JFileChooser();

		int selected = filechooser.showSaveDialog(this);
		if (selected == JFileChooser.APPROVE_OPTION) {
			File file = filechooser.getSelectedFile();
			try {
				ImageIO.write(barcodeWrite(), "png", file);
			} catch (IOException e) {
				System.out.println(e);
			} catch (WriterException e) {
				e.printStackTrace();
			}
		} else if (selected == JFileChooser.CANCEL_OPTION) {
		} else if (selected == JFileChooser.ERROR_OPTION) {
			label.setText("Error");
		}
	}

	public BufferedImage barcodeWrite() throws WriterException {

		Hashtable hints = new Hashtable();
		hints.put(EncodeHintType.CHARACTER_SET, "SHIFT_JIS");
		QRCode qrCode = new QRCode();
		Encoder.encode(textField.getText(), ErrorCorrectionLevel.L, hints,
				qrCode);
		ByteMatrix byteMatrix = qrCode.getMatrix();

		BufferedImage image = new BufferedImage(byteMatrix.getWidth() * SIZE,
				byteMatrix.getHeight() * SIZE, BufferedImage.TYPE_4BYTE_ABGR);

		if (!textField.getText().isEmpty()) {
			Graphics2D g2D = image.createGraphics();
			for (int y = 0; y < byteMatrix.getHeight(); y++) {
				for (int x = 0; x < byteMatrix.getWidth(); x++) {
					if (byteMatrix.get(x, y) == 1) {
						g2D.setColor(Color.black);
					} else {
						g2D.setColor(Color.white);
					}
					g2D.fillRect(x * SIZE, y * SIZE, SIZE, SIZE);
				}
			}
		}

		return image;

	}

	public static void startGUI() throws WriterException {

		frame = new Url2QR();
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
		frame.setVisible(true);

	}

	public static void main(String[] args) {

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				try {
					startGUI();
				} catch (WriterException e) {
					e.printStackTrace();
				}
			}
		});
	}

}

ソフトウェア技術者としての責務に関する考察

大学院のソフトウェア工学で、「ソフトウェア開発の専門家としてのソフトウェア技術者は、雇用主によって明示的に要求されていない場合であっても、保守と変更が容易なプログラムを作成する責務がある。」に対する考察を行ったので、今後の備忘のために残します。

考察があっているかは分かりませんが、心に留めながら仕事していきたいと思います。

 

1. 「ソフトウェア開発の専門家としてのソフトウェア技術者は、雇用主によって明示的に要求されていない場合であっても、保守と変更が容易なプログラムを作成する責務がある。」に対する考察をせよ。

結論から述べると、ソフトウェア技術者は成果物リリース後の運用フェーズを常に意識しながらソフトウェア開発を行う必要がある。また、常にそのソフトウェアを使用するユーザの立場に立ち、ソフトウェア開発を行う事も重要である。理由として、一度リリースしたシステムやソフトウェアは7~10年間程使用される為、この長いソフトウェアライフサイクルの中で、どのような事が起きる・行われるかを意識するかしないかにより、ソフトウェア本体や各種ドキュメント等の成果物の優劣が大きく変わってくるからである。この結論に至るまでの過程を次項以降で詳細に分析を行う。

2. 「ソフトウェア技術者の職業倫理」による分析

ソフトウェア技術者の職業倫理として常に心掛ける事として、「ユーザ志向であること」を挙げる。ユーザとは、ソフトウェアの発注元(顧客)やそのソフトウェアを実際に使用する使用者を指す。私達ソフトウェア技術者は誰の為に何の為にこのソフトウェアを開発しているかを考えると、最終的にユーザ志向に行き着くと考えている。また、再発注を受ける為には、顧客満足度を上げる必要もある。ユーザ志向を持つと、ユーザインタフェースが格段に良くなる。開発者としては工数の掛かる機能であっても、その機能を10年間使用するユーザにとっては、その一瞬の不都合な機能が積み重なる事により、大きな対応工数になる事が多い。例えば、1機能毎に開発担当者が別であったとする。それぞれの開発担当者が実現し切れない部分を安易に”ユーザによるマニュアル対応”と位置付けて処理した場合、何が起きるか。対象機能が100あれば、100のマニュアル対応が発生する事になる。マニュアル対応によるヒューマンエラーを無くし、極力システム化する事が私達の使命である事が念頭にあれば、安易な仕様決定とはならないはずである。次の職業倫理として、「保守性を意識すること」がある。ソフトウェアはリリース後に必ず機能追加やバグが発生する。その際に、保守性が高いソフトウェアは機能追加やバグ修正が容易である。保守性が低い場合、対応時の工数が大きく異なる。1機能毎の対応工数に差がある場合、ソフトウェアが使用される10年間ではどの程度差が開くかを考えると、保守性を意識することの重要性は理解出来る。また、対応工数差の比率は常に同じではない。保守性が低いながらも、機能追加を行い続けたソフトウェアは、後続の機能追加が非常に難しくなる。難しくなると、設計、実装、テスト等の各工程で工数を大きく取られる為、対応工数差の比率は更に広がる。よって、保守性を意識することは、対価を支払う顧客の為でもあり、運用保守を行う仲間の為にもなる。

3. 「ソフトウェアの品質」による分析

ソフトウェアの品質とは人により捉え方が異なるものである。例えば、ソフトウェア技術者にとっては、ソースコードの品質であり、そのレベル感も個々に異なる。ユーザにとっては、システム(ソフトウェア)の品質であり、同様に個々に使用感や、ビジネスプロセスの効率化に品質を感じるかもしれない。よって、ソフトウェア技術者として大事な事は、誰の為のソフトウェアであるかを意識することである。個人的にはソースコードの品質が大事なのは当然の事として、よりユーザの立場に立ち、分析・設計・実装等を行って行く事が、ソフトウェアの価値向上に繋がり、ソフトウェア品質が向上する。ユーザの立場に立ったのと同様に運用保守担当者の立場に立った場合はどのように振舞うべきか。意識をしていなければ、成果物には反映されない事は明白である。

4. 「ソフトウェアプロセス」による分析

ウォーターフォールモデルのような規範的なプロセスモデルとアジャイル開発のようなアジャイルプロセスモデルを比較すると、どちらも一長一短があり、どちらが優れているということは言えない。よって、必ずどちらかのプロセスモデルで実施するのではなく、状況により選択していく柔軟性が大事である。例えば、運用保守フェーズであれば、アジャイルプロセスモデルが適している。顧客もチームメンバとして迎え、設計→プロト実装→本実装→テスト→リリースのイテレーションを行う。これにより保守運用フェーズのスピード感を出し易い。一方、規範的なプロセスモデルでは、開発フェーズの前にプロトタイプフェーズを行うのが良い。プロトタイプを行う事で余分な工数が掛かるが、顧客やユーザとの仕様のズレが解消され、顧客満足度を上げやすい。また、プロトタイプを実装するにあたり、調査等も行う為、設計・実装工程以降のインプットともなり易く、結果として工数短縮に繋がりやすい。設計・実装の前にある程度詳細に把握出来ていると、後工程の成果物精度が上がり、結果より良いソフトウェアライフサイクルに繋がるのではないだろうか。

5. 「ソフトウェアの分析、設計、実装、テスト」による分析

本項ではソフトウェアの分析、設計、実装、テストの各工程における分析を行う。

 
5.1. 分析

分析工程ではオブジェクト指向分析、構造化分析等の分析方法があるが、最終的な成果物は、顧客のようなソフトウェア技術者以外の人が確認し易い記法で書かなければならない。例えば、オブジェクト指向分析であれば、UMLを使用し、ユースケース図やユースケース記述、アクティビティ図等であり、構造化分析であれば、DFD等である。これらの成果物は設計工程以降のインプット情報となり、運用保守にも読まれる成果物となる。また、要求仕様化をするにあたり、As-Isモデル、To-Beモデルを定義すると良い。これらは顧客側や開発側の漏れを無くし、ビジネスプロセスの再構築に役立つ。同様に運用保守時の理解促進に役立つ。

 
5.2. 設計

設計書は何の為にあるか。私は運用保守フェーズ以降の為にあると考える。運用保守フェーズでは、新しいベンダーや担当者が入ってくる事が多いが、分析工程の成果物と一緒に参照されるのは設計書類となる。よって、設計書は実装の為に書くのではなく、保守対応者へのメッセージとして書く必要がある。但し、注意点として、設計書が必ず正しいわけではない。結局真実はソースコードそのものである。また、設計と言っても、基本設計、概要設計、詳細設計と複数あるが、実装をする為の詳細設計は必要ないと考えている。必要があれば、後追いで作成が可能である。実装中に詳細設計に記述している内容とズレが出てくる事は普通であるし、運用保守において詳細設計書を確認する事は少ないはずだからである。前述しているが、詳細設計書を参照するのであれば、ソースコードを確認したほうが良い。詳細設計が無い事により、設計書の作成や修正といった無駄な工数削減が可能である。注意点として、その前工程にあたる概要設計の記述レベルとして詳細に記述する事は極力避けるべきである。特に大規模開発で開発工程を開発パートナーへ依頼する場合は、ある程度の仕様に揺れ(不明点や考慮が必要だと感じてもらう)を持たせ、開発工程で検討を行ってもらう事が大事である。理由として、請け負った時点である程度詳細に決まっていると、更なる詳細部分(例えば、頻度の低いエラーケース等)を考慮しなくなる恐れがあるからである。よって、各設計書の記述レベルを事前に定義し、周知徹底する事が結果的に品質のよいソフトウェアが完成する。

 
5.3. 実装

シンプルなソースコードは、可読性・保守性・開発効率、そして美がある。言い換えると美しいソースコードは結局シンプルである。シンプルなコードを実装する事は、ソフトウェアライフサイクルが上手く循環する為の根幹となる。シンプルなコード→可読性が良い→保守性が良い→開発効率が上がる→機能毎の工数が減る→他機能に工数を掛ける→→→……(循環する)また、最低限の処理効率化は必要あるが、過度な効率化やテクニカルな記述は避けるべきである。個々の要件にもよるが、以前と比較し、CPUやメモリ等のハードウェア性能も上がっている為、過度な効率化を進めるよりも、多少の緩みを持たせ可読性を上げるべきである。必要であれば、スケールアウト・スケールアップや他技術の併用によりパフォーマンス改善は可能である。

 
5.4. テスト

テストの無いソフトウェア開発は有り得ない。これは誰もが共通の認識であると思うが、テストにたいする意識レベルの差により出来は全く違うものになる。まず、ソフトウェアのテストは何の為にあるのかを考えると、テストケースやテストデータ作成に対して、気を抜けなくなるはずである。私にとってテストケース・データの作成タスクは楽しい作業ではないが、ケースやデータがソフトウェアの仕様を網羅しているかを常に意識している。テスト方法としては、ホワイトボックステストよりもブラックボックステストの方が大事である。ソフトウェアが持つ本来の仕様を満たしているかどうかは、ブラックボックスでないと分からないからである。また、後工程で発覚したテスト不足は前工程に逆戻りするだけでなく、コストに大きなインパクトを与える事が多い。テスト工程は時間が掛かるが、やはり大事な工程である。以上のような意識を持ながらテストを行う事で、運用保守フェーズにおけるバグ発覚は非常に少なくなる。

6. まとめ

以上の事から、保守と変更が容易なプログラムを作成する為には、ソフトウェア技術者がどの立ち位置で振舞うかによって、結果は全く異なる。ソフトウェア構築サービスを請け負う以上、当然これらの責務を負い、遂行しなければならないと考える。

以上

 

入学式に行ってきました

今日は大学院の入学式に行ってきました。
昨年から科目履修生として通っていましたが、今日から1年生です。

私はオライリー本の目次を見るとワクワクする事が多いのですが、この大学院の科目群を初めて見た時、同様の感覚を感じました。
単純ですが、これが複数候補からこの大学院から選んだ理由です。きっと、自分が今欲しい知識・情報・技術と合致していたのでしょう。
何でもそうですが、今したいと思った時が最善の時です。読みたいと思った本は今読むのが最も効果があるのです。
私は行動をして上手くいかなかったら次に行くようにしています。

この2年間は以下をより意識しながら過ごしてみます。
 ■既に得ている知識を更に深く・体系的に学ぶ
 ■不足している知識・技術を幅広く補完する
 ■全ての情報を得る時間は無い為、取捨選択を大事にする

快く了承してくれた妻に感謝しつつ、安くはないお金と大切な時間を大事にしながら、
少しでも大きく成長して、2年後を迎えたいです。

なお、入学式はスーツで行った方が良いようです・・・(私は私服で行って、失敗しました。。)