プログラム・プロブレム

ティータイム 2 プログラミングの実際


I はじめに

初(以下H):初(はじめ)だぞ。今回はどうやってプログラムを書くか話してみよう。
涼(以下S):涼(すず)です。今回のネタはチャット仲間からのご提供です。ありがとうございます。
H:で、題材なんだが、よく windows で使われる GUI プログラムがいいという話だったんだが…
S:GUI は言語ごと、使うライブラリごとに書き方が違う場合が多いんです。
H:というわけで CUI のプログラムということになるな。
S:それで、頂いたアイデアは「指定したディレクトリ以下のJPGファイルの場所を表示する」というプログラムにします。

II 設計

開発方法

H:世の中には色々な開発方法が存在するが、基本的には「設計」→「コーディング」→「テスト」→「完成」になる。
S:まずは、どういう条件でどう動くかを考えるわけです。これが「設計」になります。
H:設計がある程度固まってきたら、実際のプログラムを書く。これが「コーディング」だ。
S:プログラムが書けたら、ちゃんと動作するか「テスト」するわけです。
H:最終的にテストを通るプログラムができたら「完成」となるな。

S:これを順序良く進めていくモデルが「ウォーターフォール」です。よく使われるんですが、あんまりうまく開発できることはありません。
H:何故かと言えば、完全に順序良く進められる場合なんてほとんど無いからだな。
S:その代わりに「設計」「コーディング」「テスト」をぐるぐる順番に進めていくモデルが「スパイラル」です。
H:こっちの方が実情に合ってるんだが、スケジュールの引き方がややこしくなるんで、見積りの時は「ウォーターフォール」でやってしまう事が多いんだ。
S:特に日本ではまだまだウォーターフォールでの見積りが主流なので、現場泣かせの状況になりやすいです。
H:俗に言う「デスマーチ」状態になる確率が高いと。難儀だね。

S:どちらのモデルにも、いくつかの方法論が提唱されています。
H:で、最近の流行はスパイラルモデル寄りの「アジャイル」開発手法だな。
S:作者の乏しい理解では「動くコードを優先にした開発方法論」ですね。
H:その目標を達成するために、「プラクティス」と呼ばれる各種の細かい活動規範が決められている。
S:実際の作業から生まれたプラクティスを実施するため、かなり有効な開発方法だと言われています。
H:これ以上詳しい話は専門のWebサイトに任せた。検索してみてくれ。

仕様の策定

S:さて、どの方法論を使うにしても、プログラムへの要求を明確にしなければなりません。
H:「何を行うのか」ってことだな。「どう行うのか」は以降の話になる。
S:今回の場合は「指定したディレクトリ以下のJPGファイルを探して場所を表示する」という要求です。
H:機能としてはこれでいいんだが、実はその他にも決めなきゃいけないことがある。
S:「非機能要件」と言われる部分です。意外に疎かにされやすい点なので注意が必要です。
H:例えば「プラットフォーム」はどうするのか?今回は Windows 上で Windows API を使うとする。
S:その他にも「○秒以内に処理が完了すべき」みたいな「性能要件」などが指定される事もあります。
H:これらの様々な点を考慮して「仕様」としてまとめるわけだ。

言語の選択

S:仕様が決定すれば、設計に入れるはずなんですが、今回は先に言語を決めることにします。
H:もちろん、仕様で既に言語が規定されている場合もあるぞ。
S:今回の題では特に制約は無いので、言語は Ruby を使うことにします。
H:理由は… C++ ではファイルシステムの処理が面倒だからだ。
S:複数の言語を扱えるようにしておくと、こういう場合に適切な言語を選べて便利なこともあります。
H:業務の場合、言語は決定済みの場合がほとんどだがね。

設計

S:ここからがプログラマの領分です。入門とかでは、あんまり解説されない領域でもあります。
H:今回は言語が決まってるから、言語固有の機能も視野に入れた状態で設計に入れるわけだ。
S:まずは指定されたディレクトリを入力からもらってくる必要がありますね。
H:そして、出力するのはJPGファイルの場所のリストだ。
S:設計では、この間の隙間を埋めていきます。

H:ちょっと考えると、とりあえず指定ディレクトリ内のファイルとディレクトリのリストを得る方法が必要だな。
S:もちろん、ファイルなのかディレクトリなのかを区別する方法も必要です。
H:そして、ファイルだったらファイル名の最後をチェックする方法が要るわけだ。
S:こんな感じで必要な機能を考えていきます。これらを組み合わせると次のようになりますね。
01:指定ディレクトリ dir を入力から得る
02:あるディレクトリ d 以下のJPGファイルの場所を出力する手順の開始
03:  d の中にあるファイルとディレクトリのリストを得る
04:  リストのそれぞれに対して、現在の項目を f とすると
05:    もし f がファイルなら
06:      ファイルの最後が JPG かどうか確認して、そうならば場所を出力する
07:    もし f がディレクトリなら
08:      02行以降の手順を f に対して実行する
09:以上で02行の手順定義を終了
10:02行の手順を dir に対して実行する
H:ここまで分解できれば OK だ。
S:自力でこれを作れるようになれば、立派にプログラマの仲間入りです。

