• 検索結果がありません。

I 2 tutimura/ I 2 p.1/??

N/A
N/A
Protected

Academic year: 2021

シェア "I 2 tutimura/ I 2 p.1/??"

Copied!
39
0
0

読み込み中.... (全文を見る)

全文

(1)

ソフトウェア開発実践セミナー

I

∼ソフトウエアを残すために

2

土村 展之

[email protected]

http://www.misojiro.t.u-tokyo.ac.jp/˜tutimura/

情報理工学系研究科 数理情報学専攻

2002

年

4

月

25

日

(2)

分割コンパイルの意義

•

大規模

/

多人数によるプログラム開発

•

デバッグ時に部分コンパイルで時間短縮

•

関数

/

変数のスコープをファイル単位に制限

•

パーツの再利用

•

Makefile

の利用が前提か

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.2/??2

(3)

Makefile

とは

•

ファイル生成手順を書き並べたもの

•

make

コマンドで手順を実行

ファイルの新旧を比較して必要なファイルのみ生成しなおす

•

ある程度の手順はあらかじめ登録されている

•

GNU make

をはじめとして書式にいくつかの方言

(独自拡張)

•

タブとスペースの区別を厳密に

•

やはり雛型の利用を

(4)

Makefile

利用の意義

•

コンパイル・リンク方法の明文化

•

開発時の分割コンパイルを効率化

•

不要ファイル削除方法を明文化

•

プログラム開発以外にも

L

A

TEX

コンパイル等の応用

•

必要ファイルをアーカイブ(=バージョン管理)

•

テスト実行方法の明文化

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.4/??2

(5)

core

ができた時の

デバッガの簡単な使い方

(1)

gcc, gdb

の場合

•

コンパイル時のオプションに

-g

または

-ggdb

を加

える

% gcc hogehoge.c -O2 -Wall -g

•

テスト実行(ほぼ速度低下しない)

% ./a.out

•

Segmentation fault

が出て止まると

core

ファイ

ルができる

sh/bash

系では

’ulimit -c’, csh/tcsh

系では

’limit -h’

コマンド

(6)

core

ができた時の

デバッガの簡単な使い方

(2)

•

デバッガを起動

% gdb ./a.out core

・

止まった関数の呼び出された引数と、関数の中で

の止まった場所が表示される

・

コンパイルオプション

-g/-ggdb

なしだと、ここ

で表示される情報が少なくなる

・

デバッガ内でのコマンド

list

ソースを表示

where

関数の呼出し関係を表示

print

変数の値を表示

quit

終了

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.6/??2

(7)

Java

では

...

例外発生時に関数の呼出し関係を表示させる

try {

...

} catch (Exception e) {

e.printStackTrace();

}

(8)

標準入出力とは

•

UNIX

をはじめとする

OS

では

画面とキーボードはファイルの一種!

キーボードの入力終了は

CTRL+D

•

コンソール

(shell)

でプログラムを走らせる時は

標準入力,標準出力,標準エラー出力が利用可能

普通の接続先

file

へ接続先変更

標準入力

キーボード

command < file

標準出力

画面

command > file

(上書き)

command >> file

(付け足し)

標準

画面

(sh/bash)

command 2> file

エラー入力

(csh/tcsh)

command >& file

(9)

フィルタ

—

標準入出力を使う

•

1

入力

1

出力ならば

fopen()/fclose()

を使わなく

てよい

—

入力ファイルは1度だけ読み込める

•

画面に表示したものはファイルに保存できる

ファイルに書きたくないものは標準エラー出力を

fprintf(stderr, "..")

C

C++

Fortran

Java

標準入力

stdin

cin

5

System.in

標準出力

stdout

cout

6

System.out

標準

stderr

cerr

多くは

System.err

(10)

標準入出力では力不足なのは

•

入力ファイルが2つ以上

•

出力ファイルが2つ以上

標準エラー出力は2つめの出力ファイルとしては不適当

