整理されたコードが読みにくい理由 | 悪態のプログラマ

悪態のプログラマ

とある職業プログラマの悪態を綴る。
入門書が書かないプログラミングのための知識、会社の研修が教えないシステム開発業界の裏話は、新人プログラマや、これからプログラマを目指す人たちへのメッセージでもある。

@IT の「キミのコードが汚い理由 」という記事を読んだ(yasuhoの隠れ家 さん経由)。

この記事には、テニスのスコアをつけるための2つの Java コード紹介がされている。「幼稚なスタイル」で書かれたとされる「リスト1」と、それを「クリーン」に書き直したとされる「リスト2」である。記事の中では、これらは単なる「前フリ」程度の扱いなのだが、「読者指向プログラミング 」ということを考える上で、おもしろい題材なので、少し細かく見てみようと思う。

元記事の趣旨に対しては「重箱の隅」的な指摘になるかと思うが、それは承知の上で、別のテーマとして読んでいただきたい。

まず、元のソースコードは画像で公開されているので、テキストにしたものを以下に示しておこう。

リスト1

public class SetScorer
{
private int[] games = {0, 0};

public void gameWon(int i){
games[i-1]++;
}

public String getSetScore(){
if ( games[0] < 6 && games[1] < 6) {
if(games[0] > games[1]){
return "Player1 leads " + games[0] + " - " + games[1];
} else if (games[1] > games[0]) {
return "Player2 leads " + games[1] + " - " + games[0];
} else {
return "Set is tied at " + games[1];
}
}
if (games[0] == 6 && games[1] < 5) {
return "Player1 wins the set " + games[0] + " - " +
games[1];
}
if (games[1] == 6 && games[0] < 5) {
return "Player2 wins the set " + games[1] + " - " +
games[0];
}
if (games[0] == 6 && games[1] == 5) {
return "Player1 leads 6 - 5";
}
if (games[1] == 6 && games[0] == 5) {
return "Player2 leads 6 - 5";
}
if (games[0] == 6 && games[1] == 6) {
return "Set is tied at 6 games";
}
if (games[0] == 7) {
return "Player1 wins the set 7 - " + games[1];
}
return "Player2 wins the set 7 - " + games[0];
}

}

リスト2

public class SetScorer {
private int[] gamesWon = {0, 0};

public void gameWon(int player){
gamesWon[player-1]++;
}

public String getSetScore(){
int leader = gamesWon[0] > gamesWon[1] ? 1 : 2;
int leadersGames = gamesWon[leader - 1];
int opponentsGames = gamesWon[leader == 1 ? 1 : 0];
String setScoreMessage = null;
if ((gamesWon[0] < 6 && gamesWon[1] < 6)
|| (leadersGames == 6 && opponentsGames == 5)) {
setScoreMessage = "Player" + leader + " leads " +
leadersGames + " - " + opponentsGames;
} else if (gamesWon[0] == gamesWon[1]) {
setScoreMessage = "Set is tied at " +
leadersGames;
} else if (( leadersGames - opponentsGames >= 2)
|| leadersGames == 7) {
setScoreMessage = "Player" + leader +
" wins the set " + leadersGames + " - " +
opponentsGames;
}
return setScoreMessage;
}
}


yasuhoの隠れ家さんも指摘されているように、リスト1の方が分かりやすいと感じる人も多いのではないだろうか。確かに、リスト2は数式的には「整理」はされているのだが、読みやすくはないように思える。

いくつか、その原因を考えてみた。


1. レイアウトへの配慮がない


空行挿入、改行位置、インデントなど、レイアウトについての配慮が甘く、全体的に、目で追いにくい書き方になっている。もっとも、これは Web 掲載上の都合かもしれない。



2. 変数の扱い方の問題


リスト2では、リスト1より変数が多くなっている。ここでは「優勢なプレイヤー」と「劣勢なプレイヤー」の勝ち数を明確にする意図があり、それ自体は悪いことではない。

しかし、せっかく配列の内容を leaderGames、opponentGames という変数に入れ直しているのに、その後も元の配列を参照し続けているので、逆効果になっている部分がある。例えば、「gameWon[0] < 6 && gameWon[1] < 6」は単に「leaderGames < 6」でいいし、「gamesWon[0] == gamesWon[1]」も「leaderGames == opponentGames」でいい。

また、2人のプレイヤーを数値で区別しているのだが、「0 と 1 で区別する場合」と「1 と 2 で区別する場合」が混在していて分かりにくい。そもそも、プレイヤーが2人しかいないのに、配列にする必要があるだろうか? このあたりは、リスト1の書き方に引きずられている印象だ(比較しやすいように意図的にそうしているのかもしれないが)。

