...

修士論文 ソースコードの並べ替えに基づく ソフトウェア理解性評価 寺井

by user

on
Category: Documents
4

views

Report

Comments

Transcript

修士論文 ソースコードの並べ替えに基づく ソフトウェア理解性評価 寺井
NAIST-IS-MT9951074
修士論文
ソースコードの並べ替えに基づく
ソフトウェア理解性評価
寺井 淳裕
2001 年 3 月 16 日
奈良先端科学技術大学院大学
情報科学研究科 情報システム学専攻
本論文は奈良先端科学技術大学院大学情報科学研究科に
修士 (工学) 授与の要件として提出した修士論文である。
寺井 淳裕
審査委員:
井上 克郎 教授
渡邉 勝正 教授
飯田 元 助教授
松本 健一 助教授
ソースコードの並べ替えに基づく
ソフトウェア理解性評価
寺井 淳裕
内容梗概
現在ソフトウェアを理解することの必要性が増加している。
例えば再利用ソフトウェアの場合ではプロジェクト外で開発されたソフトウェ
アを利用する機会が増えている。このため、再利用部分に問題がある場合や、新
規開発部分との間に不整合がある場合などは、開発者が再利用部分を十分理解し
ていないと問題の原因を突き止めることが困難になる。
またレガシーソフトウェアでは、多数の開発者による度重なる修正によって大
部分が理解困難となっている。
本研究では、再利用ソフトウェアやレガシーソフトウェアなどの理解性の評価を
行うための方法として、ソースコードのオーバーホール(分解検査)を提案する。
さらにソフトウェアについてオーバーホールを行う手法を提案し、オーバー
ホール作業を支援するツールについて述べる。
このツールで作業者はソフトウェアのオーバーホールを行い理解性の評価を行
う。またそのオーバーホール過程の履歴から理解性評価を行う。
キーワード
ソフトウェア品質、再利用ソフトウェア、レビュー
奈良先端科学技術大学院大学 情報科学研究科 情報システム学専攻 修士論文,
MT9951074, 2001
年
3
月
16
日.
i
NAIST-IS-
Software understandability evaluation
through rearranging source codes
Atsuhiro Terai
Abstract
The necessity for understanding the software is increasing.
For example, in the case of reused software, the opportunity to use the software
developed out of the project is increasing.
For this reason, when a problem is in a reused software, or when mismatching
is between new development software, if the developer does not understand the
reused software enough, it will become diÆcult to trace the cause of a problem.
Moreover, legacy software has become diÆcult to understand because many
developers modied over and over.
In this research, I proposed the overhaul of source codes as a method for evaluating understandability of reused software and legacy software.
In this paper, I express the tool which supports overhaul operations and records
the process history to evaluate understandability of software.
Keywords:
software quality, reused software, review
Master's Thesis, Department of Information Systems, Graduate School of Information
Science, Nara Institute of Science and Technology, NAIST-IS-MT9951074,
ii
March 16, 2001.
目次
1.
はじめに
1
2.
ソフトウェアの理解性
3
2.1 理解性
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
2.2 従来手法による理解性評価
3.
ソフトウェアのオーバーホール
3.1 オーバーホール
4.
: : : : : : : : : : : : : : : : : : : : : :
6
: : : : : : : : : : : : : : : : : : : : : : : : : : : :
6
: : : : : : : : : : : : : : : : :
7
3.3 ソフトウェアのオーバーホール環境
: : : : : : : : : : : : : : : : :
8
並べ替えを用いたソースコードのオーバーホール
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
4.2 並べ替えと理解性
: : : : : : : : : : : : : : : : : : : : : : : : : : :
4.3 作業者による問題点の発見
: : : : : : : : : : : : : : : : : : : : : :
4.4 履歴を利用した問題点の発見
: : : : : : : : : : : : : : : : : : : :
オーバーホールツール
5.1 ドキュメント
10
10
13
16
16
17
: : : : : : : : : : : : : : : : : : : : : : : : : : : : :
5.2 コード分解ツール
: : : : : : : : : : : : : : : : : : : : : : : : : : :
17
17
: : : : : : : : : : : : : : : : : : : : : : : : :
18
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
18
5.2.1
ソースコード
5.2.2
単位
5.3 並べ替えツール
: : : : : : : : : : : : : : : : : : : : : : : : : : : :
5.3.1
ユーザーインターフェース
5.3.2
履歴収集
18
: : : : : : : : : : : : : : : : : :
18
: : : : : : : : : : : : : : : : : : : : : : : : : : : :
20
5.4 オーバーホールの手順
6.
4
3.2 オーバーホールによる段階的な理解
4.1 PDG の定義
5.
3
: : : : : : : : : : : : : : : : : : : : : : : :
適用実験
6.1 オーバーホールの手順
21
23
: : : : : : : : : : : : : : : : : : : : : : : :
6.2 ソースコードAのオーバーホール結果
iii
: : : : : : : : : : : : : : :
23
24
6.3 ソースコードBのオーバーホール結果
7.
: : : : : : : : : : : : : : :
まとめと今後の課題
7.1 まとめ
29
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
7.2 今後の課題
25
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
29
29
謝辞
31
参考文献
32
付録
33
A.
適用実験で用いたソースコードA
33
B.
適用実験で用いたソースコードAのドキュメント
48
C.
適用実験で用いたソースコードAのモジュール機能仕様書
51
D.
適用実験で用いたソースコードB
56
iv
図目次
1
ソフトウェアの分解と統合
2
ソフトウェアのオーバーホール環境
3
表 4 の PDG
4
並べ替えられた PDG のノード
5
並べ替えによるオーバーホールツール
6
オーバーホールのフローチャート
7
並べ替えに要した時間
8
理解性評価の比較
: : : : : : : : : : : : : : : : : : : : : :
7
: : : : : : : : : : : : : : : : :
9
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
14
: : : : : : : : : : : : : : : : : : : :
14
: : : : : : : : : : : : : : :
19
: : : : : : : : : : : : : : : : : :
22
: : : : : : : : : : : : : : : : : : : : : : : :
26
: : : : : : : : : : : : : : : : : : : : : : : : : :
28
表目次
1
プログラム例 1
: : : : : : : : : : : : : : : : : : : : : : : : : : : :
11
2
プログラム例 2
: : : : : : : : : : : : : : : : : : : : : : : : : : : :
12
3
プログラム例 3
: : : : : : : : : : : : : : : : : : : : : : : : : : : :
12
4
カレンダープログラムのあるモジュール
5
機能仕様書の一部
6
収集する履歴情報の例
: : : : : : : : : : : : : : : : : : : : : : : :
20
7
オーバーホールの履歴
: : : : : : : : : : : : : : : : : : : : : : : :
25
: : : : : : : : : : : : : :
13
: : : : : : : : : : : : : : : : : : : : : : : : : : :
17
v
1.
はじめに
本研究の背景として、保守担当者が不充分な理解でソフトウェアの不具合を回
避する対処療法的な修正をすると、別の問題が発生する可能性が高いということ
があげられる。
例えばオブジェクト指向技術やコンポーネントウェアに代表されるソフトウェ
ア再利用技術の普及により、ソフトウェア開発プロジェクトにおいてプロジェク
ト外で開発されたソフトウェアを再利用する機会が増えている。再利用されたソ
フトウェアは新規開発されたソフトウェアに比べて一般的には信頼性が高い。し
かし、再利用部分に問題がある場合や新規開発部分との間に不整合がある場合な
どは、開発者が再利用部分を十分理解していないと問題の原因を突き止めること
が困難になる。
またレガシーソフトウェア1 では、多数の開発者による度重なる修正によって
大部分が理解困難となっていたり、これを保守する場合にはまずソフトウェアを
理解することから始めなければならない。
そこで本研究では、再利用ソフトウェアやレガシーソフトウェアなどの理解性
の評価を行うための方法として、ソースコードのオーバーホール(分解検査)を
提案する。
一般にオーバーホールとは、ハードウェア分野でよく用いられるアプローチで
ある。例えば、故障車の修理において故障の症状からその原因部分を特定できな
い場合、オーバーホール(分解検査)が行われる。また、車が古くなり、全体的
に調子が悪くなった場合にもオーバーホールが行われる。修理担当者はオーバー
ホールにおける分解と統合の過程を通じて、対象の故障車を理解し、理解の結果
として故障の原因を突き止めていると考えられる。
ソフトウェアの分野では、チェックリストを用いた設計レビューやコードレビュー
などのようにハードウェアにおける点検に相当する作業は行われているが、分解
し統合するというオーバーホールに相当する作業は行われていない。また、従来
の設計レビューやコードレビューなどの検査では問題個所を見つけることに主眼
1 開発されてから非常に長い時間が経過した場合など、開発当時の担当者が組織からいなくな
り、組織で受け継がれているソフトウェア
1
が置かれていたが、本研究では対象ソフトウェアを理解するということに特に注
目している。なぜなら、統合に時間がかかった部分や統合できなかった部分は、
その開発者にとって理解が難しかったことを意味するからである。よって、その
ような部分を優先的に、より熟練した開発者が再オーバーホール、再レビュー、
再設計することにより、効率的にソフトウェアを改善できると予想される。また、
本手法の効果が確認されれば、学生や新入社員を対象としたソフトウェア教育に
も有効と考えられる。
以下、まず 2 章で、ソフトウェアの理解性について述べる。
次に、3 章でソフトウェアの理解性を評価する新しい手法としてソフトウェア
のオーバーホールを提案する。
4 章においてソフトウェアの中でも特にソースコードについてのオーバーホー
ルの具体的な方法を説明する。
5 章では 4 章で述べたオーバーホールを支援するための環境について詳しく述
べる。
さらに 6 章で、提案手法の適用例について説明する。これから、オーバーホー
ルにかかる時間、オーバーホールの過程で見つけることのできる問題についての
考察を行う。
最後に、7 章でまとめと今後の課題について述べる。
2
ソフトウェアの理解性
2.
本章ではソフトウェアの理解性について述べる。まず、B. W. Boehm らの品質
モデル [7] で定義されている理解性の概要について説明した後、従来手法による
理解性評価について紹介する。
2.1
理解性
ソフトウェアの理解性とはソフトウェアがどれほど理解しやすいかの度合いで
ある。このようなソフトウェアの品質を定義するうえで、一般的にとられている
方法は、その構成要素を示し、品質特性として定めることである。
B. W. Boehm らの品質モデルでは理解性の上位の品質特性には保守性があり、
下位には以下の品質特性を構成要素として持っている。
無矛盾性(consistency)
ソフトウェアの表記法、用語、記号などが統一されている度合い。
構造性(structuredness)
ソフトウェアの相互に関連した部分の構成が、パターン化されている度合い。
自己記述性(self-descriptiveness)
ソフトウェアが、その目的、仮定、入出力、要素、状態などに関して、十
分な情報を含んでいる度合い。
簡潔性(conciseness)
不必要な情報を除いた、必要な情報だけが備わっている度合い。
明瞭性(legibility)
コードを読むことによって、ソフトウェアの機能などが容易に認識できる
度合い。
3
理解性の上位の品質特性には保守性があるということは、保守工程における問
題の要因のうち、大部分を占めるのはソフトウェアの理解の困難さであり、その
ために保守担当者はその時間の多くをソフトウェアの理解に割いているといった
ことからも確認できる。従って、ソフトウェアの開発において高い理解性を持つ
ソフトウェアを開発することが保守工程のコストを削減するための重要な手段と
言える。
また理解性が低いソフトは、理解しにくいのはもちろんだが、レビュー後に
フォールトが残りやすい、デバッグでフォールトが見つかりにくい、デバッグで
別のフォールトが混入しやすいと考えられる。よってソフトウェアの理解性が高
いとフォールも少なく、開発者は既存ソフトウェアの理解のためのコストが低く
押さえられる。
2.2
従来手法による理解性評価
このように理解性の問題は保守上の問題との関係も深いため、レビューなどを
行う際発見されることがある。例えば、レビューのひとつのウォークスルーで考
えてみる。ウォークスルーの主な目的は、不具合を早期発見し、品質の向上を図
ることや、他のメンバーが行っている開発内容を理解することが上げられる。
ウォークスルーには様々な手法が存在するが、一般的には設計者やプログラマー
が、ドキュメントやソースコードを説明し、その他の開発者が、誤りや開発標準
違反などについて質問、コメントをするといった方法が取られている。
このプログラマーがソースコードなどを説明している際に、誤りと共に、他の
開発者の理解しにくかったところが質問、コメントとして出てくることになる。
ただし、ウォークスルーの主な目的はソフトウェアに欠陥がないことを確認する
ことであり理解性を確かめるためのものではない。また理解性を定量的に評価す
ることはできないため、理解性に焦点をあてたウォークスルーを行うと生産物の
「書きかた」についての議論に陥り、時間を浪費してしまうことに成りかねない。
また他人の作成したソフトウェアをレビューするためには理解することが必要
になるが、これには非常に多くのコストが掛かる。なぜならソフトウェア開発工
程で生み出された作成者の考え方や思想まで理解しなければソフトウェアの全体
4
が把握できないからである [3]。よって理解のためのモチベーションの維持が困難
になり、せっかくのレビューが中途半端なものに終わってしまう可能性もある。
保守のことを考慮にいれてある程度理解性が低いプログラムは作り直すという
とも考えられるが、いまのところそのような基準 [3] は存在しない。
5
3.
ソフトウェアのオーバーホール
一般にオーバーホールとは、ハードウェア分野でよく用いられるアプローチで
ある。例えば、故障車の修理において故障の症状からその原因部分を特定できな
いような場合などに、オーバーホール(分解検査)が行われる。
本章ではソフトウェアをオーバーホールという考え方について述べ、オーバー
ホールを行うことでソフトウェアを理解するということについて考察する。
3.1
オーバーホール
ハードウェアの分野ではオーバーホールは保守の一環として行われる。すなわ
ち機械が常に効率のよい経済的な運転を維持できるよう、分解点検により正常な
状態かどうか確認し、性能回復のために必要な処置を施すことである。機械の故
障や不経済な運転を防ぐために、オーバーホールは重要であるといわれている。
さらに、オーバーホールは対象となる機械を理解学習することにも役立つと考
えられている。オーバーホールすることで、あらかじめ対象の内部構造を理解、
学習しておくことは、その対象の故障を起こしそうな個所を発見しておくこと、
あるいは故障の予防措置を施すために必要不可欠であると考えられるからである。
自動車のエンジンなどを分解、統合する過程においてそれぞれの部品がどのよう
な役割を果たすかを作業者に理解させめために、オーバーホール実習などがよく
行われている。
本研究ではこのオーバーホールの学習効果、仕組みを理解することに役立つこ
とを、ソフトウェアを理解することに応用する。ソフトウェアをオーバーホール
する、つまり作業者は分解したソフトウェアの一つ一つを吟味し、元あった形に
統合する。この過程を通じて、各部分の働きとそれらの間の関係を段階的に理解
し、最終的にソフトウェア全体を理解することができると考えている。
6
3.2
オーバーホールによる段階的な理解
オーバーホールでソフトウェアを段階的に理解していくことは、ソフトウェア
の理解性を評価する上で有用であると考えている。
このことについて、図を用いて説明する。図 1 はソフトウェアのオーバーホー
ルのイメージである。丸はソフトウェアの一部分、矢印はそれらの間の関係を表
している。
ソフトウェアのオーバーホールでは、この図 1 のようにソフトウェアの一部を
部品として分解し、それが持つ役割を少しづつ理解し、その部品同士の関係を考
えて元のソフトウェアを統合していく。この際、点線で区切られた過程ごとに段
階的にソフトウェアを理解し、最終的にはソフトウェア全体を段階的に理解して
いくことになる。
そこでこの段階的な統合の履歴を利用することでどこが理解しにくいかを判断
できるのではないかと考えている。
½
Ô
図 1 ソフトウェアの分解と統合
7
すなわち統合に時間がかかった部分や統合できなかった部分は、その開発者に
とって理解が難しかったことを意味していると予想できるためであり、そのよう
な理解性の低い部分を優先的に、より熟練した開発者が再オーバーホール、再レ
ビュー、再設計することにより、効率的にソフトウェアを改善することも期待で
きる。
またオーバーホールにおける統合という具体的な目標と作業により、他人の作
成したソフトウェアを理解する時に陥りがちなモチベーションの低下や、先入観
からくる読み飛ばしなどをある程度避けることができるのではないかと考えら
れる。
3.3
ソフトウェアのオーバーホール環境
図 2 にソフトウェアのオーバーホールをおこなう環境を示す。
作業者は PC 上でのオーバーホールツールとドキュメントを使って、ソフトウェ
アのオーバーホールとくに分解されたソフトの統合を行う。
作業者はドキュメントを読みながら、そこに書かれた仕様を満たすよう、分解
されたソフトウェアを統合していくオーバーホールを行う。
エンジンなどのオーバーホールでは部品に分解した段階で、汚れている部品の
洗浄をしたり、磨耗、劣化した部品など交換したりするが、本研究で現在想定し
ているソフトウェアのオーバーホールでは、理解性の評価をすることに主眼をお
いているため、この段階でソフトウェアのおかしくなった部分の修正や、ドキュ
メントの改定を行うことはしない。
すなわち、作業者は統合することが困難であった個所を、オーバーホールが完
了した時点で報告し、PC 上のオーバーホールツールは、統合の過程やその作業
時間などから理解性の評価を行うために、それらを作業履歴として収集する。
8
e·‹·–·¦·¦
Wxƒab]U½)+
WÔ1.î 1ŠŸUI
WÔbL"+îÍo¿
+Þ:.UI
Þ:*
„h œ®ƒU\Bî€}U"K
.xƒab]UÔ
„h œ®ƒ
xƒab]1€}U@
図 2 ソフトウェアのオーバーホール環境
9
並べ替えを用いたソースコードのオーバーホール
4.
レビューなどでチェックすることのできるソフトウェアの成果物は、大抵オー
バーホールすることができると考えている。
ここではそのなかでも特にソースコードをオーバーホールする手法について述
べる。
すなわち具体的な方法として、ソースコード中の各ステートメントを一つの部
品と考え、これを並べ替えることでオーバーホールを行う。
そこでまず PDG(Program Dependence Graph)について簡単に解説し、これ
を用いてソースコード中の各ステートメントを並べ替えることと理解性評価の関
係について述べる。
4.1
PDG の定義
PDG とは、基本的にソースコードの各ステートメントをノードとし、その間
を制御依存関係、データ依存関係といったアークを使って結んだ有向グラフであ
り、プログラムの内部表現を明示することができる。
PDG は、基本的にプログラムの各文をノードとし、任意の文と文の間の依存
関係をアークとするグラフである [5][8]。元来は、プログラムのモジュール化の促
進、デバッグを目的として、プログラムの内部表現を明示するために考案された
ものである。
一般に、PDG のノードはプログラム中の一文に対応する。
定義 1
PDG のノード:プログラム P のノードは、一つの文で構成される。
例えば表 1 のような C 言語のソースコードがあったとき、PDG のノードは以
下の 4 つとなる。
"main()"
"int n"
"print("Input natural number:")"
10
01:
表 1 プログラム例 1
main(){
02:
int n;
03:
print("Input natural number:");
04:
scanf("%d"、&n);
13:
}
"scanf("%d"、&n)"
次に PDG のアークについて述べる。PDG で明示するノード間の関係は 2 つあ
る。ひとつはノード間のデータの流れに関する依存関係で、もうひとつは制御文
と被制御文との間の関係である。前者をデータ依存(Data Dependence)関係と
呼び、後者を制御依存(Control Dependence)関係と呼ぶ。これらの依存関係は
次のように定義されている。
定義 2
データ依存関係: プログラム P 中の文 s1 と文 s2 について次の 3 つの条件
が満されるとき、文 s1 から文 s2 へデータ依存関係があるという。
1. 文 s1 において変数 v が定義されている。
2. 文 s1 から文 s2 へのプログラムの実行可能パス内に v を再定義している文は
存在しない。
3. 文 s2 においての変数 v が参照されている。
表 2 のソースコードを例に考えてみる。
1 行目で、変数 x が定義されており、2 行目では x が再定義されている。3 行目
では、変数 x が参照され、変数 y への代入が行われている。このとき 2 行目の文
から 3 行目の文へはデータ依存関係があるが、1 行目の文から 3 行目の文へのデー
11
表 2 プログラム例 2
01:
x=1;
02:
x=2;
03:
y=x;
表 3 プログラム例 3
01:
if(x=1){
02:
03:
y= x;
}
タ依存関係は無い。なぜなら、2 行目の文で変数 x が再定義されているため、条
件 2 を満たさなくなるからである。
定義 3
制御依存関係: プログラム P 中の文 s1 と文 s2 について以下の 2 つの条件
が満されるとき、文 s1 から文 s2 へ制御依存関係があるという。
1. 文 s1 は条件文かループ文である。
2. 文 s2 が実行されるかどうかは s1 の文が実行されるかどうかに直接依存する。
表 3 のソースコードを例に考える。このとき、1 行目の条件文 if の実行結果 (s1
の実行結果) によって 2 行目の文(s2)が実行される場合とそうでない場合が考
えられる。このような制御文(1 行目の if 文)から被制御文(2 行目の文)への
関係を制御依存関係という。制御依存関係は、しばしば被制御文が制御文となる
ような、入れ子構造を形成する。
12
4.2
並べ替えと理解性
手続き型のプログラム言語では、ソースコードから、例えばステートメントを
ひとつの単位(部品)として分解した場合でも元のソースコードを復元すること
ができる。なぜなら、それらの分解されたステートメントの間には、制御の流れ
やさまざまな変数の関連などが存在するからである。
以下、実際のコード(表 4)を使ってそのステートメント間の関連や制御の流
れについて説明する。
表 4 カレンダープログラムのあるモジュール
Name: ymd2rd2
In : int yy,mm,dd 年, 月, 日を入力
Out : int 基準日から入力された年月日までの日数
01:
int ymd2rd2(int yy,int mm,int dd){
02:
register int cnt;
03:
int ret;
04:
ret = 0;
05:
for (cnt = 1992; cnt > yy; --cnt){
06:
ret -= getdofy(cnt);
07:
}
08:
for (cnt = 12; cnt >= mm; --cnt){
09:
ret -= getdofm(yy,cnt);
10:
}
11:
ret -= dd + 1;
12:
return(ret);
13:
}
13
LQW \PGUGLQW\\LQW PPLQWGG^
UHJLVWHULQWFQW
LQW UHW
UHW IRUFQW FQW ! \\FQW^
UHW JHWGRI\FQW
`
IRUFQW FQW ! PPFQW^
UHW JHWGRIP\\FQW
UHW GG `
UHWXUQUHW
Ùî
=2
`
‚·z
=2
図 3 表 4 の PDG
LQW \PGUGLQW\\LQW PPLQWGG^
UHW GG UHW JHWGRI\FQW
UHJLVWHULQWFQW
UHWXUQUHW
IRUFQW FQW ! \\FQW^
LQW UHW
UHW JHWGRIP\\FQW
`
`
`
UHW IRUFQW FQW ! PPFQW^
図 4 並べ替えられた PDG のノード
14
図 3 は表 4 のソースコードの PDG である。このような PDG から分かるよう
に、ソースコードからは、ステートメント間に変数のデータ依存関係や、条件文か
らの制御依存関係などを読み取ることができる。そのため図 4 のようにステート
メントを抜き出しバラバラに並べ替えた場合でも、それらのステートメント(部
品)を一つ一つ吟味し、残りのステートメントとの依存関係を見つけ出すことで、
コード全体を組み立てる、ここではステートメントの並びの順番をある程度決定
することができる。
例えば 5 行目の"for (cnt = 1992; cnt > yy; {cnt){ "の依存関係を考えたとき、
変数"cnt"と"yy"が参照されているため、データ依存関係の定義から、これより前
にこれらの変数の定義が行われていることがまず分かる。また、この行は制御文
であることと、"cnt"が再定義されていることから、"cnt"が参照されている文、す
なわちデータ依存関係から"ret -= getdofy(cnt);"か、"ret -= getdofm(yy,cnt);"
のどちらか、あるいは両方に制御依存関係があることも推測できる。
作業者は並べ替えの際、特に PDG の依存関係として意識していなくても、こ
れに近い形でこれらのステートメント間の関係をコード理解のひとつのヒント [2]
として利用していると考えられる。
このためソースコードの理解性が高い、すなわち構造的で簡潔、明瞭であれば
これら分解されたステートメントの役割や依存関係も見つけやすく、並べ替えも
容易になる。言い換えれば並べ替えが難しくなるのはソースコードの理解性が低
いからだと言える。
ソースコード自体が複雑なアルゴリズムを使う必要があったり、特殊な事柄を
扱う処理の場合にも並べ替えが難しくなるが、この様な場合でもコード中にコメ
ントを書いたり、ドキュメントを用意することで理解性を上げることができる。
ただしこれらはソースコード中のステートメント間にどのような依存関係が存
在するか、どのような役割を持っているかなどを発見することを助けるものでな
くてはならない。ソースコードと、ドキュメントやコメントの間に矛盾、例えば、
バグ、実装もれ、ドキュメントの品質劣化2 などがあると、理解性は低下し、並
べ替えはさらに困難になる。
2 ソースコードの変更を行った後、ドキュメントの修正まで手がまわらずそのままの状態になっ
ている、最新のソースコードと整合性のとれていないドキュメント
15
すなわちソースコード自体の理解性、さらにコメントやドキュメントの理解性
が並べ替えの難易度におおきく影響する。
4.3
作業者による問題点の発見
分解された状態のソースコードから、作業者が元の仕様にあるようにコードに
統合するためには、ステートメント間の依存関係を把握し、そのソフトウェアへ
の理解を深めなければならない。ソースコードの並べ替えによるオーバーホール、
すなわちステートメントをドキュメントを参照しながら一つ一つ吟味して依存関
係を理解していく過程が、自動車などのオーバーホールと同様に、ソフトウェア
の問題点を発見することに役立つと考えられる。言い換えれば、ソフトウェアの
オーバーホールで、仕様ドキュメント、選択肢、残りのソースコードから、遮断
されているデータや処理の流れを見つけ出して理解し、その発見の過程からソフ
トウェアの問題点の有無を確かめることになる。
また、3.2 節で述べたように、元のソースコードを統合するという明確な目的
が与えられることで、モチベーションの低下や、先入観による読み飛ばしなどを
少なくすることができると考えている。
4.4
履歴を利用した問題点の発見
作業者がオーバーホールする際には、抜き出されたステートメントの役割やス
テートメント間の因果関係を見出し、頭の中でその処理の過程をなぞる、または、
ドキュメントにある仕様を満たす上でそれぞれのステートメントの果たしている
役割を考えるなどの試行錯誤を伴う。作業者はこれらの問題解決の過程を通じて
プログラム全体を段階的に把握、理解していると予想される。この理解の進捗状
況が、並べ替えの作業時間や、答え合わせをした際の誤りの数などに現れると考
え、これらを履歴として収集する。ドキュメントやソースコードに問題点があっ
た場合、作業者がソフトウェアを理解できない、あるいは理解に時間がかかり、
並べ替えが困難な状況に陥ったことがこの履歴を調べることで発見できると考え
ている。
16
5.
オーバーホールツール
ソフトウェアのオーバーホールを並べ替えで行う利点には、分解されたコード
の生成、元のソースコードとの比較、作業履歴の収集などが容易であること、作
業時間が比較的短くてすむことが上げられる。
ソフトウェアのオーバーホールには、この特徴を十分生かせるようなソース
コードの分解ツールとソースコードの統合を行う並べ替えツールが必要である。
5.1
ドキュメント
オーバーホールで使用するドキュメントは、基本的に元のソースコードを作成
するために必要となった仕様書であるが、主に機能仕様書 [4] を想定している。す
なわち、モジュールの機能的、性能的な役割と、どんな入力によってどんな出力
を生ずるかを明記したものである(表 5)。
5.2
コード分解ツール
ソースコードを分解するためには以下のような点を考慮しておかなければなら
ない。
name:ymd2rd2
表 5 機能仕様書の一部
In : int yy, mm, dd 年, 月, 日を入力.
Out: int 基準日から入力された年月日までの日数.
name:getdofm
In : int yy, mm 年, 月を入力.
Out: int 閏年の 2 月ならは 29 を返す. その他は月の日数を返す.
17
5.2.1
ソースコード
主に機能仕様書をドキュメントとして利用するため、ひとつのモジュールをオー
バーホールのためのソースコードにしている。別のモジュールがコード中で参照
されている場合は、そのモジュールの機能仕様書も与える。
5.2.2
単位
ソースコードから抜き出される内容はトークン単位、ステートメント単位、ブ
ロック単位などが考えられる。抜き出されるサイズが大きくなれば並べ替えはよ
り簡単に、小さくなれば一からの再コーディングに近くなり並べ替えの作業に手
間が掛かる。また、オーバーホールにおける一つの部品として抜き出される単位
にはそれ自身にある程度の意味のまとまりが必要となる。今回は使用するソース
コードの大きさを一モジュール程度としていることから、ステートメントもしく
は複合ステートメントを原則として単位に設定した。さらにこれは 4 章で述べた
ようにステートメント間にデータ依存関係や制御依存関係が存在することからも
都合がいい。ただし一行に複数のステートメントがある場合や、引数などがうま
く一行に収まらず、二行に分けて記述されている場合、ステートメントの前後の
行にコメントが併記されている場合なども多々あるので、任意のブロックを一つ
の単位とする抜き出しについても対応する必要がある。
5.3
並べ替えツール
並べ替えツールでは、オーバーホールにおける統合、すなわち、ドキュメント
をみながら、分解されて並べ替えられたソースコードを復元する。また、この統
合の過程を履歴として自動収集し、理解性の定量的評価のために利用する。
5.3.1
ユーザーインターフェース
図 5 はオーバーホールのための並べ替えツールの外観である。これは下記の 3
つのフレームから構成されている。
18
図 5 並べ替えによるオーバーホールツール
19
コードフレーム(左辺) ステートメント、もしくは行を単位として複数の部分が
抜き出されたソースコードを表示する。空行となっている部分にステートメント
を挿入し、元のソースコードを統合する。ステートメントが挿入される際、もと
のソースコードとおなじになるようにインデントをつけてしまうと、ソースコー
ドの内容を考えず、インデントに従って機械的に並べ替えを完了させることがで
きる場合がある。よって、インデントは選択肢の内容と挿入された場所によって
決定し、並べ替えが行われるたびに再描画される。
選択肢フレーム(右辺) 元のソースコードから抜き出しされたステートメント
をランダムに並べ替えて選択肢として表示する。このステートメントをマウスで
選択し、コードフレームにドロップすることで並べ替えを行う。ここでもインデ
ントをそのまま表示しておくと、コードフレームと同様の問題が起こるので、イ
ンデントはすべて削除されている。
チェックフレーム(下辺)
作業者が並べ替えを完了したと考え、チェックボタ
ンを押したとき、元のソースコードが再現できているかを確認し、その結果(一
致しなかったステートメントの数)を表示する。
5.3.2
履歴収集
作業者が各選択肢やチェックボタンをマウスでクリックしたとき、以下の二種
類がソースコードの統合過程の履歴として収集される。表 6 は履歴の例である。
表 6 収集する履歴情報の例
01.97 }
21.75 ret -= getdofm(yy,cnt);
:
:
01.92 0
20
表 6 の二行目について説明すると、ステートメント"ret -= getdofm(yy,cnt);"
が並べ替えられるまでに掛かった時間は 21.75 秒だったことを表している。
すなわち、ステートメント"}"が並べ替えられてから 21.75 秒ステートメント"ret
-= getdofm(yy,cnt);" をどこに並べ替えるか考えていたと思われる。
最後の行は比較が行われたことを表し、不一致が 0、よってすべて一致し、並
べ替えが終わったことを示している。
5.4
オーバーホールの手順
ソフトウェアのオーバーホールの過程を図 6 に示す。
まず、作業者は、ドキュメントを読み、仕様を満たすようにコードを統合するよ
う求められる。分解されたソースコードが表示され、作業者はこれを通じでオー
バーホールを行い、ソフトウェアの評価、および問題点の発見を行う。
この時、システムではオーバーホールの統合の過程を履歴として収集し保存し
ている。
作業者が、並べ替えが終了したと考えたとき、チェックボタンを押すことで、元
のソースコードと統合されたソースコードの比較が行われ、一致しない場合は、
その不一致の数が表示される。
21
?Ž
„h œ®ƒU\C
6§
n·„|b~j
ûL
ûL
³L
図 6 オーバーホールのフローチャート
22
6.
適用実験
今回作成したツールについて適用実験を述べる。オーバーホールに使用したプ
ログラムは、C言語で書かれたもの(ソースコードA)と、Java 言語で書かれた
もの(ソースコードB)を用いている。
ソースコードAは入力された年月に基づいてカレンダーを表示するプログラム
の中から、基準日から入力された日付までの日数を計算するモジュール部分を使
用した(図 4)。プログラム全体の大きさは 430 行程度で、使用したモジュールは
14 行で構成されている。またこのモジュールは内部で別の 2 つのモジュールを呼
出している。作業者は情報系の大学院助手 1 名(約 7 年のプログラミング経験)
である。
ソースコードBは 5 章で紹介したオーバーホールツールの並べ替えを行うプロ
グラムから行数が 5∼30 行程度のメソッドを 6 個取り出して使用している。プロ
グラム全体では約 500 行程度で、主なクラスは 5 個程度である。作業者は情報系
の大学院助手 1 名、大学院生 2 名の合計 3 名である。
これらのソースコードのオーバーホールを並べ替えを用いて行い、その作業履
歴を収集する。これにより並び替えによるソースコードのオーバーホールにかか
るコストを調べることと、作業者による理解性評価と作業履歴から得られた理解
性評価の関係について調べる。
6.1
オーバーホールの手順
手順は以下のとおりである。
Step1
:
作業者には、対象プログラムのドキュメントが渡され、複数のステートメ
ントが抜き出されたモジュールのソースコードが PC 上のツールに表示さ
れる。
23
Step2
:
作業者は、仕様書とモジュールの機能仕様書を参照しながら提示されたス
テートメントの並べ替えをツール上で行う。
Step3
:
並べ替えが終了すると作業者は、ツールを用いて元のソースコードとの比
較を行う。すべて一致すれば Step4 へ、不一致ならば Step2 に戻る。
Step4
:
オーバーホール終了後、どのような問題点が対象ソフトウェアに存在した
かを作業者にインタビューすることで調べる。
6.2
ソースコードAのオーバーホール結果
この並べ替え作業そのもの(Step2、3)に掛かった時間は 42 秒であった。使用
したモジュールは 14 行であるため、ツールを用いたソフトウェアオーバーホー
ルの生産性は、およそ 20LOC/分であった。プログラマ人・月当りのコーディン
グ行数を約 1 KLOC とする [1] と、それをオーバーホールするには 50 分∼204 分
を要することになる。
このカレンダープログラムプログラムについて、ドキュメントとソースコード
の両方について作業者は問題点を見つけることができた。
まずインタビューによって作業者が明らかにした問題点を述べる。
ドキュメントの問題
モジュールが実行される条件(このモジュールは 1992 年以前の年月日につ
いてのみ処理すること)が明記されていない。
返される日数の正常値が負の値であることを記していない。
24
表 7 オーバーホールの履歴
00.00
for (cnt = 1992; cnt > yy; --cnt){
20.44
ret -= getdofy(cnt);
04.56
}
01.87
for (cnt = 12; cnt >= mm; --cnt){
01.97
ret -= getdofm(yy,cnt);
01.75
}
01.92
0
ソースコードの問題
日数を計算する為の基準日がマジックナンバーとなっている(05 行目)。
マイナス記号とプラス記号を間違えている(11 行目)。
また、並べ替えの作業履歴は表 7 のようになった。
この並べ替えの作業履歴によると、作業者はコード中の空欄を上から順番に並
べ替えて埋めていく3 中で、表 7 の二行目の部分を並べ替える前に、他と比べて多
くの時間を費やしていることが分かる。このことを再度作業者にインタビューする
と、この個所で時間を費やしたのは、モジュールの名前(getdofy() と getdofm())
が似ているため、なにが違うのか戸惑ったこと、さらに仕様書でそれらの機能を
確認することに時間がかかったためと分かった。これは、モジュール名を短縮し
すぎて、ソフトウェアの理解が難しくなっていることからくる問題であると考え
られる。
6.3
ソースコードBのオーバーホール結果
並べ替えに使用したのは、Java 言語で書かれた、全体で約 500 行程度のプログ
ラムから、10∼30 行/モジュールのものを 6 個使用した。
3 上から順番に並べ替えていくよう指定した訳ではない
25
~ŧ•Þ:(.}
ŠŒ‡‡
Š‡‡‡
‰Œ‡‡
Ðw”w‡…‡‡ŽŠÏ Šw‚wˆ…Œ‡ŠŠÏ ‰w‚w‰…‡ŒˆÏ
‰‡‡‡
ˆŒ‡‡
ˆ‡‡‡
Œ‡‡
‡
‡
Œ
ˆ‡
ˆŒ
‰‡
‰Œ
Š‡
ŠŒ
s ·¦]*<§"c
図 7 並べ替えに要した時間
まず、並べ替えにかかった時間を図 7 で表す。横軸に一モジュールあたりの行
数、縦軸にそのモジュールを並び替えるのに掛かった時間をとっている。
行数と時間について多項式近似してみると、三次の係数は小さく、ほぼ二次曲
線になっている。このため、30 行程度のモジュールを並べ替えるのに 30 分程度
掛かっているが、行数が増えると、さらに時間が掛かることが予測される。
1 モジュールあたりの並べ替えに要した時間の平均では約 8 分程度となり、これ
を効率で見てみると、平均 126 行/時間=1000 行/日(8 時間)=20000 行/月(20
日)となる。これはプログラマ人・月当りのコーディング行数を約 1K∼2kLOC
(1000∼2000 行/月)と考えた場合その 5.0∼10.0 % 程度となる。
設計とコーディング工数の 15∼20% をレビューに費やしても生産性の低下はな
い [6] といわれていることから、並べ替えによるオーバーホールは実用的なコス
トに収まっていると考えられる。
26
つぎに、作業者がみつけたソースコードとドキュメントの理解性に関する問題
点をあげる。
問題点
クラス変数のコメントがない。
冗長な処理をしている。
コメントとメソッドの名前の関係がわかりづらい。
もっと簡単なやり方がある。
不必要な行がある。
行数が長すぎる。
さらに作業者がみつけたこれらの理解性の問題点を並び替えの作業履歴とを図
8 で比較してみる。
ステートメントの左の数値は、作業者がそのステートメントを元あった位置に
並び替えるまでに使用した答え合わせの回数が表示されている。
例えば、7 行目、答えあわせの回数が 12 回と最も多かった"question.setGroupNo(ls.
getGroup())"というステートメントでは「question と answer いうインスタンスの
役割が、わからない」という作業者の評価に対応すると考えられる。question は
解答欄用 (図 5 の左フレーム) に、answer は回答群用 (図 5 の右フレーム) に使わ
れていたが、ドキュメントに記述もコメントもなかった。また、その他のステー
トメントでも question や answer いうインスタンスが現れるものは、答え合わせ
の回数が、平均より多くなっていることか分かる。
さらに、例にあげた図 8 のモジュールと同様に、他のモジュールでも正解まで
の答え合わせが多かったステートメントは、作業者自身が分かりにくいと評価し
たものとほぼ一致していた。
27
‹
‹
Œ
Œ
‹
‹
ˆ‰
Ž


