メンバページ: Go

汎用メトリクスツール「いちゃもん」の読み方

How to read 'General-Purpose Metrics Tool "Fuss"'


このページでは、C/C++/C#/Java に対応する汎用的なメトリクスツール Ichamon の計測結果の読み方を紹介します。

 

- 更新記録 -
1日目 JavaApplet 版メトリクスツール
2日目 評価関数と警告 (Ichamon の読み方と歩き方)
3日目 Ichamon の修正
4日目 コマンドライン版の使い方
5日目 拡張のコツ

1日目 --- JavaApplet版 メトリクスツール Ichamon

まずは、メトリクスツールの JavaApplet 版を以下に置きます。

ソースプログラム
メトリクス

上のテキストエリアにソースコードを貼り付けます。そのコードが C または C++ であれば、C/C++ のボタンを クリックします。Java または C# であれば、Java/C# のボタンをクリックしてください。 下のテキストエリアに計測結果が表示されます。

ここで使用している評価関数はボタンにも書いてありますが、2004/09/15 版のものです。以下に主な評価値の説明をします。

  1. ProgramRate      --- プログラムの評価で、1.0 を基準として、高い評価のときは数値が大きくなります。
  2. ProgramQuantity --- プログラムの生産量で、 ステートメント×プログラム率で計算しています

次に以前の評価関数である 2004/09/07 版で評価するものをここに置きます。

違いを強調するために GUI を AWT で記述しています。

明日は、本日紹介しました二つのメトリクスの評価関数について書いていく予定です。

See you again !!

このページトップへ

2日目 --- 評価関数と警告 (Ichamon の読み方と歩き方)

今日は評価関数について書いていきます。まずは 2004/09/07 版の評価関数について記述します。

最後に、メトリクスツールの読み方について書きます。

2004/09/07 版評価関数

Java/C# 版評価関数

対外発表している評価関数をそのまま実装したものになります。以下に評価関数式を示します。

プログラム生産率= ((1.0 - dev1 + 0.5) ** 係数1)

                                  × ((dev2 + 0.5) ** 係数2)

                                  × ((1.0 - dev3 + 0.5) ** 係数3)

 dev1: ステートメント数/ メソッドの偏差値

 dev2: メソッド数/ クラスの偏差値

 dev3: ステートメント数/ クラスの偏差値

 偏差値計算のために平均や分散は、計測対象でない別のサンプル集団のものを使用

 係数1〜3: 各値の重み付け

 ** はべき乗を表す

この式は、バグを多数内在する品質の悪いプログラムの傾向性として、以下の特徴、相関関係があるなどの観測結果から導出しています。

低生産量のプログラムは、

1) 1メソッド当たりのステートメント数が多すぎる

2) 1クラス当たりのメソッド数が少なすぎる

3) 1クラス当たりのステートメント数が多すぎる

評価関数の注意点として、計測対象のプログラムと偏差値を出すための標本集団が異なりますので、イリーガルな値が出ることがあります。例えば、マイナスの値が算出されることがあります。

C/C++ 版評価関数

C/C++ 版の評価関数は、Java/C# 版の評価関数で、dev1 のみに注目したものです。つまり dev2 や dev3 は計測せずに 1関数(メソッド関数)当たりの平均ステートメント数からの評価になります。C はクラスによる明示的なモジュール分割がありませんので dev1 のみを注目することにしました。C++ は Java や C# と比較して、C 的なクラス設計のプログラムが多く、評価も C と同じようにしました。

2004/09/15 版評価関数

この評価関数は 2004/09/07 の評価関数に改良を加えたものです。この改良は係数の値、標本集団の平均値、分散の値の変更だけでなく、式そのものを変更しています。しかし基本的な考え方は変更していません。つまり、プログラムの品質の評価(バグ件数など)との相関関係を大きくするようにしています。

具体的には dev2(クラス当たりのメソッド数の偏差値)の扱いの変更や、マイナスの数値にならないようにしたなどがあります。

完全な計算式は、次の改良した評価関数を策定したときに公開するように考えています。

この評価関数に従ってプログラミングするのではなく、例えば「美人プログラミング」を目指してプログラムすれば、自然に高い評価が得られます。

評価関数の実装はアダプタブルにして、今後も変更をしていく予定にしています。

