if-elsif-else-end
チャート式Rubyの3回目,条件分岐とArray.eachです.
お題はうるう年
うるう年の課題ってやったことありますよね.
入力がうるう年(leap year)かどうかを表示するプログラムをかけ.
1. うるう年は4で割り切れる数の年.ただし,
2. 100で割り切れる年はうるう年でなく,
3. 400で割り切れる年はうるう年.
4. 2004, 1999, 1900, 2000で試せ.
5. それぞれtrue, false, false, true
この条件分岐をあらかじめ考えて実装するのってきつくなかったですか?そいつをテスト駆動開発の順を追って見てもらいましょう.
簡単な解説:if-elsif-else-end
https://docs.ruby-lang.org/ja/index.htmlを読んで見てください.これは,オブジェクト指向スクリプト言語 Ruby リファレンスマニュアルです.文法は,rubyの使っているversionに合わせて中身を見てください.そいつを開けて,「Rubyの文法:制御構造」の「条件分岐if」に「文法」として次のような記述があります.
if 式 [then]
式 ...
[elsif 式 [then]
式 ... ]
...
[else
式 ... ]
end
その解説を「読めばわかる」と思うかもしれませんが,例を見るほうがいいかも.ただし,上の表現で[]は省略可能箇所を示しています.従って,最小で
if year%4==0 then
p 'not leap year'
end
とかになります.さらにthenは省略可能で,ifをつなげるのはelsifです.
詳しい解法:2004年はleap year
まずお題の4,5は振る舞いを記述する仕様(specification, spec)ではないですよね.例えば,
ruby check_leap_year.rb 2004
とした時に
true
と返ってきてほしいというテストの仕様です.
2004年の振る舞いだけを実装すると,
#+begin_src ruby
p year = ARGV[0].to_i
if year % 4 == 0
p true
end
#+end_src
です.これをtestすると
> ruby check_leap_2004.rb 2004
2004
true
となります.
類題:1999はfalse
次に1999をtestすると
> ruby check_leap_0.rb 1999
1999
です.これをfalseと返すようにすると,
p year = ARGV[0].to_i
if year%4 == 0
p true
else
p false
end
ちゃんと返ってます?
array, each
お題:テストの自動化
この後,さらにテストの内容が増えてくると,いちいち引数(ARGV[0])で全てチェックするのが面倒になってきそうでしょ.lazy programmerは二つ以上は耐えられません.そこで,テストを自動化をしなさい.
なに,配列にしてloopで回すだけです.
[2004, 1999].each do |year|
p year
if year%4 == 0
p true
else
p false
end
end
簡単な解説: array, each
先ほどのhttps://docs.ruby-lang.org/ja/index.htmlで何かを探すときはるりまサーチ(全文検索)へ行ってください.そこで,Array#eachと検索を掛けてください.
各要素に対してブロックを評価します。
ブロックが与えられなかった場合は、自身と each から生成した
Enumerator オブジェクトを返します。
[1, 2, 3].each do |i|
puts i
end
#=> 1
2
3
この記法では,出力が#=>以下に記されています.
配列Arrayは[]で定義されます.その中身を順番に処理するときには,上記のような記法が用いられます.rubyでdo-loop内で使う値は'|i|'のように縦棒で挟まれた変数名に入っていきます.これはmethodで引数名を参照するのに似せた構文です.
詳しい解法1:1900はtrue? いえいえfalse
では次の
100で割り切れる年はうるう年でなく,
ですが,1900はtrueが返って来るのをfalseが返るように変更します.
まずはテストの修正
[1900, 2004, 1999].each do |year|
次に100で割り切れる場合の処理です.
if year%100 == 0
p false
end
なんですが,これをどこに入れるかで悩んでしまいます.とりあえず別処理として前に付け加えておくと,
[1900,2004,1999].each do |year|
p year
if year % 100 ==0
p false
next
end
if year % 4 == 0
p true
else
p false
end
end
そしてtestです.
> ruby check_leap_1900_2004_1999.rb
1900
false
2004
true
1999
false
うまいこと動いていますよね.
詳しい解法2:refactoring 条件式の単純化
先ほどの条件式は二つに完全に分けています.でも,論理的にネスト(入れ子)にする方がいい場合もあります.
[1900,2004,1999].each do |year|
p year
if year % 100 ==0
p false
else
if year % 4 == 0
p true
else
p false
end
end
end
ですが,ここまでするとif 100 else .. endのelseを省略できることがわかるでしょ.そうすると,
[1900,2004,1999].each do |year|
p year
if year % 100 ==0
p false
elsif year % 4 == 0
p true
else
p false
end
end
となります.
類題:そして2000はtrue!
最後の
3. 400で割り切れる年はうるう年.
です.2000年はtrue?
類題:refactoring_methodに
次はmethodにしたくなりますよね.関数の名前はleap?としましょう.
def leap?(year)
if year % 400 ==0
p true
elsif year % 100 ==0
p false
elsif year % 4 == 0
p true
else
p false
end
end
[2000, 1900, 2004, 1999].each do |year|
p year
leap?(year)
end
です.true falseを返して,main loopでpする方がいいです.
caseで
お題:case
ここまででも十分に綺麗なのですが,もっと綺麗にかけます.caseを使って短くせよ.
簡単な解説
先ほどのreference manualのifの下の方にcaseの解説があります.文法は
case [式]
[when 式 [, 式] ...[, `*' 式] [then]
式..]..
[when `*' 式 [then]
式..]..
[else
式..]
end
です.そこにある例も参照してください.ちょっと例と違うのは,普通は0番の式を共通にして一致判定をするのですが,それを省略して一致判定をしてもいいというところです.
類題
ちゃんと動くのは,
def leap?(year)
case
when year % 400 ==0 ; return true
when year % 100 ==0 ; return false
when year % 4 ==0 ; return true
else ; return false
end
end
です.
もっとすごいtdd
上の知識があると全く逆からのtddも可能です.
def leap?(year)
case
when year == 2000 ; p true
when year == 1900 ; p false
when year == 2004 ; p true
else ; p false
end
end
ですよね.この後,より一般化して,
2000 -> % 400 == 0
とかにしても問題ありません.
さらに,
def leap?(year)
return case
when year % 400 ==0 ; true
when year % 100 ==0 ; false
when year % 4 ==0 ; true
else ; false
end
end
とするといいかも.これは確認済み.
TDD
codingの最終目標
こういうcodingの仕方はTDD(Test Driven Development:テスト駆動開発)と呼ばれています.
- テストを作る
- エラーを出す(red)
- エラーをなくす(green)
- codeを綺麗にする(refactoring)
です.codingの最終目標は
clean code that works
動く綺麗なコードを作ることです.順番は,まず動かして,それからこそこそと綺麗にするです.えっと,もし,あなたが,
- codeを書くまえに設計すべき
- コピペは悪
なんていう,マントラを信じているならこのprogramming styleを提唱し始めたKent Beckの文章を載せておきます.よーーく心に刻んでください.
もしあなたがプログラミング嫌いならば,少し心が晴れると思いますよ.俺のせいじゃないって.
clean code that works
TDD by Example (Kent Beck) p.13
目指すのは,動作するきれいなコードだ(このRon Jeffriesの簡潔な表現は素晴らしい).動作するきれいなコードは,偉大なプログラマでもすぐには書けないことがあるし,普通のプログラマならなおさらだ(私もそうだ).
=ここは分割統治しよう.=
各個撃破だぜ,ベイビー.最初に「動作する」に取り組み,その後で「きれいな」に取り組む.この方法はアーキテクチャ駆動とは正反対だ.アーキテクチャ駆動は「きれいなコード」に最初に取り組み,そのうえで苦心してあちこち設計の辻褄を合わせながらどうにか「動作する」を実現させる.
TDD by Example (Kent Beck) p.11
The goal is clean code that works(thenks to Ron Jeffries for this pithy summary).Clean code that works is out of the reach of even the best programmerssome of the time,and out of the reach of most programmers(like me)most of the time.Divide and conquer, baby.First we'll solve the "that works" part of the problem.Then we'll solve the "clean code" part.This is the opposite of architecture-driven development,where you solve "clean code" first,then scramble around trying to integrate into the design the things you learn as you solve the "that works" problem.
phases and speed
TDD by Example (Kent Beck) p.30
グリーンバーを出す小さなステップはどんなものだろうか.Dollarクラスを丸ごとコピーして,DollarをFrancに書き換えるのはどうだろう.
ちょっと待った.これで唾棄すべき糞コードの出来上がりだとあざ笑う声が聞こえる.その声は続けて言う.コピー&ペーストによる再利用は抽象化の敗北であり,きれいな設計を殺すと.
イラっとしただろうか.なら深呼吸だ.鼻から息を吸い込んで,3秒止めて口から吐き出す.オーケー?さて,サイクルにはフェーズがあることを思い出してほしい(中には数秒で通り過ぎるものもあるが,フェーズはフェーズだ).
- テストを書く.
- コンパイラを通す.
- テストを走らせ,失敗を確認する.
- テストを通す.
- 重複を排除する.
各フェーズにはフェーズなりの目的と解決法があり,価値観も異なっている.最初の3つのフェーズはなるべく速く通過して,新しい機能がどの状態にあるのかわかるところまで行きたい.そこにたどり着くためには,どのような罪をおかしてもよい.その短い時間だけは,速度が設計よりも重要だからだ.
1つ心配なのは,私がここに書いていることが,良い設計の原則を無視してよいという免罪符になってしまうことだ.チームに対して「Kent Beckが設計なんてどうでもいいって書いてたよ」などと言い始めるのは,ちょっと待ってほしい.サイクルはまだ終わっていない.4つ足のアーロンチェアは倒れる.最初の4つのステップは,5つ目がなければ無意味だ.正しい設計を,正しいタイミングで行う.動かしてから,正しくする.
ここまで言っておけば安心だろう.これで読者の皆さんは重複を排除するまで誰にもコードを見せないはずだ(ただし,ペアプロ相手は除く)
TDD by Example (Kent Beck) p.23
What short step will get us to a green bar?Copying the Dollar code and replacing Dollar with Franc.
Stop. Hold on.I can hear the aesthetically inclined among you sneering and spitting.Copy-and-paste reuse?The death of abstraction?The killer of clean design?
If you're upset, take a cleanthing breath.In through the nose … hold it 1, 2, 3… out through the mouth.There. Remember, our cycle has different phases(they go by quickly, often in seconds, but they are phases.):
- Write a test.
- Make it compile.
- Run it to see that it fails.
- Make it run.
- Remove duplication.(Make it right.)
The different phases have different purposes.They call for different styles of solution, different aesthetic viewpoints.The first three phases need to go by quickly,so we get to a known state with the new functionality.We can commit any number of sins to get there,because speed trumps design,just for that brief moment.
Now I'm worried.I've given you a license to abandon all the principles of good design.Off you go to your teams –"Kent says all that design stuff doesn't matter."Halt. The cycle is not complete.A four-legged Aeron chair falls over.The first four steps of the cycle won't work without the fifth.Good design at good times.Make it run, make it right.
There, I feel better.Now I'm sure you won't show anyone except your partner your codeuntil you've removed the duplication.Where were we?Ah, yes.Violating all the tenets of good design in the interest of speed(penance for our sin will occupy the next several chapters).
- source ~/git_hub/ruby_docs/chart_style_ruby/c3_if_case_leap_year.org