H:じゃあ、どうやって作ったかを追いかけてみるぞ。
S:まず01行目は問題無いですね。
H:02から10行目までの作り方はこうだ。ディレクトリ内のファイルとディレクトリの一覧を取れるとする。
S:実際、Ruby にはそういう機能があります。この辺は言語の知識や調べ方に依存しますね。
H:一覧が取れたら、それを順番に調べていけばいいだろう、と。ここまでで03、04行目ができる。
S:次は、今持っているモノがファイルなのかディレクトリなのか調べる必要がありますね。これが05と07行目です。
H:ファイルだったら JPG かどうか調べて出力の有無を判断すればいい。これが06行目になる。
S:ディレクトリの場合は、その中に対して同じような処理をすればいいと思いませんか?
H:つまり、03行から定義している自分自身を f に対して実行するわけだ。これに気付けば後は楽勝だろう。
S:ここまでで「d 以下のJPGファイルの場所を出力する処理」ができてるので、これを dir に適用して終わりです。

別解(もうちょっと簡単に)

H:上の設計ができる人は、ここを読む必要は無いな。
S:もうちょっと初心者向けの設計だと次のようにもできますね。
01:指定ディレクトリ dir を入力から得る
02:あるディレクトリ d のJPGファイルを出力する手順の定義開始
03:  d のファイルとディレクトリの一覧を得る
04:  一覧の各項目について、該当のものを f とすると
05:    もし f がファイルならば
06:      ファイル名の末尾を調べ、JPGファイルなら場所を出力する
07:02行からの手順定義終了
08:あるディレクトリ d の中のディレクトリのリストを得る手順の定義開始
09:  結果返却用のリスト l を準備する
10:  d のファイルとディレクトリの一覧を得る
11:  一覧の各項目について、該当のものを f とすると
12:    もし f がディレクトリならば
13:      リスト l に f を追加する
14:  結果であるリスト l を返す
15:08行からの手順定義終了
16:対象ディレクトリの一覧を入れるリスト l を準備する
17:未探索のディレクトリを入れるリスト tmp を準備する
18:tmp に dir を追加する
19:tmp の中身が無くなるまで以下を繰り返す
20:  tmp の中から一つ取り出して f とする
21:  f を対象に08行の手順を実行し、結果のリストを tmp に追加する
22:  f を l に追加する
23:20行からの繰り返し終了
24:l の中身それぞれについて、該当のものを d とする
25:  d に対して02行の手順を実行する
H:まず01行目は問題無いよな。入力を取ってくるわけだ。
S:02から07行目までは、あるディレクトリ d の直下にある JPG ファイルの場所を出力する手順ですね。
H:そして、08から15行目までは、あるディレクトリ d の直下にあるディレクトリを取り出す手順になる。
S:それぞれの内容については、ちょっと考えれば分かると思います。
H:16から23行目までがキモだな。ここでは dir 以下の全てのディレクトリのリストを取っている。
S:それができれば、後はそのリストの各ディレクトリ中にある JPG ファイルの場所を出力すれば終わりです。

III コーディング

実装

H:設計ができれば、あとは手順をプログラミング言語のコードに変換していけばいいわけだ。
S:この時にはリファレンスマニュアルが役に立ちます。
01:dir = ARGV[0]
02:def list_jpeg(d)
03:  l = Dir.entries(d)
04:  l.each do |f|
  :    next if f == "." # (1)
  :    next if f == ".." # (2)
  :    path = File.join( d, f )
05:    if File.ftype(path) == "file"
06:      puts( path ) if /\.jpg$/i =~ f
  :    end
07:    if File.ftype(path) == "directory"
08:      list_jpeg( path )
  :    end
  :  end # (3)
09:end
10:list_jpeg( dir )
H:これでほとんど置き替えただけのコードになるな。
S:いくつか補足説明をしておきましょう。まず (1) と (2) ですが…
H:あるディレクトリ d のファイルとディレクトリ一覧を取ると「.」と「..」っていうのも含まれるんだ。
S:これは「自分自身」と「一つ上」にあたるんですが、これに対して list_jpeg() を実行してしまうと大変な事になります。
H:だから「.」と「..」の時は次の項目へ進む「next」を使っている。
S:まあ、Ruby は賢いので、実行しても途中でエラー終了してくれますが。
H:どうなるかは… 考えてみてくれ。試してみても分かるが。
S:それから (3) ですが、これは04行の「do」の終わりを示す「end」です。文法上必要なんです。
H:以上になるな。後はリファレンスと首っ引きで考えてもらえば大丈夫だろ。

IV テスト

S:今回のプログラムの場合、テストと言っても実際に実行してみるくらいですね。
H:JPGファイルのあるディレクトリと、無いディレクトリを指定してみて、ちゃんと表示されてれば OK って感じか。
S:実際の業務の場合は、それらのチェックを自動的に行えるようにしておいて、誰がテストしても同じ事が起きるようにするのが正しいです。
H:テスト環境の設定やテスト実行を自動化するツールが各種あるから調べるといいぞ。

V 終わりに

S:プログラム作成の実際を簡単に解説してみましたが、どうでしょう?
H:設計の部分あたりがネックなんだよな。あそこがクリアできれば後は比較的簡単だ。
S:後はコーディング中に設計がマズいと分かれば、一旦戻って設計を修正するのは普通です。
H:実際、後になればなるほど知識は増えてくわけだから、考えるのを後回しにしていい部分は積極的に遅延させるべきだな。
S:その辺の感覚は、いくつかプログラムを作ってくうちに身に付いていくと思いますよ。
H:結局のところ、苦労した分だけ勉強になるのは世の常ってことだ。学問に王道無し。頑張れ。

ティータイム 2 終了


一覧に戻る