警告

このメトリクスツールではいくつかの警告を発します。プログラム生産率も 1.0 よりも小さいときは警告の一つになります。

以下の警告や計測結果に注意するようにしてください。

  1. コメント率 --- ドキュメンテーションコメントとプログラムコメントの合計の率が小さいときに警告を発します
  2. 1行の文字数 --- 1行の文字数が多いときに警告を発します
  3. 1メソッド当たりのステートメント数 --- 警告は発しませんが、 100 個(または 50個)を越えるときは警告レベルになります。ここの数値が高いとプログラム清算率も低くなります。

なお、警告はこれからも増加する予定です。

Ichamon の読み方と歩き方

警告やステートメント数が大きいものは、チェックするようにしてください。警告をなくすのは最低限のエチケットみたいなものです。

特別な理由もないのに プログラム生産率が 1.0 未満であるときは、普段のプログラミングを見直してください。自動生成されたコードが原因とか、流用したプログラムが原因であるなどは仕方がないでしょう。上記にも書きましたが、この評価関数に従ってプログラミングするのではなく、例えば「美人プログラミング」を目指してプログラミングしてください。

このチェックはまだまだ第一次審査、書類審査のようなもので、本審査はプログラムの内容になります。

それでは、みなさん、楽しみながら、プログラミングしていきましょう。

See you again !!

このページトップへ

3日目 --- Ichamon の修正 (2004-10-14)

Ichamon は汎用メトリクスツールですが、一部 C++ で合致しない動作がありました。そこを修正しました。上記のアプレット版では修正されています。

具体的には C++ のクラス定義後のクラス定義外での関数定義の処理が正しく動作していませんでした。これを修正しました。

See you again !!

このページトップへ

 

4日目 --- コマンドライン版の使い方

まずは機能追加1件とバグフィックス2件を行いました。

機能追加は、コンソール版の Ichamon ですが、ファイルタイプを見て、C や C++ であれば、simple 版評価関数を使い、 Java や C# であれば標準版評価関数を使うようにしました。

バグフィックスですが、内部クラスでない、兄弟クラスがあるときに、クラス当たりのステートメント数が間違うという バグを修正しました。さらに JavaApplet 版のときに、スレッドセーフにしました。

このページに JavaApplet 版の Ichamon を貼り付けていますが、この Ichamon にはコマンドライン版の機能も入っています。

というよりも、コマンド版の機能を削除していません。

使い方は以下のようになります。

>java -cp Ichamon.jar jp.co.okisoft.esc.metrics.Ichamon *.java

>

上記の計測結果は files.csv に出力されます。また exit コードとして、プログラム量を返しています。

>java -cp Ichamon.jar jp.co.okisoft.esc.metrics.Ichamon -print test.c
File is ./test.c
Function: test 0 - 2 (3)
a1 = 3.0  a2 = 1.0  a3 = 0.0
dev1 = 0.6384393063583815  dev2 = 0.3278592375366569  dev3 = 0.6807692307692308
rate = 1.1128366204464937
programRate 1.1128366204464937
programQuantity 3

上記は print オプション付きで test.c を計測してみました。

>java -cp Ichamon.jar jp.co.okisoft.esc.metrics.Ichamon -help
Metrics tool for Java/C#/C++/C
    [-simple][-print][-help] file*
  output metrics data to "files.csv"
   -simple   --- for C++, C, and so on
   (default) --- for Java, C#, and so on
   -print    --- prints metrics data
   -help     --- prints this help

ヘルプの表示です。

Ichamon.jar は ここ からもリンクしました。 保存して上記を実行してください。

このページを表示してアプレットが表示されているということは、既にダウン ロードされていることになりますが、37 KB の小さなものですので、 気楽にダウンロードしてください。

注意: WindowsXP SP2 をインストールしたマシンでは、zip 互換の圧縮ファイルは ファイルタイプが zip になりますので、 上記の例では zip に変えて実行してください。

See you again !!

このページトップへ

5日目 --- 拡張のコツ

今日は Ichamon の拡張のコツについて、書いていきます。

Ichamon で拡張される可能性の高い部分として、評価関数とプログラムステータスの部分を考慮しています。

評価関数の拡張

