Help us understand the problem. What is going on with this article?

読みやすいコードを目指して

More than 3 years have passed since last update.

読みやすいコードを目指して-俺流リーダブルコード(前半)

  • 対象者:プログラマ
  • 終着点:周りに優しいコードを書けるようになる

そもそも読みやすいコードとは

あなたの周りには処理は正しく動いているのだが、メンテするときに「はっ?なにこれ?」となるプログラムはないだろうか?
いわゆる読みづらいコードというやつだ。
もし読みづらいコードが無い職場で働いているなら、ご幸せに...この記事は必要ありません。
少しでも不便を感じたことがあるならざっとでもいいので目を通してみてほしい。
当たり前のことしか書かないけど、どこか一つでも学びにつながれば幸いだ。

この記事は「リーダブルコード」の内容を私なりに掻い摘んで噛み砕いて記載しています。(出典は記事の最後へ)

変数の命名

例えばに変数名をつけるとしたら

  • circle
  • dot

などとなるだろうか。
大きさは比べるものがないので円と感じるか大きな点と感じるかは議論しない。
circleだとしたらまだ情報を付け加えれるだろう。

  • filledCircle

塗りつぶされた円だということが分かる。だったら是非openCircleとしてほしい。
後から見たときにその変数の役割が分かりやすと理解までの時間が大幅に減る。
コツは明確な名前付けなので、間違っても略したりしないように。

変数名を長くするとコーディングが大変だという人がいるかもしれない。
しかし最近のエディタやIDEでは補完機能が優秀で苦にならないだろう。
何より私たちはコーディングしている時間よりコードを眺めている時間の方が長いのではないだろうか?
恐れずどんどん(常識的な範囲で)長くしていこうじゃないか。

次に一時的な変数についての話

users.each do |i|
  puts i.user_name
end

これならイテレータで利用する変数はiでもいいでしょう。
次のコードではどうでしょう。

users.each do |i|
  cooking = Cooking.find(i.id)
  cooking.each do |j|
    # 長い処理
  end
end

ijが何を指しているか分からなくなるし、ijで書き間違いが起きそうです。
めんどくさがらずにijにはそれぞれusercookなどと命名するのが良いです。
もしこれがループに使うインデックスだとしても単純にindexではなくcities_indexなど意味を持たせることが大事です。

変数につけやすい、最初や最後を表す場合など良く使えそうな表現を以下にまとめておきます。

命名 意味
first,last 最初と最後(ただし範囲内という意味合い)
begin,end 最初と最後(範囲内、範囲外ともに使える)
min,max 下限と上限

ついでに変数の命名記法

記法 命名例 説明
camelCase キャメルケース 単語の先頭を大文字で書き始める
snake_case スネークケース 単語間をアンダーバーでつなぐ
szHungarian ハンガリアン記法 接頭または接尾に識別子(データ型、スコープなど)を付ける

現在ハンガリアン記法は使わない方が良いと言われている。
識別子が何を意味するかを知らなくてはいけないし、型やスコープは変数の宣言を参照すれば分かるから必要ないだろう。

コードの整形

次にコードを整形について

SELECT U.USER_NAME , U.ADDRESS , U.TELEPHONE_NUMBER
FROM USERMST U, SALARY S
WHERE S.SALARY > 500000
AND U.AGE > 30;

これと

SELECT U.USER_NAME
     , U.ADDRESS
     , U.TELEPHONE_NUMBER
  FROM USERMST U
     , SALARY S
 WHERE S.SALARY > 500000
   AND U.AGE > 30;

見比べてみると一目瞭然で後者の方が分かりやすいだろう。
まず項目で縦の(見えない)線を引いている感じだ。
そして前者はSELECTFROMでは複数項目が横並びなのにWHEREでは1行1項目だからなおさら質が悪い。
1行1項目なら全てそのように揃えた方が分かりやすい。

また論理的なブロックで適宜段落分けされていると理解しやすい。

SELECT U.USER_ID
     , U.USER_NAME
     , U.ADDRESS
     , U.TELEPHONE_NUMBER

  FROM USERMST U

 WHERE U.USER_NAME        LIKE 'YAMADA%'
   AND U.ADDRESS          LIKE '%CHIYODA%'
   AND U.TELEPHONE_NUMBER LIKE '090%'

まぁ一時的に使うSQL文でこんな改行が入っていると逆に気持ち悪いのだが...
これが複雑なSQL文であれば修正で条件を変えるときにWHERE句がどこかすぐに見つけることができるだろう。
またSELECTWHERE内で項目の出現順が同じようになっているが、これも変えてしまうと読み手に不快感を与えるので気を付けるといいだろう。

コメントについて

有用なコメントは意識していないとつけられない。

//10足す1を代入
int calculatedNumber = 10 + 1;

極端な例だが、このコメントはコード見れば明らかなので要らない。
他には随所に変数ごとの意味合いをコメントする(変数名が分かりやすければ必要ない)などは不要です。
逆に定数についてはその定数が生まれた(必要な)理由などをコメントしておくと後で使いやすかったりします。

