くそコードが無くなる日を目指して

実例はC++/Javaがメインです|良いコードは健康を増進します

2015年09月

こんばんは。
今日は短めに。こんなのはやめましょうという話。
 
    try {
        exceptionThrower();
    } catch (Exception e) {
        // Catch everything
    }

ExceptionがexceptionThrower(注:これはこの例のために作ったメソッドです)から投げられて、なんかtry〜catchを書かないとコンパイルが通らないという理由で上のように書くのはやめましょう。 めんどくさいからとりあえず、Exceptionでキャッチしとけ!はやめましょう。 最初想定したExceptionだけキャッチするようにしましょう。 全部キャッチしてしまうと、想定外のバグが埋もれたり、変なところに影響がでて迷宮入りしやすくなります。

コード=読みやすくあるべきという観点で見ると、書く際に気をつけるべきは命名と分割という話。

命名を意識するというのは次のような感じです。
例えばこんなコードがあったとします。
String orange = "Apple"; // これは論外
String info = "Some Information";
前者はそもそも書いてあることと中身が違うので、混乱をきたすどころか、バグでも起きた日には迷宮入りしかねません。後者は、結局なんの情報?という部分が欠落しているので、読み手は中身が正しいのかどうか判定できません。(前後の文脈で判断を仰ぐのは、時間の無駄になるので適切ではありません)
一方で、命名がしっかりしていれば、細かなロジックがわからなくても何をしているのか理解できる(ここが重要)はずなので、命名はコードを書く際に意識すべき要素だといえます。

分割を意識するというのは次のような感じです。
次のコードを見てください
void eatBreakfast() {
    while () {
        〜
    }
    while () {
        〜
    }
}
eatBreakfastの中で2つの処理が混在しています。これでもいいのでは?という意見はあると思いますが、これでは、ぱっと見た時に、eatBreakfast(朝ごはんを食べる)が2つのwhileループからなることはわかりますが、それが一体何をしているのかはわかりません。
(じっくり読めばわかるんだと思いますが、それをさせるのは良いコードではありません)

なぜわかりにくいのかというと、朝ごはんを食べる処理にはいくつか分割できる要素がある(eatBreakfastは抽象度が高い)のですが、それがそのまま(抽象度が高いまま)になっているからです。
そこでeatBreakfastが、実際には、「パンを食べる」と「コーヒーを飲む」から構成されていたとするならば、
void eatBreakfast() {
    eatBread();
    drinkCoffee();
}
となります。明らかにわかりやすくなりました。

命名と分割 、ぜひ意識してコードを書いて見てください。

それでは、良いコード日和を 

自分だけなのかみんなもそうなのかわかりませんが、家にいると、コーディングがしたくないわけではないけど、かと言って特に作りたいものもないから、何もせず、あーでも何かしなきゃという妙な焦りだけがあって、ただ時間だけが過ぎてしまうことはないでしょうか?

そんなときに個人的におすすめなのが、コーディングスキルチェックサービスやいわゆる競技プログラミングのサイトにある問題を解くことです。

サイトはいろいろ有りますが、有名どころだと
コーディングスキルチェック:
CodeIQ
Paiza
競技プログラミング:
TopCoder
Codeforces
あたりがありますね。

おすすめは、PaizaとCodeforcesです。
Paizaは初心者でも解けるような問題(文字列の中からAの個数を数えるなど)から1時間以上かかる難しい物まであって、その日の時間的な余裕とバランスをとりながら楽しめる、CodeforcesはTopCodeに比べてサイトがシンプルでわかりやすいところが好きで、おすすめです。

解けたからといって、きれいなコードがかけるようにはあまりなれないかもしれませんが、
スピードと正確さが求められる中で、コードの品質も気にすることができれば、一歩先のレベルに行けるようになると思います。

是非、試してみてください。

それでは、良いコード日和を
 

前回、良いコードかどうかは相手に内容を伝えようとしているかどうかの違いといったことを書きました。

今回は実際のコードで実例を交えながら、説明したいと思います。

前回、カレーの例をお見せしましたが、そのときのA)をソースコード風に表現するとこんな感じになります
String menu() {
    String p = "p";
    String o = "o";
    String m = "m";

    Thread.sleep(5000);
    m += "c";

    Thread.sleep(1000);
    o += "c";

    String c = p + o + m;
    Thread.sleep(10000);
    c += "bl";
    return c;
}
問題は、いろいろ有ります。順番に前回のB)のレシピのようにしていってみましょう。

まず、pだのoだのが何を指し示しているかわかりません。
お見せした例が極端に見えるかもしれないですが、意外とめんどくさがられて省略された名前が放置されることはよくあるので、何が何を示すのか名前付けることは常に意識したほうが良いです。
今回はこんな感じになると思います
String menu() {
    String potato = "potato";
    String onion = "onion";
    String meat = "meat";
(以下略) }
これで、p、o、mがそれぞれ何を表しているかわかりやすくなりました。

でも、まだわかりにくいところは残っています。注目すべきは3回出てくるsleepです。
大抵の場合、このように同じような処理を何回もやる場合は、メソッドにくくりだして、それに適切な名前をつけるとわかりやすいコードになります。ここでは、次のようにします。
(よく、くくりだすことが強調されますが、それ以上に名前が重要です。後述します)
String menu() {
    String potato = "potato";
    String onion = "onion";
    String meat = "meat";

    meat = cook(meat, 5000);
    onion = cook(onion, 1000);
    curry = boil(potato + onion + meat, 10000);

    return curry;
}