評価関数については、拡張というよりも「改善」「改良」になる部分が多いかも知れませんが、 この機能だけのクラスを独立したクラスにすることにより、拡張を容易にしています。

  1. public class Evaluation {
  2.    ...
      
    public
    static double programRate (double a1, double a2, double a3) {
  3.        ...
  4.         return programRate;
  5.     }
  6.   ...
  7. }
  8. public class EvaluationCustom extends Evaluation {
  9.    ...
  10.    public static double programRate (double a1, double a2, double a3) {
  11.        ...
  12.       return programRate;
  13.    }
  14.   ...
  15. }

のように、基本である Evaluation$programRate と、そのカスタマイズである EvaluationCustom$programRate を継承関係にしています。まだインタフェースは用意していません。

今回のカスタマイズは、パラメータを変更せずに、評価関数の機能カスタマイズだけなので、継承ではなく委譲でも良かったでしょう。

しかし、評価関数では、パラメータ変更なども入る可能性がありますので、継承にして、クラス全体をカスタマイズ対象にした方が便利であるでしょう。

実際に、二つの評価関数を用いています。これからも、さらにパラメータ込みの評価関数を用意することになります。このときは、インタフェース+継承で作成するように考えています。

プログラムステータスの拡張

この Ichamon では、字句解析と、超簡単な(または手抜きの)構文解析を行っています。

メトリクスを新たな視点で計測することが必要になってきたときに、構文解析部を直接変更するのではなく、構文解析部とコルーチンのように動作するクラス「プログラムステータス」を構文解析から、独立して設けました。

このクラスは興味ある構文が来れば、対応するメソッドが呼び出されるようになっています。例えば、"class { }"  というクラス定義構文に興味を持ったとすると、class が来たときに、それに対応するプログラムステータスのメソッド classID が呼び出されるようになります。またユーザ関数定義に興味を持ったとすると、メソッド userDefinedFunction が呼び出されるようにしています。

これにより、構文解析部に興味ある構文の一部を示し、それに対応するプログラムステータスのメソッドを登録することにより、拡張が容易に行えます。例えば、if 文に興味を持ったとすると、それに対応するプログラムステータスのメソッドを用意し、そこで収集した情報を解析して蓄積することができます。

しかし、構文解析部は手抜きして作ってあり、かつ上記のようにその処理部である「プログラムステータス」クラスを独立させたために、ルックアップとそのフック関数呼び出しのみになり、少ない量になりましたので、字句解析部の一部に含むようにしています。

これは拡張性を低下させています。例えば、C#, Java, C のような構文規則が似ているプログラム言語に対しては問題ありませんが、構文規則が大きく異なる言語、例えば、FORTRAN や Lisp, Python などに対しては、拡張を困難なものにしています。字句解析部から構文解析部の癒着部分を切り離して、独立したクラスにする必要があるでしょう。

付録として、プログラムステータスのプログラムを掲載します。(インデントは、ハードウェアタブを用いていますので、Web ページでは、ずれて表示されています。)

package jp.co.okisoft.esc.metrics;

import java.util.ArrayList;

/**
 * Program Status Class
 * @author gomi
 */
public class ProgramStatus {
	// constant
	private final static int lineComment = 1;	// line comment
	private final static int blockComment = 2;	// block comment
	private final static int empty = 4;		// empty line or normal line
	private final static int normalMode = 0;
	private final static int classMode = 1;

	// public variable
	public int lineMode;			// current line mode 
	public static ArrayList files;	// file inf.

	// private variable
	private String fileName;		// file name
	private int curlLevel;			// Curl Bracket level
	private int operationLines;		// total operation line number
	private int statementLines;		// total statement line number
	private int totalLines;		// total source file line
	private int commentLines;		// comment line number
	private int emptyLines;		// emptyLine number
	private String functionSymbol;	// current function symbol
	private int startFunction;		// start position of Function defined 
	private int startLevelFunction;	// start level of Function defined
	private ArrayList functions; 	// function inf.
	private int statementMode;		// statement mode;
	private ArrayList classInf;
	private int currentClassCurlLevel = -1;
	