//分かりづらいがこのコードはFizzBuzzを行っています。
public static void fizzbuzz(int n){for (int i=1; i<(n+1); i++){System.out.print(i%15==0 ? "FizzBuzz ":((i%3==0) ? "Fizz ":(i%5==0) ? "Buzz ":i+" "));}}

コメントしてねーで書き直せという話です。
この例とは違いますが修正を重ねてコメントだらけになっているコードは、分かりやすいようにリファクタリングしてコメントを消しちゃいましょう。

//この関数は記号を処理できません
public String documentFormatting(String text, int formatType){
    /* 省略 */
}

記号が処理できないことがどうかの議論は置いておいて、これは有用なコメントです。
この関数を使う人は引数に記号を使うとエラーになるんだなと予測できます。
これに似た有用なコメントとして、コードを見ただけでは予測が付きづらい動作をコメントするのも良いですね。

formattingResult = documentFormatting( /* documentText = */ text, /* formatType = */ 8);

引数が多い場合などは関数呼び出しにインラインコメントを入れておくと引数が分かりやすくなります。

タスクコメント一覧

タスク 意味
TODO やらなければいけないこと
FIXME 修正が必要な個所
XXX 動いてはいるが、修正が必要

もちろん//TODOなどのようにコメント内に記述します。
EclipseなどのIDEはもちろんVimなどのエディタでもシンタックスハイライトしてくれます。

他にも関数の全体像を説明したり段落ごとの意味を説明したりもありかと思います。
この時に詳しく説明しすぎて、コードをそのまま説明するのに近くならないように注意してください。
なるべく高次元(人間の分かりやすい)なコメントを残すことを心がけて下さい。

プログラムの制御文に関わるあれこれ

ifやforなどで心がけることを書いていきます。

USER_MANY_NUMBER = 10

if USER_MANY_NUMBER > userCounter
  moreNeedUser = "Yes!!"
elsif userCounter == USER_MANY_NUMBER
  puts "Wooowo!! ten menbers!!"
else
  moreNeedUser = "Maybe yes."
end

最初のifはユーザーが10名未満の場合を表していると考えて下さい。
おそらく最初のifよりelsifの方が分かりやすいのではないでしょうか。
比較では左側により変わりやすい値、右側に変わりにくい値を置いたほうが理解しやすいです。

常習的に定数を左側に置くことによって定数 = 変数のような誤った比較式を除外できるという人もいるだろう。
等価比較は変数 == 定数が当たり前かと思います。
ただし言語によっては変数 = 定数が同意であることがあります。(著者の知る限りはVBがこれ)
混乱して間違うことは限りなくないにしても0ではありません。
転ばぬ先の杖、風習として使っているならそれでいいと思います。
と言いつつ私は定数は右側に持っていきますけどね。
定数をどちらに置くかはプロジェクトのコーディング規則や風習に従えばいいと思います。

hoge = foo < 0 ? getString(bar, buz, fizz) : getString(bar, foo, fizz)

このくらいの量だと判断に迷うのですが...
三項演算子の中に複雑なことを書くと分かりづらいのでif文にした方が良いです。
とくに三項演算子の中にif文などがネストされていると最悪にわかりづらいです。

puts "hoge is filled" unless hoge.blank?

これは

puts "hoge is filled" if hoge.present?

にできます。
しかも下の方が分かりやすいです。
if文には肯定系,単純なものな条件を先に持ってきた方が明らかに理解しやすいです。
ただし否定形だけを判断したい場合や判断後に処理する主軸が否定形,分かりづらいものの場合はこの限りではありません。

do/whilegotoはなるべく使わないようにしましょう。
単純にこの二つの構文はプログラムの流れに逆らっています。
do/whileの多くはwhileで書き直せます。
gotoは関数終了前の後処理やエラー処理を行う程度ならいいのですが、上に戻り始めると手が付けられません。

最後にネストの話です。

if(computerStatus == 0){
    //statement
    if(memoryStatus == 0){
        //statement
    }
}else if(computerStatus == TYPE_ERROR_CODE){
    //statement
}else if(computerStatus == INTERNAL_ERROR_CODE){
    //statement
}

のような式があったとする。
最初のifが正常処理、それ以降は異常時の処理を行っている場合
処理が増えるとこのまま正常処理部分のネストが深くなっていくことが予想できる。

if(computerStatus == TYPE_ERROR_CODE){
    //statement
    return anything;
}

if(computerStatus == INTERNAL_ERROR_CODE){
    //statement
    return anything;
}

//statement
if(memoryStatus == 0){
    //statement
}

こんな感じに異常処理を先にreturnで切り崩してやれば正常処理部分のネストは浅くなる。
これはガード節というやり方で、forなどのループ内でも異常時にcontinueするなどして使うことができるのでぜひ活用してほしい。

最後に

予想よりかなり長くなったがこれで前半戦終了です。
投稿時点で「リーダブルコード」を読み終えていないため後半の投稿はいつになるのか分かりません。
ストックしてくださっている方には後半投稿時に後半へのリンクを当投稿に貼ってお知らせする予定です。

出典

リーダブルコード - Dustin Boswell、Trevor Foucher 著、角 征典 訳(ISBN978-4-87311-565-8)

okamos
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away