更に細かいことを言えば、"set" で始まる変数名があるが、これは一見、関数(setter)のように見えるので、避けたほうがいいだろう。



3. 直感的表現の消失


「幼稚なスタイル」のコードは、幼稚であるが故に読者にとって「直感的」であることも多い。逆に、コードを「整理」すると、その代償として、素直な表現が失われてしまうことがあるのだ。

例えば、リスト1の if 文は処理としては冗長なのだが、条件が馬鹿正直に書かれているので、逆に分かりやすい。

また、return しているメッセージの文字列も、リスト1ではやはり馬鹿正直に書かれているため、「文」として意味を取りやすい。コード内の文字列は、往々にして、そこで何が行われているのかを伝えてくれる。いわば「コメント的な効果」を持っているのだ。リスト2のメッセージは変数で分断されている箇所が多く、この効果が薄れている。

もちろん、私もリスト1のような「幼稚な」書き方が良いなどと言うつもりはない。効率や汎用性、あるいは「他の角度から見た読みやすさ」のために、こうした直感的な表現が犠牲になることは、宿命だろう。しかし、そうした表現の効果について、知っておくということは必要である。「読者指向プログラミング」のテクニックのひとつになりうるからだ(※)



4. 読者層が想定外


これは読者側の問題になるのだが、英語が苦手な読者にとって、変数名が直感的に理解できないという問題もある。個人的には、"leader" や "opponent" といった単語が「優勢なプレイヤー」とその「対戦相手」であると理解するために、変数に代入している式を解読する必要があった。つまり、変数名が「a」や「b」でも同じだったわけだ。

多くのプログラミング言語で、ソースコードはアルファベットと数字と記号で書くことになっている。このため、日本人向けのコードには、より多くのコメントが必要になる(漢字という「表意文字」の効果は絶大である)。しかし、リスト2の作者は日本の読者を想定していなかっただろう。



こうした点を考慮しながら、リスト2を修正してみよう。といっても、全ての問題を同時に解決することはできない。全体のバランスを考えた「落とし所」を見極めることが必要だ。

リスト3

public class SetScorer
{
private int[] gamesWon = {0, 0};

public void gameWon(int player){
gamesWon[player-1]++;
}

public String getSetScore(){

// どっちが優勢か
int leader = gamesWon[0] > gamesWon[1] ? 0 : 1;
int leadersGames = gamesWon[leader];
int opponentsGames = gamesWon[leader == 0 ? 1 : 0];

String scoreMessage = null;
if ((leadersGames < 6) || (leadersGames == 6 && opponentsGames == 5)) {
// 試合途中
scoreMessage = "Player" + (leader + 1) + " leads "
+ leadersGames + " - " + opponentsGames;
} else if (leadersGames == opponentsGames) {
// 同点
scoreMessage = "Set is tied at " + leadersGames;
} else if ((leadersGames - opponentsGames >= 2) || leadersGames == 7) {
// 試合終了
scoreMessage = "Player" + (leader + 1) + " wins the set "
+ leadersGames + " - " + opponentsGames;
}

return scoreMessage;
}
}

読みやすさという点において、いくらか良くなっていると思うが、どうだろうか。もちろん、コードの書き方に「正解」はない。私が自分でゼロから書いたとしたら、また違ったコードになるだろう。「読者指向プログラミング」について考えるには、適度な題材である。プログラマの方は、自分ならどう書くか、実際にやってみてはいかがだろうか。






※例えば、これは何をする関数だろうか?

 double func(double r){ return r * 6.28; }

では、これは?

 double func(double r){ return r * 2.0 * 3.14; }

「3.14」と書けば「円周率」だと分かるため理解しやすい。つまり、これらは半径を r とする円周の長さを求める関数である。前者の方が数式としては「整理」されており、効率もよいかもしれない。しかし、後者の方が読者指向である。



■関連記事
読者指向プログラミング
もっとコメント論 ~その1~ コメントとは何か
インデントについて考える 前編
誰のためのコード?
まずは丁寧なプログラミングを
プログラムは二度書け
慣例的コーディングルール



Code Complete第2版〈上〉―完全なプログラミングを目指して
スティーブ マコネル Steve McConnell クイープ
日経BPソフトプレス
売り上げランキング: 2788
おすすめ度の平均: 5.0
5 これは理解できないとヤバイです。
5 我流プログラミングから抜け出す意思のあるひとへ
5 人に勧めたくなる本


続 直観でわかる数学
続 直観でわかる数学
posted with amazlet on 07.01.21
畑村 洋太郎
岩波書店
売り上げランキング: 86453
おすすめ度の平均: 4.0
2 自分で考える覚悟があるか
4 解説と算数授業の批判
5 目からウロコの算数