	/**
	 * construct program status
	 */
	ProgramStatus () {
		functions = new ArrayList();// function inf.
		curlLevel = 0;			// Curl Bracket level
		lineMode = empty;			// current line mode as empty line
		operationLines = 0;		// total operation line number
		statementLines = 0;		// total statement line number
		totalLines = 0;			// total source file line
		commentLines = 0;			// comment line number
		emptyLines = 0;			// emptyLine number
		functionSymbol = null;		// current function symbol
		functions = new ArrayList(); // function status ArrayLists
		currentClassCurlLevel = -1;
		classInf = new ArrayList();
	}

	/**
	 * regist file name
	 * @param fileName File name
	 */
	public void registFileName (String fileName) {
		this.fileName = fileName;	// file name
	}
	
	/**
	 * open curl bracket
	 */
	public void openCurl (String symbol) {
		if (curlLevel == 1 + currentClassCurlLevel && symbol != null) {
			userDefinedFunction(symbol);
		}
		curlLevel++;
		return;
	}

	/**
	 * close curl bracket
	 */
	public void closeCurl () {
		curlLevel--;
		// for class
		for (int i = 0, size = classInf.size(); i < size; i++) {
			ClassInf classInformation = (ClassInf) classInf.get(i);
			if (classInformation.curlLevel == curlLevel) {
				if (!classInformation.setStatements) {	
					classInformation.startStatement 
						= statementLines - classInformation.startStatement;
					classInformation.setStatements = true;	
					if (Ichamon.print) {
						System.out.print("Class " + classInformation.className);
						System.out.print(" definition end");
						System.out.print(" at " + statementLines);
						System.out.println(" in " + classInformation.startStatement);
					}
				}
			} 
		}
		// for method
		if (functionSymbol == null) return;
		if (startLevelFunction == curlLevel) {
			functions.add(new Integer(statementLines - startFunction));
			functionSymbol = null;
			if (Ichamon.print) {
				System.out.print(" - " + (statementLines - 1));
				System.out.print(" (" + (statementLines - startFunction));
				System.out.println(")");				
			}

		}
		return;
	}

	/**
	 * line feed
	 * @param emptyLine The ine with line feed is emmty line ?
	 */
	public void lineFeed (boolean emptyLine) {
		totalLines++;
		lineFeedFinal(emptyLine);
		return;
	}

	/**
	 * line feed Final at last line
	 * @param emptyLine The ine with line feed is emmty line ?
	 */
	public void lineFeedFinal (boolean emptyLine) {
		if (!emptyLine) operationLines++;
		if (emptyLine) emptyLines++;
		if (lineMode == lineComment || lineMode == blockComment) commentLines++;
		if (lineMode == lineComment) lineMode = empty;
		return;
	}

	/**
	 * semiColon
	 */
	public void semiColon () {
		statementLines++;
		return;
	}

	/**
	 * line comment in
	 */
	public void lineCommentIn () {
		lineMode = lineComment;
		return;
	}


	/**
	 * block comment in
	 */
	public void blockCommentIn () {
		commentLines++;		// starting block comment self
		lineMode = blockComment;
		return;
	}

	/**
	 * block comment out
	 */
	public void blockCommentOut () {
		lineMode = empty;
		return;
	}

	/**
	 * user defined function
	 * @param function Function name
	 */
	public void userDefinedFunction (String function) {								
		if (functionSymbol != null) return;
		functionSymbol = function;
		startFunction = statementLines;
		startLevelFunction = curlLevel;
		functions.add(function);
		if (Ichamon.print) {
			System.out.print("Function: " + functionSymbol);
			System.out.print(" " + statementLines);
		}
		return;
	}

	/**
	 * symbol name
	 * @param name symbol name 
	 */
	public void symbolName (String name) {
		if (statementMode == classMode
			&& Ichamon.mode == "normal") {
			classInf.add(new ClassInf(name, 
									  totalLines, 
									  statementLines, 
									  curlLevel,
									  fileName));	
			currentClassCurlLevel = curlLevel;
			if (Ichamon.print) {
				System.out.print("Class: " + name);
				System.out.println(" at " + statementLines);
			}
		}
		statementMode = normalMode;
		return;
	}

	/**
	 * class id
	 */
	public void classId () {
		statementMode = classMode;
		return;
	}

	/**
	 * special form
	 * @param symbol special forma identifier
	 */
	public void specialForm (String symbol) {
		statementLines++;
		return;
	}
	