Œ

‹
‹



‹
‹
wwwww™Ì½½¼É¼»©¼¸»¼ÉwÀÅw”wżÎw™Ì½½¼É¼»©¼¸»¼ÉżÎw ÅÇÌ˪Ëɼ¸Ä©¼¸»¼É½€€’
wwwwwοÀüÃÀżw”wÀŅɼ¸»£Àż€€wx”wÅÌÃÀwÒ
wwwwwwwww¤Ð¡£¸¹¼ÃwÈ̼ÊËÀÆÅw”wżÎw¤Ð¡£¸¹¼Ã€’
wwwwwwwww¤Ð¡£¸¹¼Ãw¸ÅÊμÉwww”wżÎw¤Ð¡£¸¹¼Ã€’
wwwwwwwwwÃʅʼˣÀżÃÀż€’w††wwÃÀżw1Íoè.<ÿ€
wwwwwwwwwÀ½Ãʅ¾¼Ë™Ã¸Å€€wÒw††è.<Z
wwwwwwwwwwwwwÈ̼ÊËÀÆŅʼ˞ÉÆÌÇ¥ÆÃʅ¾¼ËžÉÆÌÇ€€’
wwwwwwwwwwwwwÈ̼ÊËÀÆŅʼËÌÇÃʅ¾¼Ë£Àż€ƒÃʅ¾¼Ë Å»¼ÅË€ƒyyƒËÉ̼€’
wwwwwwwwwwwwwͅ¸»»ÃÀżÅƃÈ̼ÊËÀÆŀ’w††wèU01ÏÎ.LI
wwwwwwwwwwwww¸ÅÊμɅʼ˞ÉÆÌÇ¥ÆÃʅ¾¼ËžÉÆÌÇ€€’
wwwwwwwwwwwww¸ÅÊμɅʼËÌÇyyƒyyƒÃʅ¾¼ËªË¸Ë¼Ä¼ÅË€ƒËÉ̼€’
wwwwwwwwwwwwwͅ¸»»œÃ¼Ä¼Å˸ÅÊμɀ’††w01U¨ß.J
wwwwwwwwwÔ¼ÃʼÒw††Ubñ+NZ
wwwwwwwwwwwwwÈ̼ÊËÀÆŅʼËÌÇÃʅ¾¼Ë£Àż€ƒÃʅ¾¼Ë Å»¼ÅË€ƒÃʅ¾¼ËªË¸Ë¼Ä¼ÅË€ƒ½¸Ãʼ€’
wwwwwwwwwwwwwͅ¸»»ÃÀżÅƃÈ̼ÊËÀÆŀ’w††wèU01ÏÎ.LI
wwwwwwwwwwwww¸ÅÊμɅʼËÌÇyyƒyyƒyyƒ½¸Ãʼ€’
wwwwwwwwwwwwwͅ¸»»œÃ¼Ä¼Å˸ÅÊμɀ’††w01U¨ß.J
wwwwwwwwwÔ
wwwwwwwwwÃÀżÅƂ‚’
Þ:*.KNO
nœ®ƒÐDQVZHU+TXHVWLRQ1Î
Ð-
図 8 理解性評価の比較
28
\BO-ï
7.
まとめと今後の課題
本研究のまとめと、今後の課題、その課題の解決方法について述べる。
7.1
まとめ
本研究では、再利用ソフトウェアなどに潜在する問題を見つけるための方法と
して、ソースコードのオーバーホール(分解検査)を考案し、その具体的な方法
としてコード中のステートメントを並べ替えることを提案した。またこの作業を
支援する環境を構築した。
この手法を用いることで、ソフトウェアのチェックを行う作業者は、コードの
統合という具体的な目標を持つことができ、ソフトウェアの理解が進むと考えて
いる。またこの統合の過程を理解の過程と考え、履歴として収集している。この
履歴からどのようなコードのチェックが行われたかを再確認することが可能であ
ると思われる。
さらに提案手法と支援環境の適用例で、オーバーホールに掛かる時間について
考察した。これにより、ソフトウェアのオーバーホールで作業者が理解しにくかっ
た個所がそのソフトウェアの問題点である可能性を指摘している。
7.2
今後の課題
適用実験を通じて本研究で作成したオーバーホールツールには修正が必要であ
とことがわかった。具体的には以下の 3 点が今後の課題として挙げられる。
1. 並べ替えるステートメントをお互いに入れ替えてもモジュールの機能が同
じになる場合を判定できない。
2. 分解するステートメントを増やしすぎると並べ替えるのが非常に難しい。
3. 解答欄にステートメントを挿入する際、自動的にもとのソースコードと同
じインデントがつくためどこからどこまでが制御文に含まれるかが分かっ
てしまう。
29
1 つ目はステートメントの順序そのものが単純に元のソースコードと一致して
いるかをチェックしているために起こってしまう。このためステートメントの位
置が入れ替わっていてもモジュール全体としては正常に動作するものも一々並べ
替えていかなくてはならない。この課題の解決方法としては PDG による依存関
係を比較する方法が考えられる。この場合、PDG の定義から上記のような問題
は起こらない。
2 つ目は、分解されたステートメントが増えると、回答群からステートメント
を選択して解答欄に並べ替えるという作業そのものに手間が掛かってしまうから
だと考えられる。ある程度意味のまとまりをもったステートメントをブロックと
して抜き出し、並べ替えできないか検討している。
3 つ目の問題は、インデントを見るだけでなにも考えずに、元のソースコード
を復元できてしまう場合があるため、理解性を評価するときに問題があるのでは
ないかと考られることである。
インデントを作業者に入力させるか、ツールの方で作業者が並べ替えたソース
コードに基づいたインデントを挿入する方法で解決できると考えられるが、そう
した場合、並べ替えの難しさが大幅に上がってしまうことが予想される。なぜな
ら、並べ替えの作業を観察すると、作業者はこのインデントをヒントにして全体
の大まかな構成を把握してから細かな並べ替えに移る様子が確認できるからで
ある。
また、ハードウェアのオーバーホールの場合でも、全体の構造と部品の機能は
その「形」が有効であり、例えば実際に直接はめ込んでみれば、ぴったり当ては
まるかどうかは感触で分かることから、インデントをそのまま残しておくほうが、
ハードウェアのオーバーホールの対応という点で考えるとよいのかもしれない。
そのため、このインデントによるヒントが、理解性を評価する上でどの程度影
響があるのかさらに考察する必要がある。
30
謝辞
本研究を遂行するにあって多くの方々にご指導、ご助言、ご協力を頂きました。
まず主指導教官である井上克郎教授に対し、厚く御礼を申し上げたいと思い
ます。
副指導教官である渡邉勝正教授には、研究の本質に関わる事柄について鋭い御
指導、御指摘を頂き、研究を次のステップへと進ませる力を与えて頂きました。
心より感謝致しております。
松本健一助教授、飯田元助教授には、様々な御指摘、御意見を頂きました。ま
た研究においてのみならず、研究を離れた場面においてもさまざまな御助言を頂
きました。深く感謝致します。
門田暁人助手には、本研究に関する問題について多くの有益な御指摘、御助言
を頂きました。心より感謝致します。
島和之助手には、日頃から、また本研究テーマの決定、論文作成、発表準備ま
で、研究を進めるにあたって、多大な御指導、御助言を頂きました。頂いた多く
の御指摘は非常に示唆に富むものでした。心から感謝致します。
大阪芸術大学短期大学部の武村泰宏助教授には本研究テーマを見つけるきっか
けを与えて頂き、さらに本研究を進めるにあたっても多くの資料と適切な御助言
を頂きました。深く感謝致します。
日頃、様々な御助言、御協力を頂いたソフトウェア計画構成学講座の皆様に心
から感謝致します。
秘書の森川由希さんには、研究活動を様々な面から支えて頂きました。感謝の
意を表したいと思います。
最後に、家族、友人、その他 2 年間私を支えて頂いた皆さんに感謝します。あ
りがとうございました。
31
参考文献
[1] C.Jones 著, 井上義祐,荒木淳三監訳,“ システム開発の生産性 ”, マグロ
ウヒルブック,pp.8-13, 1986.
[2] 柏原昭弘,寺井淳裕,豊田順一,“ いかにプログラム空欄補充問題を作る
か?”, 人工知能学会研究会資料,no.SIG-IES-9901-2, pp.9-16, May.1999.
[3] 河村一樹,“ 保守に関する現状の問題点 ”,ソフトウェア工学入門,近代科
学社,pp.198-201, 1995
[4] 管野文友,“ ドキュメントの種類 ”,ソフトウェア・デザインレビュー,日
科技連出版社,pp.38-40,1982
[5] 下村隆夫,“ Program Slicing 技術とテスト、デバッグ、保守への応用 ”,情
報処理学会, vol.33, no.9, pp.1078-1086, 1992
[6] 高木徳生, 田中敏文, 新原直樹, "実績データによるレビュー有効性の評価",
ソフトウェアシンポジウム,pp.50-55,June 14-16,1995
[7] 玉井哲雄,三嶋良武,松田茂弘,“ ソフトウェアの品質 ”,ソフトウェアの
開発技法,共立出版株式会社,pp.164-168, 1988.
[8] Horwitz,S.,Reps,T., and Binkley,D. "Interprocedural Slicing Using Dependence Graphs", ACM Transactions on Programming Languages and Systems, vol.12, no.1, pp.26-60, 1990
32
付録
A.
適用実験で用いたソースコードA
適用実験で用いたのはモジュール"ymd2rd2"のみである。また、モジュール"ymd2rd2
"の末尾にフォルトについてのコメントがついているが、適用実験では削除して
行われている。
#include <stdio.h>
#include <string.h>
char
cal[12][8][22];
int
mchk(int);
int
dchk(int,int,int);
int
getdofm(int,int);
int
getdofy(int);
int
isulu(int);
int
ymd2rd(int,int,int);
int
getdt(int);
void
rd2ymd(int *,int *,int *,int);
int
ymd2rd1(int,int,int);
int
getdbm(int,int);
void
setcal(int,int,char [][]);
void
mcmd(int,int);
void
ycmd(int);
int
ymd2rd2(int,int,int);
void
printdt(int,int,int);
void
scmd(int,int,int,int);
void
usage(void);
33
/*************************************************/
/* name:main
*/
/* In: None
*/
/* Out: None
*/
/* 入力されたコマンドにより制御する
*/
/*************************************************/
main(){
char buf[BUFSIZ];
char cmd[BUFSIZ];
int
yy;
int
mm;
int
dd;
int
rr;
int
ret,ret1;
printf("> ");
fflush(stdout);
gets(buf);
while (1) {
ret = sscanf(buf,"%s%d%d%d%d",cmd,&yy,&mm,&dd,&rr);
if (ret >= 1) {
if (cmd[0] == 'm') {
if ((ret == 3)&& mchk(mm)) {
mcmd(yy,mm);
}
else {
usage();
}
34
}
else if (cmd[0] == 'y') {
if ((ret == 2)) {
ycmd(yy);
}
else {
usage();
}
}
else if (cmd[0] == 'd') {
if ((ret == 4)&& dchk(yy,mm,dd)) {
printdt(yy,mm,dd);
}
else {
usage();
}
}
else if (cmd[0] == 's') {
if ((ret == 5)&& dchk(yy,mm,dd)) {
scmd(yy,mm,dd,rr);
}
else {
usage();
}
}
else if (cmd[0] == 'q') {
if (ret == 1) {
exit(0);
}
35
else {
usage();
}
}
else {
usage();
}
}
printf("> ");
fflush(stdout);
gets(buf);
}
}
/*************************************************/
/* name:mchk
*/
/* In: int mm 月を入力
*/
/* Out: int 0
誤った入力の場合
*/
/*
正しい入力の場合
*/
int 1
/* 月が 1∼12 の間にあるか調べる
*/
/*************************************************/
int mchk(int mm){
return((mm > 0)&&(mm < 13));
}
/*************************************************/
/* name:dchk
*/
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 0 正しい入力でない場合
36
*/
/*
int 1 正しい入力の場合
*/
/* 日が 0∼31 の間にあるか調べる
*/
/*************************************************/
int dchk(int yy,int mm,int dd){
if (!mchk(mm)) {
return(0);
}
if (dd < 1) {
return(0);
}
if (dd <= getdofm(yy,mm)) {
return(1);
}
else {
return(0);
}
}
/*************************************************/
/* name:getdofm
*/
/* In: int
yy,mm 年, 月を入力
*/
/* Out: int
閏年の 2 月ならば 29 を返す
*/
/*
その他は月の日数を返す
*/
/*************************************************/
int getdofm(int yy,int mm){
static int dom[] = {31,28,31,30,31,30,31,31,30,31,30,31};
/* 1 2 3 4 5 6 7 8 9 10 11 12 */
if ((mm == 2) && isulu(yy)) {
return(29);
37
}
return(dom[mm-1]);
}
/*************************************************/
/* name:getdofy
*/
/* In: int yy 年を入力
*/
/* Out: int
閏年ならば 366 を返す
*/
/*
その他は 365 を返す
*/
/*************************************************/
int getdofy(int yy){
return (isulu(yy)?366:365);
}
/*************************************************/
/* name:isulu
*/
/* In: int yy 年を入力
*/
/* Out: int
閏年ならば 1 を返す
*/
/*
その他は 0 を返す
*/
/*************************************************/
int isulu(int yy){
int ret;
ret = 0;
if(yy % 4 == 0) {
ret = 1;
if((yy % 100 == 0)&&(yy % 400 != 0)) {
ret = 0;
}
}
38
return (ret);
}
/*************************************************/
/* name:ymd2rd
*/
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 基準日から入力された年月日までの日数 */
/*************************************************/
int ymd2rd(int yy,int mm,int dd){
register int cnt;
int
ret;
ret = 0;
if (yy >= 1993) {
ret = ymd2rd1(yy,mm,dd);
}
else {
ret = ymd2rd2(yy,mm,dd);
}
return(ret);
}
/*************************************************/
/* name:getdt
*/
/* In: int rd 日数を入力
*/
/* Out: int
*/
/* 曜日の計算
*/
/*************************************************/
int getdt(int rd){
39
int
ret;
if (rd >= 0) {
return((rd + 5) % 7);
}
rd = -rd;
rd %= 7;
return((7 - rd + 5) % 7);
}
/*************************************************/
/* name:rd2ymd
*/
/* In: int yy,mm,dd,rd 年, 月, 日を入力
*/
/* Out: None
*/
/* 基準年から入力された年月日までの日数を計算
*/
/*************************************************/
void rd2ymd(int *yy,int *mm,int *dd,int rd){
register int cnt;
*yy = 1993;
*mm = 1;
*dd = 1;
if (rd < 0) {
while (rd < 0) {
*yy -= 1;
rd += getdofy(*yy);
}
}
while (rd >= getdofy(*yy)) {
40
rd -= getdofy(*yy);
*yy += 1;
}
while (rd >= getdofm(*yy,*mm)) {
rd -= getdofm(*yy,*mm);
*mm += 1;
}
*dd += rd;
return;
}
/*************************************************/
/* name:ymd2rd1
*/
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 基準日から入力された年月日までの日数 */
/*************************************************/
int ymd2rd1(int yy,int mm,int dd){
int
ret;
register int cnt;
ret = 0;
for (cnt = 1993; cnt < yy; ++cnt) {
ret += getdofy(cnt);
}
for (cnt = 1; cnt < mm; ++cnt) {
ret += getdofm(yy,cnt);
}
ret += dd - 1;
return(ret);
41
}
/*************************************************/
/* name:getdbm
*/
/* In: int yy,mm 年月を入力
*/
/* Out: int
*/
*/
/* 曜日の計算
/*************************************************/
int getdbm(int yy,int mm){
int dt;
dt = getdt(ymd2rd(yy,mm,1));
return(7-dt);
}
/*************************************************/
/* name:setcal
*/
/* In: int yy,mm,mcal 年月と作業用は配列
*/
/* Out: int
*/
/* カレンダーを表示するための配列を作成
*/
/*************************************************/
void setcal(int yy,int mm,char mcal[8][22]){
char
buf[22];
char
numbuf[22];
int
dbm;
int
dofm;
int
len;
register int dcnt;
register int wcnt;
42
register int daynum;
sprintf(mcal[0],"
%4d %d",yy,mm);
sprintf(mcal[1]," S M T W T F S");
dofm = getdofm(yy,mm);
dbm = getdbm(yy,mm);
buf[0] = '\0';
for (dcnt = 7 - dbm; dcnt > 0; --dcnt) {
strcat(buf,"
");
}
for (dcnt = 0; dcnt < dbm; ++dcnt) {
sprintf(numbuf,"%2d ",dcnt+1);
strcat(buf,numbuf);
}
strcpy(mcal[2],buf);
daynum = dbm+1;
for(wcnt = 3;wcnt<8;++wcnt){
buf[0] = '\0';
for(dcnt=0;(dcnt<7)&&(daynum<=dofm);++dcnt,++daynum){
sprintf(numbuf,"%2d ",daynum);
strcat(buf,numbuf);
}
strcpy(mcal[wcnt],buf);
}
}
/*************************************************/
/* name:mcmd
*/
43
/* In: int yy,mm
*/
/* Out: None
*/
/* 入力された年月のカレンダーを表示する
*/
/*************************************************/
void mcmd(int yy,int mm){
register int cnt;
setcal(yy,mm,cal[0]);
for (cnt = 0; cnt < 8; ++cnt) {
printf("%-21s\n",cal[0][cnt]);
}
return;
}
/*************************************************/
/* name:ycmd
*/
/* In: int yy 年を入力
*/
/* Out: None
*/
/* 入力された年のカレンダーを表示する
*/
/*************************************************/
void ycmd(int yy){
register int mcnt;
register int wcnt;
for (mcnt = 0; mcnt < 12; ++mcnt) {
setcal(yy,mcnt+1,cal[mcnt]);
}
for (mcnt = 0; mcnt < 12; mcnt += 3) {
for (wcnt = 0; wcnt < 8; ++wcnt) {
printf("%-21s ",cal[mcnt][wcnt]);
44
printf("%-21s ",cal[mcnt+1][wcnt]);
printf("%-21s\n",cal[mcnt+2][wcnt]);
}
}
return;
}
/*************************************************/
/* name:ymd2rd2
*/
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 基準日から入力された年月日までの日数 */
/*************************************************/
int ymd2rd2(int yy,int mm,int dd){
register int cnt;
int
ret;
ret = 0;
for (cnt = 1992; cnt > yy; --cnt) {
ret -= getdofy(cnt);
}
for (cnt = 12; cnt >= mm; --cnt) {
ret -= getdofm(yy,cnt);
}
ret -= dd + 1;
return(ret);
}
/*********This is bug line.**********************
ret -= dd + 1;
45
**********Answer is ret += dd - 1;***************/
/*************************************************/
/* name:printdt
*/
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: None
*/
*/
/* 入力された年月日の曜日を調べる
/*************************************************/
void printdt(int yy,int mm,int dd){
static char *dtstr[]= {"Sunday","Monday","Tuesday","Wendsday",
"Thurthday","Friday","Sataday"};
int
dt;
dt = getdt(ymd2rd(yy,mm,dd));
printf("%04d/%02d/%02d is %s\n",yy,mm,dd,dtstr[dt]);
}
/*************************************************/
/* name:scmd
*/
/* In: int yy,mm,dd,rr 年, 月, 日, 日数を入力
*/
/* Out: None
*/
/* 入力された年月日から rr 日前 (後) の日付を計算
*/
/*************************************************/
void scmd(int yy,int mm,int dd,int rr){
int newyy;
int newmm;
int newdd;
rd2ymd(&newyy, &newmm, & newdd, ymd2rd(yy,mm,dd)+rr);
46
printf("%d day%s %s %04d/%02d/%02d is %04d/%02d/%02d\n",
abs(rr),(abs(rr)==1)?"":"s",(rr>0)?"after":"before",
yy,mm,dd,newyy,newmm,newdd);
}
/*************************************************/
/* name:usage
*/
/* In: None
*/
/* Out: None
*/
/* コマンドの入力例を示す
*/
/*************************************************/
void usage(void){
printf("USAGE:\n");
printf("
m year month\n");
printf("
y year\n");
printf("
d year mont day\n");
printf("
s year mont day sa\n");
}
47
B.
適用実験で用いたソースコードAのドキュメント
仕様書
・万年カレンダーを作る.この万年カレンダーには以下の機能がある.
(1) 指定した月のカレンダーを表示
(2) 指定した年のカレンダーを表示
(3) 指定した年月日に関するデータを表示
(4) 指定した年月日の n 日前あるいは n 日後に関するデータを表示
各機能の詳しい説明は以下の通りである.
(1) 指定した月のカレンダーを表示
コマンド m によって指定した月のカレンダーを表示する.
入力は年 (西暦) と月である.
入力,出力例:
> m 1993 6
1993 6
S M T W T F S
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
(2) 指定した年のカレンダーを表示
コマンド y によって指定した年のカレンダーを表示する.
入力は年 (西暦) である.
入力,出力例:
48
> y 1993
1993 1
1993 2
1993 3
S M T W T F S
S M T W T F S
S M T W T F S
1 2
1 2 3 4 5 6
1 2 3 4 5 6
3 4 5 6 7 8 9
7 8 9 10 11 12 13
7 8 9 10 11 12 13
10 11 12 13 14 15 16 14 15 16 17 18 19 20 14 15 16 17 18 19 20
17 18 19 20 21 22 23 21 22 23 24 25 26 27 21 22 23 24 25 26 27
24 25 26 27 28 29 30 28
28 29 30 31
31
……(中略)
1993 10
1993 11
1993 12
S M T W T F S
S M T W T F S
S M T W T F S
1 2
1 2 3 4 5 6
1 2 3 4
3 4 5 6 7 8 9
7 8 9 10 11 12 13
5 6 7 8 9 10 11
10 11 12 13 14 15 16 14 15 16 17 18 19 20 12 13 14 15 16 17 18
17 18 19 20 21 22 23 21 22 23 24 25 26 27 19 20 21 22 23 24 25
24 25 26 27 28 29 30 28 29 30
26 27 28 29 30 31
31
(3) 指定した年月日に関するデータを表示
コマンド d によって指定した年月日の曜日を表示する.
入力は年 (西暦),月,日である.
入力,出力例:
> d 1993 6 4
1993/06/04 is Friday.
49
(4) 指定した年月日の n 日前あるいは n 日後に関するデータを表示
コマンド s によって指定した年月日の n 日前あるいは n 日後を表示する.
入力は年 (西暦),月,日,n である.
入力,出力例:
(1993 年 6 月 4 日の 15 日後)
> s 1993 6 4 15
15 days after 1993/06/04 is 1993/06/19.
(1993 年 6 月 4 日の 15 日前)
> s 1993 6 4 -15
15 days before 1993/06/04 is 1993/05/20.
コマンド q(終了) が入力されるまで,プログラムは引続きコマンドの入力を
要求する.
注)閏年の計算
うるう年とは基本的に 4 の倍数の年であるが, 例外があるので注意が必要である.
100 の倍数の年の中で 400 の倍数でない年はうるう年ではない. 例えば,
1900 年は,4 の倍数であるが,100 の倍数でもあるので, うるう年でない.
2000 は,100 の倍数であるが,400 の倍数でもあるのでうるう年である.
400 年間では 97 年のうるう年があることになる.
50
C.
適用実験で用いたソースコードAのモジュール機能
仕様書
/*************************************************/
/* name:main
*/
/* In: None
*/
/* Out: None
*/
/* 入力されたコマンドにより制御する
*/
/*************************************************/
/*************************************************/
/* name:mchk
*/
/* In: int mm 月を入力
*/
/* Out: int 0 誤った入力の場合
*/
/*
*/
int 1 正しい入力の場合
/* 月が 1∼12 の間にあるか調べる
*/
/*************************************************/
/*************************************************/
/* name:dchk
*/
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 0 正しい入力でない場合
*/
/*
*/
int 1 正しい入力の場合
/* 日が 0∼31 の間にあるか調べる
*/
/*************************************************/
51
/*************************************************/
/* name:getdofm
*/
/* In: int yy,mm 年, 月を入力
*/
/* Out: int
閏年の 2 月ならば 29 を返す
*/
/*
その他は月の日数を返す
*/
/*************************************************/
/*************************************************/
/* name:getdofy
*/
/* In: int yy 年を入力
*/
/* Out: int
閏年ならば 366 を返す
*/
/*
その他は 365 を返す
*/
/*************************************************/
/*************************************************/
/* name:isulu */
/* In: int yy 年を入力
*/
/* Out: int
閏年ならば 1 を返す
*/
/*
その他は 0 を返す
*/
/*************************************************/
/*************************************************/
/* name:ymd2rd */
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 基準日から入力された年月日までの日数 */
/*************************************************/
52
/*************************************************/
/* name:getdt */
/* In: int rd 日数を入力
*/
/* Out: int
*/
/* 曜日の計算
*/
/*************************************************/
/*************************************************/
/* name:rd2ymd */
/* In: int yy,mm,dd,rd 年, 月, 日, 日数を入力
*/
/* Out: None
*/
/* 基準年から入力された年月日までの日数を計算
*/
/*************************************************/
/*************************************************/
/* name:ymd2rd1 */
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 基準日から入力された年月日までの日数 */
/*************************************************/
/*************************************************/
/* name:getdbm */
/* In: int yy,mm 年月を入力
*/
/* Out: int
*/
*/
/* 曜日の計算
/*************************************************/
53
/*************************************************/
/* name:setcal
*/
/* In: int yy,mm,mcal 年月と作業用は配列
*/
/* Out: int
*/
/* カレンダーを表示するための配列を作成
*/
/*************************************************/
/*************************************************/
/* name:mcmd
*/
/* In: int yy,mm
*/
/* Out: None
*/
/* 入力された年月のカレンダーを表示する
*/
/*************************************************/
/*************************************************/
/* name:ycmd
*/
/* In: int yy 年を入力
*/
/* Out: None
*/
/* 入力された年のカレンダーを表示する
*/
/*************************************************/
/*************************************************/
/* name:ymd2rd2 */
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: int 基準日から入力された年月日までの日数 */
/*************************************************/
54
/*************************************************/
/* name:printdt */
/* In: int yy,mm,dd 年, 月, 日を入力
*/
/* Out: None
*/
/* 入力された年月日の曜日を調べる
*/
/*************************************************/
/*************************************************/
/* name:scmd */
/* In: int yy,mm,dd,rr 年, 月, 日, 日数を入力
*/
/* Out: None
*/
/* 入力された年月日から rr 日前 (後) の日付を計算
*/
/*************************************************/
/*************************************************/
/* name:usage */
/* In: None
*/
/* Out: None
*/
*/
/* コマンドの入力例を示す
/*************************************************/
55
D.
適用実験で用いたソースコードB
適用実験で並べ替えに用いたメソッドは以下の 6 つである。
FileRead
mouseClicked
listShue
setIndent
ClickedLabel
labelChange
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
public class test {
static MyJLabel[] list;
// ラベル (行) の配列
static int L_HEIGHT = 24;
// ラベルの高さ
static int L_WIDTH = 630;
// ラベルの幅
static Font font = new Font("Serif", Font.BOLD,16);
public static void main(String[] args) {
/*フレームの作成*/
56
JFrame frame = new JFrame("-List-");
frame.addWindowListener(new WindowEventHandler());
Container c = frame.getContentPane();
c.setLayout(new BorderLayout());
/*ラベルのセット ベクターを配列へ変換*/
Vector v = FileRead(args[0]);
list = new MyJLabel[v.size()];
v.copyInto(list);
// メインパネルの追加
JScrollPane sp = MakeScrollPanel(list);
c.add("North",sp);
// 答え合わせボタンの追加
JButton answerButton = new JButton("Answer Check");
answerButton.addActionListener(new ButtonListener());
c.add("South",answerButton);
frame.pack();
frame.setVisible(true);
}
static Vector FileRead(String filename){
int lineno = 0;
//行番号
Vector v = new Vector();
//ソースの行を一時保管
String line = null;
LineScanner ls = new LineScanner();
try{ /*ファイル処理*/
57
FileInputStream f = new FileInputStream(filename);
BufferedReader in = new BufferedReader(new InputStreamReader(f));
while((line = in.readLine()) != null) {
MyJLabel question = new MyJLabel();
MyJLabel answer
= new MyJLabel();
ls.setLine(line); // 行 line の判定 (空行にすべきか?)
if(ls.getBlank()) { //空行にすべき場合
question.setGroupNo(ls.getGroup());
question.setup(ls.getLine(),ls.getIndent(),"",true);
v.add(lineno,question); // 空行を元の位置に挿入
answer.setGroupNo(ls.getGroup());
answer.setup("","",ls.getStatement(),true);
v.addElement(answer);// 元の行を最後に追加
}else{ //行を通常表示する場合
question.setup(ls.getLine(),ls.getIndent(),ls.getStatement(),false);
v.add(lineno,question); // 空行を元の位置に挿入
answer.setup("","","",false);
v.addElement(answer);// 元の行を最後に追加
}
lineno++;
}
in.close();
}catch(IOException e) {
e.printStackTrace();
}
return v;
}
static JScrollPane MakeScrollPanel(MyJLabel[] list){
58
int b = 0;
int lineNo = list.length/2;
MyPain pain1 = new MyPain(); // 問題文のフレーム
pain1.setPreferredSize(new Dimension(L_WIDTH, L_HEIGHT*(lineNo+2)));
MyPain pain2 = new MyPain(); // 回答群のフレーム
pain2.setPreferredSize(new Dimension(L_WIDTH, L_HEIGHT*(lineNo+2)));
for(int i=0;i<list.length;i++){ // 各フレームにラベルを追加
list[i].setOriginalLineNo(i+1);
list[i].setFont(font);
list[i].setPreferredSize(new Dimension(L_WIDTH, L_HEIGHT));
if(i<lineNo){// 問題文
pain1.add(list[i]);
list[i].setLocation(0, L_HEIGHT+L_HEIGHT*i);
}else{
// 回答群
pain2.add(list[i]);
list[i].setLocation(0, L_HEIGHT+L_HEIGHT*b);
b++;
}
}
// 答え合わせに使うために配列をセット
list[0].setList(list); list[0].setLastNo(lineNo);
// 選択文をシャッフル
//list[0].listShuffle(list.length-lineno,list.length-1);
// パネルの追加
JPanel panel = new JPanel();
59
panel.add(pain1); panel.add(pain2);
JScrollPane sp = new JScrollPane(panel);
sp.setPreferredSize(new Dimension(L_WIDTH*2, L_HEIGHT*24));
return sp;
}
// リスナクラス
static class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent ae) {
list[0].answerCheck();
}
}
}
class MyJLabel extends JLabel
implements MouseListener,MouseMotionListener {
protected boolean clickable = false; //クリックできるかどうか?(空
欄になるか?)
protected boolean clicked
= false; //クリックされてるか?(一回目
のクリック)
protected String original
= null; //もともとの行の内容
protected String statement
= null;
protected String indent
= null;
protected int originalLineNo = 0;
//行番号
protected int groupNo
//グループ番号
= 0;
static MyJLabel[] list
= null; //どれがクリックされてるかの検索用
static int lastNo
= 0;
//これ以以前は問題文、以降は選択文
60
/*コンストラクタ*/
public MyJLabel() {
super();
addMouseListener(this);
addMouseMotionListener(this);
setOpaque(true);
}
/*パブリックメソッド*/
public void setup(String sOriginal, String sIndent,
String sStatement, boolean bClickable) {
this.setText(sIndent + sStatement);
original = sOriginal;
statement = sStatement;
indent = sIndent;
clickable = bClickable;
if(clickable){
setClickableColor();
}else{
setNotClickableColor();
}
}
// オリジナルラベルの設定
public void setOriginal(String t) {
original = t;
}
61
public String getOriginal() {
return original;
}
// オリジナルラインナンバーの設定
public void setOriginalLineNo(int t) {
originalLineNo = t;
}
public int getOriginalLineNo() {
return originalLineNo;
}
// グループ設定
public void setGroupNo(int no) {
groupNo = no;
}
public int getGroupNo() {
return groupNo;
}
// インデント設定
public void setIndent(String text) {
indent = text;
}
public String getIndent() {
return indent;
}
// ステートメント設定
62
public void setStatement(String text) {
statement = text;
this.setText(indent + statement);
}
public String getStatement() {
return statement;
}
// 問題文と解答郡の境界を設定
public static void setLastNo(int no) {
lastNo = no;
}
/*プライベートメソッド*/
/*選択されていない行 (クリックできる) の設定*/
private void setClickableColor(){
if(groupNo%2 == 0){
this.setForeground(Color.black);
this.setBackground(Color.gray);
}else{
this.setForeground(Color.gray);
this.setBackground(Color.black);
}
this.clicked = false;
this.clickable = true;
}
/*選択された行 (一度クリックされている) の設定*/
private void setClickedColor(){
this.setForeground(Color.white);
63
this.setBackground(Color.blue);
this.clicked = true;
}
/*普通の行 (クリックできない) の設定*/
private void setNotClickableColor(){
this.setForeground(Color.black);
this.setBackground(Color.white);
this.clickable = false;
}
/*普通の行の反転 (クリックできない) の設定*/
private void setReversNotClickableColor(){
this.setForeground(Color.red);
this.setBackground(Color.white);
}
/*行の表示の入れ替え*/
private void labelChange(MyJLabel destination, MyJLabel sourse){
String sTemp = sourse.statement;
sourse.setStatement(destination.statement);
destination.setStatement(sTemp);
destination.setClickableColor();
sourse.setClickableColor();
}
/*
行の配列でクリックされているのはどれか?
クリックされたラベルを返す
64
*/
private MyJLabel ClickedLabel(MyJLabel[] l){
for(int i=0; i<l.length; i++){
if(l[i].clicked){
return list[i];
}
}
return null;
}
/*パブリッククラスメソッド*/
public static void setList(MyJLabel me[]) {
list = me;
}
// 配列をシャッフル。範囲は start -> last
public static void listShuffle(int start,int last){
Random rnd = new Random();
int j = 0;
int w = last-start+1;
for(int i=0;i<w;i++){
j = rnd.nextInt(w-i);
String tmp = list[last-i].getText();
int itmp = list[last-i].getGroupNo();
list[last-i].setText(list[last-j-i].getText());
list[last-i].setGroupNo(list[last-j-i].getGroupNo());
list[last-j-i].setText(tmp);
list[last-j-i].setGroupNo(itmp);
}
65
}
// 答え合わせ
public static void answerCheck(){
int count = 0;
for(int i=0; i<lastNo; i++){
if( list[i].clickable ){
String org = new String(list[i].getOriginal().trim());
String ans = new String(list[i].getStatement().trim());
if(org.equals(ans)){
list[i].clickable=false;
list[i].setNotClickableColor();
}else{
count++;
}
}
}
Date time = new Date();
System.out.println("a" +","+
time.getTime() +","+ count );
}
/* マウスイベントハンドラ */
public void mouseClicked(MouseEvent e) {
Date time = new Date();
// 移動可能なラベルの場合
if(this.clickable){
// 選択されたラベルがすでにあるか
66
MyJLabel ml = ClickedLabel(list);
// 選択されているラベルがすでにある場合
if(ml != null) {
// 選択ラベルのグループナンバーが同じ場合
if(ml.getGroupNo() == this.getGroupNo()) {
// ラベルの入れ替え
this.labelChange(ml,this);
System.out.println("Drop" +","+ time.getTime() +","+
this.originalLineNo +","+ ml.getStatement() );
// 選択ラベルのグループナンバーが同じでない場合
}else{
// ラベルの表示を元にもどす
this.setClickableColor();
ml.setClickedColor();
}
// 選択されているラベルがない場合
}else{
this.setClickedColor();
System.out.println("Drag" +","+ time.getTime() +","+
this.originalLineNo +","+ this.getStatement() );
}
}
}
// マウスの出入りでラベル表示の変更
public void mouseEntered(MouseEvent e) {
if(this.clickable){
// 背景だけ変更
this.setBackground(Color.cyan);
67
}else{
this.setReversNotClickableColor();
}
}
public void mouseExited(MouseEvent e) {
if(this.clicked){
this.setClickedColor();
}else{
if(this.clickable){
// 背景をもとにもどす
this.setClickableColor();
}else{
this.setNotClickableColor();
}
}
}
/* 今のところ使わないマウスイベントハンドラ */
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseDragged(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
}
class WindowEventHandler extends WindowAdapter {
public void windowClosing(WindowEvent e){
System.exit(0);
}
}
68
class MyPain extends JComponent {
/*
* コンストラクタ。
* レイアウトマネージャは、非常に単純なもの。位置を変えずに、そのサイズ
* を PreferredSize に設定するだけ。
*/
public MyPain() {
setLayout(new LayoutManager() {
public void addLayoutComponent(String s, Component c) {}
public void removeLayoutComponent(Component c) {}
public Dimension preferredLayoutSize(Container destination) {
return new Dimension(0, 0);
}
public Dimension minimumLayoutSize(Container destination) {
return new Dimension(0, 0);
}
public void layoutContainer(Container destination) {
int numberOfComponents = destination.getComponentCount();
for (int i = 0; i < numberOfComponents; i++) {
Component comp = destination.getComponent(i);
Dimension sz = comp.getPreferredSize();
comp.setSize(sz);
}
}
});
setBackground(Color.white);
setOpaque(true);
}
69
/* 不透明な JComponent なので背景を自分で描画する。*/
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (super.isOpaque()) {
Dimension sz = super.getSize();
g.setColor(super.getBackground());
g.fillRect(0, 0, sz.width, sz.height);
}
}
}
class LineScanner {
protected boolean blank
= false;
protected int
= 0;
group
protected String line
= new String();
protected String indent
= new String();
protected String statement = new String();
public void setLine(String text) {
statement = text.trim();
indent = setIndent(text);
blank = false;
if( statement.endsWith("/*1*/") ){
blank = true;
group = 1;
statement = (statement.substring(0,statement.length()-5));
}
70
if( statement.endsWith("/*2*/") ){
blank = true;
group = 2;
statement = (statement.substring(0,statement.length()-5));
}
line = indent + statement;
}
public boolean getBlank() {
return blank;
}
public int getGroup() {
return group;
}
public String getLine() {
return line;
}
public String getStatement() {
return statement;
}
public String getIndent() {
return indent;
}
// インデントのスペースを返す (タブには未対応)
protected String setIndent(String text) {
int count = 0;
String spaces = "
";
for(int i=0;i<text.length();i++) {
71
if( text.charAt(i) == ' ' ) {
count++;
}else{
break;
}
}
return spaces.substring(0,count);
}
}
72
Fly UP