Ruby言語でプログラムを書く場合、文法的に合ってさえいればプログラムは動く。しかし、プログラムを作成していく過程では、何度も読み直して修正したり、他の人に見てもらう必要があるため、一目見ただけでプログラムの構造が分かりやすいような形式で書いていくことが望ましい。実際、構造が分かりやすいように気を付けて書いたものは完成までにかかる時間が短くなる。
プログラムを書く場合のスペースの空け方、括弧の置き方など、色々な流儀があるが、統一の取れたスタイルは読みやすい。以下のルールを守って書くとプログラムは非常に見やすくなる。
Emacsであれば全ての行でTabキーを押すことで自動的に揃えられる。
if y == 3 then
x = 0
print "foo\n"
end
↓
if y == 3 then
x = 0
print "foo\n"
end
インデント位置を揃えることはプログラムの end
の数が合わなくなるというミスを減らすためだけでなく、プログラムの論理構造を視覚的に分かりやすくするために非常に重要である。揃えることで if, while, for
などの制御構文がどこから始まりどこで終わっているかが一目瞭然となる。
C-SPC でマークした位置からポイント位置までをまとめてインデントするには M-C-\ (または M-x indent-region [Return])をタイプする。
大事な意味を持つ変数を、x
とか x2
無味乾燥な名前にせず、products, sum
といった値の意味が想像しやすい名前にする。
制御構造や条件分岐にはかならず意味があるはずである。その意味を表すようなコメントを書いておく。
無意味なコメントの例:
scores=[] # scores配列を用意
sum=0 # sumを0にする
i=0 # iを0にする
while line=gets # line=getsが真である間繰り返す
scores[i] = line.to_i # scores[i]にline.to_iを代入
sum += scores[i] # sumにscores[i]を足す
i += 1 # iに1を足す
end
意味のあるコメントの例:
scores=[] # 全員分の得点を記憶する配列
sum=0
i=0
while line=gets # 点数の入力が続く間繰り返す
scores[i] = line.to_i
sum += scores[i]
i += 1
end
解説:上の例に含まれている「sumを0にする」のようなコメントは、ソースコード自体を見ることだけでも分かることしか説明しないので意味がない。それに対して下の例ではより抽象的でプログラムを分かりやすくするコメントが記載されている。
Emacsであれば、M-;(メタセミコロン)をタイプすると簡単に決まった桁位置に #
が入れられる。
引数区切りなどのカンマの後には1つスペースを入れる。
printf("%sの残りのマッチは%d本です\n", who, match)
↑ ↑
一般的に空白を多めにするとスッキリして見やすくなるが、入れすぎるとどこが大事なのかがつかみにくくなる。とくに括弧の内側は中に数式を入れることが多いので、数式を分かりやすくするためには括弧の内側には空白を入れない方がよい。
x[ i ] # NG
x[i] # OK
コンピュータに与えるプログラムに含まれる論理的誤りのことをバグという。プログラムにひそんだバグを取り除く作業のことをデバッグという。デバッグするためには、プログラムが動くときのことを頭の中で追いかける必要があるので、文法エラーを取り除くのよりはるかに時間がかかる。プログラムを作る過程では、最初に書き進めて行く時間よりも後から間違いを直す(デバッグする)時間の方が長い。熟練するにつれ、デバッグにかかる時間が短くなり、同時に最初からバグの少ないプログラムが作れるようになる。
学習が進むにつれ作成するプログラムも徐々に複雑になっていくので、プログラミングに掛ける時間を節約する意味で、
を覚えておこう。
if, while, open
などは必ず対応する end
を書く必要がある。にもかかわらず end
を忘れてエラーになることが最初のうちは多い。end
を後で入れるから忘れるのである。end
で閉じるべき構文を書いたら、すぐに前もって end
を書くようにすると忘れない。
if y%7 == 3 then
↓
if y%7 == 3 then
end
↓
if y%7 == 3 then
…処理… # endを書いてから中味を書き始める
end
括弧や引用符 ( ) [ ] "" ''
も同様で閉じ忘れると大量のエラーメッセージに見舞われる。開けたらすぐ閉じて戻って中を書くようにすると絶対に閉じ忘れがない。
printf("") # まず閉じ引用符、閉じ括弧を打ってから
↓
printf("答は...") # 戻って中味を入力
変数名の綴り間違いでエラーになるのは悲しいので、積極的に補完を利用する。Emacsであれば単語を2、3文字打った状態で M-/ を押す(つまり、「Esc」キーと「/」キーを同時に押す)と既にソースコードに入っている名前であるならその残り部分を補ってくれる。
printf
デバッグ
プログラミングの初歩の段階では、変数の値が未定義だったり、変数の値を変えずにループを作って暴走させてしまったりするなど、変数の値が予期せぬ状態になっていることが原因のバグが多い。これを確認するために、現在の変数の値を要所要所で出力する printf
を差し込むとよい。
line=gets
while match > 0
printf("debug: 今のマッチは%d本\n", match)
…処理…
end
このように、printf
を挟んで、そのときの変数状態を表示してデバッグする方法を、printf
デバッグといい、初歩的ではあるが複雑なプログラミングのときにも有効な方法である。
p
メソッドの利用
Ruby特有の便利なメソッドとして、p
メソッドがある。p
は1つ以上の引数を受け取り、文字列は文字列と分かるように、配列は全ての要素を列挙して配列と分かるように、その他の構造の値も目で見て分かりやすい形で出力してくれる。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
number = 5
text = "えっほえっほ"
list = [100, "foo", "ばあ"]
p(number, text, list)
上記のプログラムを実行すると以下のような結果になる。p
の引数として与えられたオブジェクトはそれぞれ別の行で出力される。
5
"えっほえっほ"
[100, "foo", "ばあ"]
特に配列の値がどうなっているか確かめるときにはp
メソッドが有用である。
上記のような方法でデバッグを行いたい場合はそのためにソースコードに printf
や p
を挿入する必要があってあまり効率的ではない。ソースコードを修正せずにプログラムの動きを止めながら、1行ずつ実行して変数の値を確認することもできる。そのための道具をソースレベルデバッガと言う。コマンドラインでプログラムを起動する時に、通常の ruby [プログラムファイル]
の代わりに、rdbg [プログラムファイル]
と入力するとプログラムがデバッグモードで起動する。たとえば,第4回の授業の時に作った match_game.rb
というプログラムをデバッグしたいのであれば
rdbg match_game.rb
と入力する。すると,デバッグモードに入って以下のような画面になる。
[1, 10] in match_game.rb
1| #!/usr/koeki/bin/ruby
2| # -*- coding: utf-8 -*-
3|
=> 4| left = 17 # 残っているマッチ棒の数
5|
6| print "1から3本の範囲でマッチ棒を取ります。最後の1本を取った方の負け!\n"
7|
8| while left > 0
9| printf("あなたの番: マッチ棒が%d本あります\n", left)
10| print "何本取りますか?(1~3): "
=>#0 at match_game.rb:4
(rdbg)
プログラムのソースコード(の一部)が表示され、次に実行される行のところに矢印(=>
)がついている。
このモードでは (rdbg)
というプロンプトが表示される度に、デバッガの操作を行うためのコマンドを入力できる。代表的なコマンドには以下のものがある。
s
(step)
ステップ実行。1行ずつ実行する。
n
(next)
次の行まで実行。メソッド呼出しはトレースされない。
c
(continue)
継続実行。ブレークポイントで止まる。
b
(break)
「b
行番号」などとして、
ブレークポイントを設定する。引数を省略すると設定されているブレークポイント一覧を出す。
del
(delete)
「del
ブレークポイント番号」などとして、
指定したブレークポイントを除去する。
p
(print)
「p 変数
」などとして,p
の後ろに書いた変数の値を表示する。
l
(list)
ソースプログラムを表示する。
display
「display 変数
」などとして,変数の値をステップ実行ごとに常時表示する。「undisplay 番号
」で解除できる。
i
(info)
すべての変数の値を表示する。
その他のコマンドについては、こちらを参照すること(英文である)。
短いプログラムの場合は,s
を打ち続けて1行ずつ実行を進めて,適宜変数の値を p
で表示して確認する。
Return を空打ちすると直前と同じコマンドが繰り返し実行される。
長いプログラム,あるいはループで何度も繰り返すプログラムの場合は動きを調べたい場所の直前にブレークポイントを設定してから c
で継続実行する。ブレークポイントを設定するにはソースプログラムの該当行番号を調べ、
(rdbg) b [行番号]
と入力する。
以下は、ユーザーが入力した3つの整数で計算を行って結果を出力する簡単なプログラム debug_exercise2.rb
であるが、ソースコードにバグがあって間違った結果になる場合がある。例えば、5
、2
、1
を入力すると 5 ÷ 2 + 1
という計算だから 3.5
という結果になるはずだが実際には 3.0
が表示される。
debug_exercise2.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
puts "整数を入力してください"
num1 = gets.to_i
puts "もう一つの整数を入力してください"
num2 = gets.to_i
quotient = num1 / num2
puts "もう一つの整数を入力してください"
num3 = gets.to_i
result = quotient + num3
printf("%d / %d + %d = %.2f\n", num1, num2, num3, result)
実行結果の例:
% ruby debug_exercise2.rb
整数を入力してください
5
もう一つの整数を入力してください
2
もう一つの整数を入力してください
1
5 / 2 + 1 = 3.0
デバッグを行ってみよう。
rdbg match_game.rb
と入力すると以下の内容が表示される。
rdbg debug_exercise2.rb
[1, 10] in debug_exercise2.rb
1| #!/usr/koeki/bin/ruby
2| # -*- coding: utf-8 -*-
3|
=> 4| puts "整数を入力してください"
5| num1 = gets.to_i
6| puts "もう一つの整数を入力してください"
7| num2 = gets.to_i
8| quotient = num1 / num2
9| puts "もう一つの整数を入力してください"
10| num3 = gets.to_i
=>#0 at debug_exercise2.rb:4
(rdbg)
s
を入れてステップ実行すると最初の行(4行目)が実行されて「整数を入力してください」というメッセージが表示される。
(rdbg) s # step command
整数を入力してください
[1, 10] in debug_exercise2.rb
1| #!/usr/koeki/bin/ruby
2| # -*- coding: utf-8 -*-
3|
4| puts "整数を入力してください"
=> 5| num1 = gets.to_i
6| puts "もう一つの整数を入力してください"
7| num2 = gets.to_i
8| quotient = num1 / num2
9| puts "もう一つの整数を入力してください"
10| num3 = gets.to_i
=>#0 at debug_exercise2.rb:5
(rdbg)
何も入力せずEnterキーを押すと直前に使われたコマンド(つまり s
)が繰り返されて、次の行が実行される。そして1番目の整数(ここでは 5
)を入力する。
(rdbg)
5
[1, 10] in debug_exercise2.rb
1| #!/usr/koeki/bin/ruby
2| # -*- coding: utf-8 -*-
3|
4| puts "整数を入力してください"
5| num1 = gets.to_i
=> 6| puts "もう一つの整数を入力してください"
7| num2 = gets.to_i
8| quotient = num1 / num2
9| puts "もう一つの整数を入力してください"
10| num3 = gets.to_i
=>#0 at debug_exercise2.rb:6
(rdbg)
p
で num1
の値を表示すると期待通りに 5
が入っていることを確認できる。
(rdbg) p num1 # command
=> 5
(rdbg)
ステップ実行を繰り返してもう一つの整数を入力しそれも正しく保存されたことを確認する。
(rdbg) s # step command
もう一つの整数を入力してください
[2, 11] in debug_exercise2.rb
2| # -*- coding: utf-8 -*-
3|
4| puts "整数を入力してください"
5| num1 = gets.to_i
6| puts "もう一つの整数を入力してください"
=> 7| num2 = gets.to_i
8| quotient = num1 / num2
9| puts "もう一つの整数を入力してください"
10| num3 = gets.to_i
11| result = quotient + num3
=>#0 at debug_exercise2.rb:7
(rdbg)
2
[3, 12] in debug_exercise2.rb
3|
4| puts "整数を入力してください"
5| num1 = gets.to_i
6| puts "もう一つの整数を入力してください"
7| num2 = gets.to_i
=> 8| quotient = num1 / num2
9| puts "もう一つの整数を入力してください"
10| num3 = gets.to_i
11| result = quotient + num3
12| printf("%d / %d + %d = %.2f\n", num1, num2, num3, result)
=>#0 at debug_exercise2.rb:8
(rdbg) p num2 # command
=> 2
(rdbg)
もう一度 s
を実行するとこれまで入力した2つの数字の割り算が行われる。
(rdbg) s # step command
[4, 12] in debug_exercise2.rb
4| puts "整数を入力してください"
5| num1 = gets.to_i
6| puts "もう一つの整数を入力してください"
7| num2 = gets.to_i
8| quotient = num1 / num2
=> 9| puts "もう一つの整数を入力してください"
10| num3 = gets.to_i
11| result = quotient + num3
12| printf("%d / %d + %d = %.2f\n", num1, num2, num3, result)
=>#0 at debug_exercise2.rb:9
(rdbg)
quotient
に入っている割り算の結果を確認しよう。
(rdbg) p quotient # command
=> 2
(rdbg)
本当は 2.5
のはずだが 2
になっている!これで、バグの原因は8行目で行われる計算にあるということが分かった。考えてみたら num1
と num1
はどちらも Integer
(整数)だから割り算を行うと小数点以下の部分が切り捨てられてしまうのは当然である。以下のようにプログラムを修正すれば、num1
と num2
に保管される値は Integer
ではなく Float
(小数点数)として読み込みされて正しい結果になる。これでデバッグが完了した。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
puts "整数を入力してください"
num1 = gets.to_f
puts "もう一つの整数を入力してください"
num2 = gets.to_f
quotient = num1 / num2
puts "もう一つの整数を入力してください"
num3 = gets.to_i
result = quotient + num3
printf("%d / %d + %d = %.2f\n", num1, num2, num3, result)
これまでの課題は、指示されたプログラムを作成するというものであったが、今回は自由にプログラムを作成する。これまでに学んできたことを活かして、何か面白いプログラムまたは役に立つプログラム(たとえば、簡単なゲームやクイズ、難しい数学関数を計算するプログラム、占いプログラムなど)を作成しよう。
提出要領:
本文は下記の通り記入してください.
氏名: 苗字名前
学籍番号: C11xxxxx
プログラムの概要:
...
使い方の説明:
...
ソースコード:
...
実行結果:
...
工夫した点:
...
応用したプログラミング技術:
...
参考文献:
...