	/**
	 * save program status
	 */
	public void saveStatus () {
		FileStatus fileStatus = new FileStatus(fileName,
											    totalLines, 
											    operationLines, 
											    statementLines,
												commentLines,
												emptyLines,
												functions,
												classInf);
		files.add(fileStatus);
	}

	/**
	 * total count
	 */
	static public FileStatus totalStatus () {
    	int totalLines =0; 
		int operationLines = 0; 
		int statementLines = 0;
		int commentLines = 0;
		int emptyLines = 0;
		int classNumber = 0;
		int methodNumber = 0;
		int classStatements = 0;
		int methodStatements = 0;

		for (int i = 0, size = files.size(); i < size; i++) {
			FileStatus fs = (FileStatus) files.get(i);
			totalLines += fs.getTotalLines();
			operationLines += fs.getOperationLines();
			statementLines += fs.getStatementLines();
			commentLines += fs.getCommentLines();
			emptyLines += fs.getEmptyLines();
			// class
			ArrayList classes = fs.getClasses();
			for (int j = 0, jsize = classes.size(); j < jsize; j++) {
				classNumber++;
				classStatements += ((ClassInf) classes.get(j)).startStatement;
			}
			// method
			ArrayList methods = fs.getFunctions();
			for (int j = 0, jsize = methods.size(); j < jsize; j += 2) {
				methodNumber++;
				if (j + 1 >= jsize) {
					break;
				}
				methodStatements += ((Integer) methods.get(j + 1)).intValue();
			}
			 
		}
		// for class average and method average
		double average = 0.0; 
		double averageM = 0.0; 
		if (classNumber != 0) average = classStatements * 1.0 / classNumber;
		if (methodNumber != 0) averageM = methodStatements * 1.0 / methodNumber;

		ArrayList classAverage = new ArrayList();
		classAverage.add(new Double(average));
		classAverage.add(new Integer(classNumber));
		
		ArrayList methodAverage = new ArrayList();
		methodAverage.add(new Double(averageM));
		methodAverage.add(new Integer(methodNumber));
		if (classNumber == 0) classNumber = 1;
		methodAverage.add(new Double(methodNumber * 1.0 / classNumber));
		
		return new FileStatus("Total", totalLines, operationLines, 
							   statementLines, commentLines, emptyLines, 
							   methodAverage,
							   classAverage);
	}			
	
	/**
	 * print program status
	 */
	public void printStatus () {
		println("Total lines     : " + totalLines);
		println("Operation lines : " + operationLines);
		println("Statement lines : " + statementLines);
		println("Comment lines : " + commentLines);
		println("Empty lines : " + emptyLines);
		for (int i = 0, size = classInf.size(); i < size; i++) {
			ClassInf classInformation = (ClassInf) classInf.get(i);
			println("Class " + classInformation.className +
					" : statement number = " + classInformation.startStatement);
		}
		for (int i = 0, size = functions.size(); i < size; i += 2) {
			println("Function " + functions.get(i) + " : statement number = " +
					functions.get(i + 1));
		}
	}
	
	/**
	 * print string with newLine
	 * @param string String for output
	 */
	private static void println(String string) {
		System.out.println(string);		
	}
	/**
	 * print newLine
	 */
	private static void println() {
		System.out.println();		
	}
	
	/** function size threshold */
	private static int maxFunctonSizeThreshold = 50;

	/** Class information */
	class ClassInf {
		String className;
		int startLine;
		int startStatement;
		int curlLevel;
		String fileName;
		boolean setStatements;
		
		/** constructor */	
		ClassInf (String name, int lines, int statements, 
				  int curl, String file) {
			this.className = name;
			this.startLine = lines;
			this.startStatement = statements;
			this.curlLevel = curl;
			this.fileName = file;
			this.setStatements = false;
		}
	}		

	/** Method information */
	class MethodInf {
		String methodName;
		int startLine;
		int startStatement;
		int curlLevel;
		String className;
		/** constructor */	
		MethodInf (String name, int lines, int statements, 
				  int curl, String className) {
			this.methodName = name;
			this.startLine = lines;
			this.startStatement = statements;
			this.curlLevel = curl;
			this.className = className;
		}
	}		
}
  

See you again !!

このページトップへ


Copyright (c) 2004, 2005 OKI Software Co., Ltd.