美しいプログラムコードとは。
「まずは、次のサイトにアクセスして、プログラムコードから仕様を理解してみて欲しい。」
さて、何がしたいプログラムなのか分かっただろうか? テニスを知ってる人なら分かるかもしれないが、知らない人には、どちらのソースもなかなかやっかいな代物だったのではないだろうか?
"これらはいずれも同じユニットテストに合格している。" とあるのだが、実は両者の振る舞いが違う部分がある。さらに、バグがあったりもするのだ。しかも、お手本の方に…。(^^; まあ、それはともかく、話題は "クリーンなコード" である。
見やすいかどうかが話題になると、コーディングルールが話題になったりするが、実際のところ、コードになる前の段階の話として、対象となるものを単純な問題に変換 (問題の切り分けや、モデル化) して解決しているかどうかが大きい。どれだけ文章を整えようが、練られていない話は面白くないように、問題を整理していないものは、見かけ上のコードを整理したところで、大きな改善効果は期待できないだろう。
んで、件のプログラムコードを書き直してみた。Java はブランクがあってすらすら書けないので、ruby で表現した。オリジナルに近い表現をしたつもりだが、ruby の特徴を使っている部分もあり、知らない人には読み難いかもしれない。それでも、あえてコメントを記入せず、また、ここでの補足説明もすることなく、件の記事と同様に、プログラムコードだけで表現することを試みる。論より証拠となるといいのだが…。
class Player attr_accessor :games attr_reader :name def initialize(name) @name = name @games = 0 end def won @games += 1 end end CONDITIONS = [{:COM=>7,:ADV=>1}, {:COM=>6,:ADV=>2}] MSG = {:OVER=>"%s wins the set %s - %s", :TIE=>"Set is tied at %s", :PLAYING=>"%s leads %s - %s"} def games_as_string(player1, player2) temp = [player1, player2].sort{|a, b| a.games <=> b.games} other = temp.shift leader = temp.shift status = :PLAYING status = :TIE if leader.games == other.games CONDITIONS.each {|con| if con[:COM] == leader.games && con[:ADV] <= (leader.games - other.games) status = :OVER end } if status == :TIE options = leader.games else options = [leader.name, leader.games, other.games] end return MSG[status] % options end # テスト p1 = Player.new("Player1") p2 = Player.new("Player2") (0..7).each {|i| (0..7).each {|j| p1.games = i p2.games = j puts "[#{i}:#{j}] #{games_as_string(p1, p2)}" } }
もう一つ。こちらは友人のヒントを基にして、別の方法で実現してみたもの。
NAMES = {
true => "Player1",
false => "Player2"
}
MSG = {
:OVER => "%s wins the set %s - %s",
:TIE => "Set is tied at %s",
:PLAYING => "%s leads %s - %s"
}
def get_set_score(games1, games2)
statuses =
8.times {|i| statuses << Array.new(8, :PLAYING)}
(0..6).each {|i|
statuses[6][i] = :OVER if i < 5
statuses[i][6] = :OVER if i < 5
statuses[7][i] = :OVER if i >= 5
statuses[i][7] = :OVER if i >= 5
statuses[i][i] = :TIE
}
status = statuses[games1][games2]
options =
if status == :TIE
options = [games1]
else
options << NAMES[games1>games2]
options << [games1, games2].sort.reverse
end
return MSG[status] % options.flatten
end
# テスト
(0..7).each {|i|
(0..7).each {|j|
puts "[#{i}:#{j}] #{get_set_score(i, j)}"
}
}