読みやすいコードを目指して-俺流リーダブルコード(前半)
-
対象者
:プログラマ -
終着点
:周りに優しいコードを書けるようになる
そもそも読みやすいコードとは
あなたの周りには処理は正しく動いているのだが、メンテするときに「はっ?なにこれ?」となるプログラムはないだろうか?
いわゆる読みづらいコードというやつだ。
もし読みづらいコードが無い職場で働いているなら、ご幸せに...この記事は必要ありません。
少しでも不便を感じたことがあるならざっとでもいいので目を通してみてほしい。
当たり前のことしか書かないけど、どこか一つでも学びにつながれば幸いだ。
この記事は「リーダブルコード」の内容を私なりに掻い摘んで噛み砕いて記載しています。(出典は記事の最後へ)
変数の命名
例えば●
に変数名をつけるとしたら
- 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
i
やj
が何を指しているか分からなくなるし、i
とj
で書き間違いが起きそうです。
めんどくさがらずにi
とj
にはそれぞれuser
やcook
などと命名するのが良いです。
もしこれがループに使うインデックスだとしても単純に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;
見比べてみると一目瞭然で後者の方が分かりやすいだろう。
まず項目で縦の(見えない)線を引いている感じだ。
そして前者はSELECT
やFROM
では複数項目が横並びなのに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句がどこかすぐに見つけることができるだろう。
またSELECT
とWHERE
内で項目の出現順が同じようになっているが、これも変えてしまうと読み手に不快感を与えるので気を付けるといいだろう。
コメントについて
有用なコメントは意識していないとつけられない。
//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/while
やgoto
はなるべく使わないようにしましょう。
単純にこの二つの構文はプログラムの流れに逆らっています。
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)