JRubyでJUDE CRUD-APIを試す

JUDE-CRUD-API

JUDE5でCRUD図がサポートされ、さらにAPIも公開されました。さっそく試してみましょう。題材は、おなじみファンクションポイント測定アプリです。さらに今回はJRubyも使い、JUDE-APIJRubyの相性も体感します。

仕様

仕様は基本的に前回と同じで、計測結果も前回と同じになるはずです。
使い方はユースケースとER図を描いて、ユースケースとエンティティそれぞれの「タグ付き値」にDET/RETなどの見積りに必要な情報を入力します。(DET/RETの詳細は前回のエントリを参照してください)
前回との仕様の違いは次の通りです。

ユースケースとER図を使う

前回は、ユースケースとクラスを使っていましたが、JUDECRUD図は、ER図が必要です。ということでデータのモデルはER図で表現します。(ちなみにERモデルとクラスのモデルは相互に変換できます)

CRUDの指定により、ILF/EIFの判別を行う

前回は、ユースケースとエンティティとの依存+依存線に付けるステレオタイプによってILFとEIFの判別を行っていました。今回はCRUD図を使って判別します。
具体的にはC(Create)/U(Update)/D(Delete)は、ファイルをメンテナンスするという意味でILF、R(Read)はメンテナンスが不要なEIFと判別します。

環境

JavaではなくJRubyを使いました。実は平鍋さんのエントリに刺激を受けたのです。

ファンクションポイント算出ライブラリ

これは前回作ったサンプルアプリをJARファイルにしたものです。今回のサンプル動作に必要となります。JRubyのlibフォルダに突っ込むか、クラスパスに通しておきます。

JUDE-API

JUDE5Pro正式版のJARファイル(jude-pro.jar/jude-api.jar)を使っています。これらもクラスパスの通るところに配置する必要があります。

Ruby初体験

Hello World以外では、初めてRubyのコードを書きました。まだ「Rubyっぽさ」というかその良さが分かってないですね。(私の周りのRubyエキスパートの皆さん、優しいツッコミをお待ちしております)

JRubyはいい

JRubyは便利です。特に今回のように試行錯誤を繰り返す場合はインタープリターはやっぱ楽ちん。JUDE APIJRubyの組み合わせは流行る気がします。

サンプルソースコード
require 'java'

module Jude
    include_package "com.change_vision.jude.api.inf.project"
    include_package "com.change_vision.jude.api.inf.model"
end
module Fp
    include_package "com.jude_api.fp"
end
# データファンクションのDETを求める
# 単純にエンティティ中の属性を数えているだけ
def getDataFunctionDET(model)
  model.primaryKeys.length + model.nonPrimaryKeys.length
end
# データファンクションのRETを求める
# JUDEのタグ付き値に"RET"というキーで設定する
# 未設定の場合は1
def getDataFunctionRET(model)
  model.taggedValues.each do |tv|
    if tv.key == "RET" then
      return tv.value.to_i
    end
  end
  return 1
end
# データファンクションポイントを求める
def getDataFunctionPoint(fileTypeId,det,ret)
  complexity = Fp::FunctionPointUtil.getDataFunctionComplexity(det, ret)
  dataFp = Fp::FunctionPointUtil.getDataFunctionPoint(fileTypeId,complexity)
end
# トランザクションファンクションのDETを求める
# JUDEのタグ付き値に"DET"というキーで設定する
# 未設定の場合は30
def getTransactionalFunctionDET(model)
  model.taggedValues.each do |tv|
    if tv.key == "DET" then
      return tv.value.to_i
    end
  end
  return 30
end
# トランザクション種別(EI/EO/EQ)を求める
# JUDEのタグ付き値に"type"というキーで設定する
# 未設定の場合は0(EI)
def getTranTypeId(model)
  types = ["EI","EO","EQ"]
  model.taggedValues.each do |tv|
    if tv.key == "type" then
      types.each_with_index {|type,i| return i if tv.value == type}
    end
  end
  return 0
end
# トランザクションファンクションポイントを求める
def getTransactionalFunctionPoint(tranTypeId,det,ftr)
  complexity = Fp::FunctionPointUtil.getTransactionalFunctionComplexity(det,ftr,tranTypeId)
  tranFp = Fp::FunctionPointUtil.getTransactionalFuncitonPoint(tranTypeId,complexity)
end

pa = Jude::ProjectAccessorFactory.projectAccessor

pa.open "test2.jude"

fpTotal = 0
pa.project.diagrams.each do |dg|
  if dg.java_kind_of? Jude::IMatrixDiagram then
    dg.showRowHeaders.each_with_index do |rh, i|
      # グルーピングおよび合計行は無視
      next if !rh.parent || rh.isTotal
      puts rh.label
      
      dataFpSum = 0
      ftr = 0
      dg.showColumnHeaders.each_with_index do |ch, j|
        # グルーピングおよび合計行は無視
        next if !ch.parent || ch.isTotal
        fp = 0
        det = getDataFunctionDET(ch.model)
        cell = dg.getShowValueCell(i,j)
        if cell != nil then
          if (cell.isCreate || cell.isUpdate || cell.isDelete) then
            # C/U/DがあればILF
            ret = getDataFunctionRET(ch.model)
            fp = getDataFunctionPoint(0,det,ret)
            printf "  %s %s DET=%d RET=%d FP=%d \n","ILF",ch.label,det,ret,fp
            ftr+=1
          elsif cell.isRead
            # Rのみの場合はEIF
            ret = getDataFunctionRET(ch.model)
            fp = getDataFunctionPoint(1,det,ret)
            printf "  %s %s DET=%d RET=%d FP=%d \n","EIF",ch.label,det,ret,fp
            ftr+=1
          else          
            # 一旦CRUDを作成しその後削除したCELL
            print "  (使わない) ",ch.label,"\n"
          end 
        else
          # 一度もCRUDを設定していないCELL
          print "  (使わない) ",ch.label,"\n"
        end
        
        dataFpSum += fp
        
      end
      print "** Data FP合計 ",dataFpSum,"\n"
      
      tranDet = getTransactionalFunctionDET(rh.model)
      tranFp = getTransactionalFunctionPoint(getTranTypeId(rh.model),tranDet,ftr)
      
      print "** Transactional FP ",tranFp,"\n"
      fpTotal += dataFpSum
      fpTotal += tranFp
    end
  end
end
puts "\nFP合計:", fpTotal

pa.close

JUDE-APIにフィードバック

CRUD-APIを使っててちょっと気になった点を挙げます。

一度もCRUD設定していないCELLと、一旦設定し削除したCELLの違い。

一度もCRUD設定していない場合、IMatrixDiagram#getShowValueCellメソッドはnullを返します。一方、一旦設定し削除したCELLの場合はIValueCellのインスタンスが返ります。JUDEの見た目上は区別が付かないのですがAPIでは区別されます。

グルーピングと合計行の判別

合計行はIHeaderCell#isToralメソッドが使えますが、グルーピング行の判別は、IHeaderCell#getParentがnullを返すかどうかでチェックする方法しかありません。

まとめ

  • JUDECRUD図編集はとても使いやすいので、前回に比べ見積りツールとしての実用度は上がりました。
  • 見積りをツールで自動化しておけば作業を何度でもトライすることができるます。その結果、効率の向上と見積り作業に対する心理的な敷居を下げることも期待できます。
  • Ruby化したのは実は大きいです。コンパイル不要ですぐ動作を試せるので改良・改善に向けた「腰が軽く」なります。