String cook(String ingredient, long timeMillis) {
    Thread.sleep(timeMillis);
    return ingredient + "Cooked";
}

String boil(String ingredient, long timeMillis) {
    Thread.sleep(timeMillis);
    return ingredient + "Boiled";
}

どうでしょうか。だいぶ元のコードが改善されて、パッと見ただけでも何をしているかわかるようになっていないでしょうか?
ここでのポイントは、メソッドを分けた上で、さらにcookとboilの2つがあることです。
同じ処理構造をしている=まとめて一つのメソッド/関数にしてしまう。と、いったことをやりがちですが、個人的には目的が同じでなければ、このように、似ていても分けて、それぞれに意味のある名前をつけるべきだと思います。もし統一するのであれば、より抽象的な、「火にかける」のような名前にしますが、肉と玉ねぎとカレーの具を火にかけるは同じ作業なのか考える必要があります。
(炒めることと煮ることは違う作業ですよね)

これでだいたい終わりです(数字がマジックナンバーなのはまた別の日にでも)。最後にメソッドの名前を変えるのを忘れないようにしましょう。
しつこいですが、メソッドに、その中身がやっていることをなるべく正確に示す名前をつけることは重要です。
今回はカレーを作ったのでこんな感じでしょうか。
 
String prepareCurry() {
    String potato = "potato";
    String onion = "onion";
    String meat = "meat";

    meat = cook(meat, 5000);
    onion = cook(onion, 1000);
    curry = boil(potato + onion + meat, 10000);

    return curry;
}

String cook(String ingredient, long timeMillis) {
    Thread.sleep(timeMillis);
    return ingredient + "Cooked";
}

String boil(String ingredient, long timeMillis) {
    Thread.sleep(timeMillis);
    return ingredient + "Boiled";
}
いかがでしたでしょうか?意外と簡単な印象を受けていただけてたら嬉しいです。 もちろん、これ以外にも様々なことを気をつける必要は有りますが、まずはそれぞれのメソッドの目的を明確化して、きちんと名前付けするのを意識するだけでもだいぶ綺麗なコードが書けるようになるはずです。

それでは、良いコード日和を 

ソースコードって難しいですよね。
極論いうとif, for, whileあたりがわかれば誰でも書き始められて、
最近はAndroidやiOSの登場で、初心者でも気軽に簡単にアプリを作れてしまう。
作れてしまうがゆえに、コードの見た目はとりあえず脇に置かれてしまう。
で、気づいたらぐちゃぐちゃ・・・。

あー、もどかしい。

そもそも綺麗なコードってどんな感じ?
自分のはどうなのだろう?
とif, for, whileの使い方を教えてくれたあのサイト、あの本たちは
そういうことは何も教えてくれなくて全然わからないこともよくあると思います。
綺麗に書くのは、スーパープログラマーじゃないとできない。とすら、思ってしまうこともあるかもしれません。

でも、実は綺麗なコードを書くには、知識も特別な技術も本質的には要りません。
相手に伝えようとしているかどうかという、心構えが一番重要です。

良いソースコードは、料理のレシピのようなものです(たぶん)。
たとえば:

A)メニュー
じゃがいもとたまねぎをきりつつ、おゆをわかしながら、にくをいためて、そのごきったじゃがいもとたまねぎをいため、いためたらわかしたおゆをいれてにじゅっぷんくらいにる、じゃがいもがにえたら、かれーるーをいれてあじをととのえてかんせい

B)カレーの作り方
-じゃがいもは皮をむき、一口大に切る
-たまねぎも皮をむき、スライスする
-フライパンに油をひき、肉を炒める
-肉に火が通ったら、切った野菜を入れ、中火で炒める
-玉ねぎがきつね色になるまで炒めたら、鍋にお湯をいれ、炒めた具を煮る
-じゃがいもに串が通るまで煮えたら、カレールーを入れる。
-ひと煮立ちさせたあと味を整えれば完成

なにが言いたいかはなんとなくわかりますよね?
AよりBのほうがわかりやすくないでしょうか?
それはきっと、
・Bはカレーの作り方と書いてあるから、最初からその後にどんなことが書いてあるか想像できる
・順番になっていて、それぞれの手順が分割されている。Aはぱっと見なんだかよくわからない。(ひらがなだし・・・)
・Bは細かい指示(きつねいろまでとか)が書いてあるので、間違えにくそう。Aはどこまで炒めれば良いのかよくわからないので、初めてだと???となりそう
・そもそもAは伝える気があんのかよ!
というところにあると思います。

ソースコードもこれと同じです。今、何を、どのように、どこまで、したいか、それを説明するように書かれているかどうか、それが綺麗なコードと俗にいうくそコードの差です。
意外と単純な気がしてこないでしょうか?

今まさにこれに気づき、意識し始めたあなたは、もう綺麗なコードを書くプログラマーの仲間入りです。

長くなってしまったので、実例は次の記事からお見せしたいとおもいます。

では、良いコード日和を!
 

↑このページのトップヘ