(画面に表示されることが期待されている)

•

入力ファイルを先頭に戻って同じ内容をもう一度読み

込む

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.10/??2

(11)

パイプの利用

•

% command1 | command2

では

“command1”

の標準出力が

“command2”

の標準

入力につながる

•

% command1 > tempfile

% command2 < tempfile

と結果は等価

•

中間ファイルがディスクに書き込まれなくなるので、

処理が早くなることが期待できる

(12)

パイプの実用

後戻りして見る

“command | less”

行単位のソート

“command | sort”

表示しながらファイルに保存

“command | tee outputfile”

データ圧縮

“gzip -cd input.gz | command”

(入力が圧縮済)

“command | gzip > output.gz”

(出力を圧縮)

(13)

バージョン管理

/

アーカイブ製作

(1)

•

UNIX

なら

tar.gz

“hogehoge-1.0.tar.gz”

の中身は

“hogehoge-1.0/README”

などとディレクトリを作って

製作

tar cvf - hogehoge-1.0/ | gzip > hogehoge-1.0.tar.gz

確認

gzip cd hogehoge1.0.tar.gz | tar tvf

-展開

gzip cd hogehoge1.0.tar.gz | tar xvf

-tar

のバージョンによっては、以下も使用可能

製作

tar czvf hogehoge-1.0.tar.gz hogehoge-1.0/

確認

tar tzvf hogehoge-1.0.tar.gz

(14)

バージョン管理

/

アーカイブ製作

(2)

•

MS-Windows

なら

zip/lzh

“hogehoge-1.0.zip”

の中身は

“README”

などとフォルダを作らず

製作

zip -r ../hogehoge-1.0.zip *

確認

unzip -l hogehoge-1.0.zip

展開

unzip hogehoge-1.0.zip

→

手作業ではせず、スクリプトや

Makefile

で

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.14/??2

(15)

diff

と

patch

•

2

つのテキストの差異を抽出するツール

・

ファイル名2つを指定

・

-r

でディレクトリ名2つを指定

・

-c, -u

で出力形式を変更

•

patch

コマンドで逆の操作

差分と片方のテキストからもう片方のテキストを生成

diff -ur software-20020424/memory_leak.c software-20020425/memory_leak.c --- software-20020424/memory_leak.c Sun Apr 21 12:29:45 2002

+++ software-20020425/memory_leak.c Wed Apr 24 22:52:46 2002 @@ -7,7 +7,7 @@

int *x;

x = malloc(sizeof(x[0]) * 100); - for (i=0; i<101; i++) {

(16)

C

言語の種類

K&R (1973)

関数プロトタイプ宣言なし

,

void*

型なし

ANSI (1989)

関数プロトタイプ宣言あり

,

void*

型あり

現在の主流

C99 (1999)

//

コメントあり

,

標準複素数ライブラリあり

これから広まるのか?

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.16/??2

(17)

汎用パーツを作る人種

•

実コードの抜粋 ≠ 汎用パーツ

•

実コードは汎用化への第一歩

•

汎用化のための手間や複雑化と効率化や信頼性確保の

トレードオフ

•

汎用化しやすい部分を見抜く

•

誰もが汎用パーツを作れるわけではない

汎用パーツ製作に溺れないよう

...

(18)

例題

-III

問

:2

つの長方形領域

[x

1

, x

2

]

× [y

1

, y

2

]

と

[x

3

, x

4

]

× [y

3

, y

4

]

に共通する部分

((

面積

)

>

0)

があるかどうかを判定する関数

を作れ。

int is_overlapped(int x1, int x2, int y1, int y2,

int x3, int x4, int y3, int y4)

{

(ここを考えて下さい)

}

以下の動作を期待

is_overlapped(0,5, 0,5, 2,6, 2,6) ==> TRUE

is_overlapped(0,5, 0,5, 6,9, 6,9) ==> FALSE

is_overlapped(0,5, 0,5, 0,5, 5,9) ==> FALSE

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.18/??2

(19)

x

軸方向と

y

軸方向に分けて考える

•

x

軸方向に射影して、重なりがある。

•

y

軸方向に射影して、重なりがある。

この両方が成り立つときに限って、長方形に重なりがある。

int is_overlapped(int x1, int x2, int y1, int y2,

int x3, int x4, int y3, int y4)

{

return is_overlapped2( x1, x2, x3, x4 )

&& is_overlapped2( y1, y2, y3, y4 );

}

(20)

ちょっと脱線

同じ処理を

2

度書くと

...

•

保守性、再利用性が下がる。

バグが見付かるたびに

2

ヶ所を書き直す必要がある。

•

動作確認が困難になる。

後述するテストの手間が増える。

•

下請け関数を独立して使えなくなる。

•

3

次元以上への拡張が困難になる。

•

x

軸と

y

軸に対称な性質を持っていることが、ソース

コードから読みとれなくなる。

(むしろ、異なる性質を持っているように読める。

)

→

冗長性が高いとロクなことがない。

(開発速度は早いかも)

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.20/??2

(21)

話を戻して

問題を単純化

/*

閉区間

(a,b)

と

(c,d)

に共通部分があるかどうか

*/

int is_overlapped2(int a, int b, int c, int d)

{

(ここを考える)

}

(22)

条件チェックを忘れない

#include <assert.h>

/*

閉区間

(a,b)

と

(c,d)

に共通部分があるかどうか

*/

/*

ただし

a < b, c < d */

int is_overlapped2(int a, int b, int c, int d)

{

assert( a < b );

assert( c < d );

return ( c < b && a < d );

}

→ a = b

や

c

= d

の処理も追加できそう。

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.22/??2

(23)

条件を緩める

#define TRUE

1

#define FALSE 0

#include <assert.h>

/*

閉区間

(a,b)

と

(c,d)

に共通部分があるかどうか

*/

/*

ただし

a <= b, c <= d */

int is_overlapped2(int a, int b, int c, int d)

{

assert( a <= b );

assert( c <= d );

if ( a == b || c == d ) return FALSE;

return ( c < b && a < d );

}

(24)

構成の見直し

#define TRUE 1

#define FALSE 0

#include <assert.h>

/* 閉区間 (a,b) と (c,d) に共通部分があるかどうか (a<=b, c<=d) */ int is_overlapped(int a, int b, int c, int d) {

assert( a <= b ); assert( c <= d ); if ( a == b || c == d ) return FALSE; return ( c < b && a < d ); } /* 長方形領域 [x1,x2] × [y1,y2] と [x3,x4] × [y3,y4] に共通面積があるかどうか */ /* (x1<=x2, y1<=y2, x3<=x4, y3<=y4) */

int is_overlapped2(int x1, int x2, int y1, int y2, int x3, int x4, int y3, int y4) { return is_overlapped( x1, x2, x3, x4 )

&& is_overlapped( y1, y2, y3, y4 ); }

(25)

テストプログラムを書く

#include <stdio.h>

void test(int a, int b, int c, int d, int answer) {

int ret = is_overlapped(a, b, c, d);

if (ret && !answer) {

printf("[%d,%d] and [%d,%d] "

"should not have any overlap.\n",

a, b, c, d);

} else if (!ret && answer) {

printf("[%d,%d] and [%d,%d] "

"should have an overlap.\n",

a, b, c, d);

(26)

行ったテストを残す

int main() { test(0,3, 4,9, FALSE); test(0,4, 3,8, TRUE); test(0,8, 3,4, TRUE); test(0,8, 3,8, TRUE); test(0,8, 0,8, TRUE); test(0,0, 0,0, FALSE); test(0,8, 1,1, FALSE); /* 長さ 0 の区間が包括されている */ test(0,1, 8,8, FALSE); test(0,0, 0,8, FALSE); test(0,4, 4,9, FALSE); return 0; } ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.26/??2

(27)

単体テスト(ユニットテスト)の方針

(1)

•

境界条件を確認するのが重要

•

理想的には網羅的に条件を列挙したい

•

書いたプログラムの各行は、すべからく実行される

べし

•

本体と平行して作る(本体よりも先に作る人もいる)

•

本体にバグが見付かるたびにテストを追加する

•

本体を修正するたびに、以前のテストをすべてパスす

ることを確認しよう

•

エラーがあった時のみ報告させる

(28)

単体テスト(ユニットテスト)の方針

(2)

•

自分のバグだけでなく、コンパイラの信じられない動

作が見付かることもある

•

コピー&ペーストをしてたら、どのぐらいテストすれ

ば安心できます?

•

テスト作成に手間がかかりすぎることも

...

•

デバッグの困難な部分だけをテストすればほぼ十分

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.28/??2

(29)

例題

-IV

2つの区間

[a

, b]

と

[c

, d]

の共通する部分の長さを返す関

数を作れ。

/* [a,b]

と

[c,d]

の共通部分の長さを返す

*/

int intersection(int a, int b, int c, int d)

{

(ここを考えて下さい)

}

以下の動作を期待

intersection(1,5, 2,6) ==> 3

intersection(1,2, 3,4) ==> 0

(30)

解答例

#include <assert.h>

#define MAX(a,b) ( ( (a)>=(b) ) ? (a):(b) ) /* 有名なマクロ */ #define MIN(a,b) ( ( (a)<=(b) ) ? (a):(b) ) /* 自作しない */ /* [a,b] と [c,d] の共通部分の長さを返す(a<=b,c<=d の制限付き) */ int intersection(int a, int b, int c, int d) {

int lower, upper, length; assert(a <= b && c <= d);

lower = MAX(a,c); upper = MIN(b,d);

length = upper - lower; if (length < 0) return 0; return length;

}

(31)

悪い解答例

#include <assert.h>

/* 変数名の悪い例 */

/* [a,b] と [c,d] の共通部分の長さを返す(a<=b,c<=d の制限付き) */ int intersection(int a, int b, int c, int d) {

int i, j, k; assert(a <= b && c <= d); if (a > c) i = a; else i = c; /* i = max(a,c); */ if (b > d) j = d; else j = b; /* j = min(b,d); */ k = i - j; if (k < 0) return 0; return k; }

(32)

構成の見直し

/* [a,b] と [c,d] の共通部分の長さを返す(a<=b,c<=d の制限付き) */ int intersection_normalized(int a, int b, int c, int d) {

(以前の内容)

}

/* [a,b] と [c,d] の共通部分の長さを返す(区間 [2,1] はエラー) */ int intersection(int a, int b, int c, int d) {

if ( a > b ) { fprintf(stderr, "intersection: a > b\n"); exit(1); } if ( c > d ) { fprintf(stderr, "intersection: c > d\n"); exit(1); } return intersection_normalized(a,b,c,d);

}

/* [a,b] と [c,d] の共通部分の長さを返す(区間 [2,1] の長さは 0 とする) */ int intersection2(int a, int b, int c, int d) {

if ( a > b ) return 0; if ( c > d ) return 0;

return intersection_normalized(a,b,c,d); }

(33)

テストプログラム例

void test(int a, int b, int c, int d, int answer) { int ret = intersection(a, b, c, d);

if (ret != answer) {

printf("error! [%d,%d] [%d,%d] ==> %d, which should be %d.\n", a, b, c, d, ret, answer); } } int main() { test(1,5, 2,6, 3); test(5,1, 6,2, 3); test(1,2, 3,4, 0); test(1,1, 1,1, 0); test(0,0, 0,0, 0); test(-1,-1, -1,-1, 0);

(34)

条件分岐−例題

二値変数

a

, b, c, d

がある。下の表のように、

a

, b, c, d

の値

に従って

X

,

Y

,

□

Z

のどれかの処理をしたい。どのような条

件分岐を行えばよいか。

b

!b

a

!a

a

!a

d

c

X

Y

X

Y

!c

Y

X

Y

X

!d

c

X

X

X

X

!c

□

Z

□

Z

□

Z

□

Z

例

:

if (a && b && c && d) {

X();

} else if (!a && b && c && d) {

Y();

} ...

(35)

条件分岐−解答例

(1)

if ( d ) {

if ( (a && c) || (!a && !c) ) {

X();

} else {

Y();

}

} else {

if ( c ) {

X();

} else {

Z();

}

}

b

!b

a

!a

a

!a

d

c

X

Y

X

Y

!c

Y

X

Y

X

!d

c

X

X

X

X

!c

□

Z

□

Z

□

Z

□

Z

(36)

条件分岐−解答例

(2)

#define XOR(a,b)

( !(a) ˆ !(b) )

/* ( !(a) != !(b) )

でも等価

*/

if ( !d && !c ) Z();

else if ( !d &&

c ) X();

else if ( XOR(a,c) ) Y();

else

X();

b

!b

a

!a

a

!a

d

c

X

Y

X

Y

!c

Y

X

Y

X

!d

c

X

X

X

X

!c

□

Z

□

Z

□

Z

□

Z

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.36/??2

(37)

条件分岐−解答例

(3)

int table[] = {

1,2,1,2, 2,1,2,1, 1,1,1,1, 3,3,3,3

};

int index = 0;

if (!a) index += 1;

if (!b) index += 2;

if (!c) index += 4;

if (!d) index += 8;

switch (table[index]) {

case 1: X(); break;

case 2: Y(); break;

case 3: Z(); break;

b

!b

a

!a

a

!a

d

c

X

0

Y

1

X

2

Y

3

!c

Y

4

X

5

Y

6

X

7

!d

c

X

8

X

9

X

10

X

11

!c

□

Z

12

□

Z

13

□

Z

14

□

Z

15

(38)

条件分岐−解答例

(4)

void (*func[])() = {

X, Y, X, Y,

Y, X, Y, X,

X, X, X, X,

Z, Z, Z, Z

};

int index = 0;

if (!a) index += 1;

if (!b) index += 2;

if (!c) index += 4;

if (!d) index += 8;

(*func[index])();

b

!b

a

!a

a

!a

d

c

X

Y

X

Y

!c

Y

X

Y

X

!d

c

X

X

X

X

!c

□

Z

□

Z

□

Z

□

Z

ソフトウェア開発実践セミナーI∼ソフトウエアを残すために – p.38/??2

(39)

条件分岐−まとめ

•

条件式

1

つと二値変数

1

つは等価

•

複雑な条件分岐には

すべての場合の一覧を作ってみるのもよい

•

テーブル利用は最終手段

–

たいていは意味のある簡単なルールが存在

参照

関連したドキュメント

Seiler, Gauge Theories as a Problem of Constructive Quantum Field Theory and Sta- tistical Mechanics, Lecture Notes in Physics, 159(1982) Springer

平成26年の基本方針策定から5年が経過する中で、外国人住民数は、約1.5倍に増

洋上液化施設及び LNGRV 等の現状と展望を整理するとともに、浮体式 LNG 受入基地 を使用する場合について、LNGRV 等及び輸送用

名称 International Support Vessel Owners' Association (ISOA) 国際サポート船オーナー協会. URL

&amp; Shipyarrd PFIs.. &amp;

パターン 1 は外航 LNG 受入基地から内航 LNG 船を用いて内航 LNG 受入基地に輸送、その 後ローリー輸送で

2)海を取り巻く国際社会の動向

パキロビッドパックを処方入力の上、 F8特殊指示 →「(治)」 の列に 「1:する」 を入力して F9更新 を押下してください。.. 備考欄に「治」と登録されます。