皆さんこんにちは!底辺プログラマーのヤスです!
今回はプログラミングをするならとりあえず読んでおけ!
【リーダブルコード】の大事な部分を記事にしていきます。
内容的には上級プログラマーならやっていて当然のルールや考え方が記載されています。
中級プログラマーでも実践できていない人は是非、自分を見つめ直す意味も込めてこの本を読んでみると良いのではないでしょうか。
少し厄介なのが、C++、Python、Javascript、Javaにてサンプルが記載されていることです。難易度の高いコードではありませんが、上記の言語に馴染みがない方は少し理解に苦しむかもしれません。(特にC++)
本の内容の要約と、それに対する私の考えを記事として紹介していきます。
ついにリーダブルコード買った🤲
評判通りかどうか確かめたくて買ってしまった✨基本に立ち返るのも時には必要ですよね‼️#プログラミング初心者 #駆け出しエンジニアと繋がりたい pic.twitter.com/rz8IeJQsV1
— ヤス@フリーランスエンジニア (@biz_yasu) May 24, 2020
Contents
理解しやすいコード
コードは他の人が最短時間で理解できるように書かなければいけない。
他の人が理解できるコードを書くという事に疑問を持つ方もいるのではないでしょうか。
例えば自分ひとりで開発を行っている方。
他人に読みやすいコードを書く必要はないように思えます。
でも、他人に理解しやすいコードを書く必要性は絶対にあります。
なぜなら、他の人の中に「6ヶ月後の自分」が含まれているかもしれないから。
更に理解しやすいコードは再利用もしやすいです。
他のプロジェクトを開始する際に既存のコードを使用するなんてことはよくあること。
そんな時に毎回コード分析を行うのは非常に無駄な時間です。
なので、
常に他人が理解しやすいコードを書くという意識を持って
コードを書くようにしましょう。
たまに『短いコードを書くことが正義』と勘違いして何でもかんでも一行のコードにまとめる人を見かけます。Gitとかでコード公開している人にはよくある傾向です。
確かにコードは短いほうがカッコイイんですよね。
でもそのようなコードは非常に理解が為難いです。
ついでにデバッグで途中の式の戻り値を確認したいとなった場合に
式を分解する手間も発生してしまいます。
短くて理解が易しいコードが書けるとどの現場に行っても
評価されるエンジニアになれると思います。
名前に情報を詰め込む
名前をつける時には変数でも、関数でも、クラスでも以下の項目に注意しよう。
明確な単語を選ぶ
例えばGetではなくFetchやDownloadなどを使用する。
tmpやretvalなどの汎用的な名前を避ける
tmpというのはtemporary(一時的)の略称で、頻繁に使われている変数名の1つです。
tmpはスコープが小さく、本当に一時的にしか使わない変数名として使用します。
それ以外の場合に使用されると混乱を招いてしまうので止めましょう。
retvalもよく見かける戻り値に使用される変数名です。
しかしこれは『戻り値です!』以外の情報は何も含まれていません。
戻り値であることはコードを見れば明らかですよね。。。
具体的な名前を使って物事を詳細に説明する
ServerCanStart()よりもCanListenOnPort()の方が明確でわかりやすい。
関数名を見ただけで関数の中身が想像できるのが理想的です。
変数名に大切な情報を追加する。
例えばミリ秒を表す変数名には_msを付けたり
エスケープが必要な変数名には前にraw_を付けたりなど。
C言語やVBAを使用する時に接頭辞に型名をつけたりもしますよね。
- intAAA:数値
- strBBB:文字列
- clsCCC:クラス
他にもこんなのもありますね。
- html_utf8
- html_sjis
日本語以外にも対応しているプロジェクトだったらこういうのもあります。
- .en:英語
- .ja:日本語
- .zh:中国語
スコープの大きな変数には長い名前をつける
スコープが複数画面に及ぶような変数には短い名前をつけるべきではありません。
短い名前はスコープが小さい範囲での使用しましょう。
グローバル変数で『file』という変数があった場合、
何のファイルかわからないし、どの画面でいつ使われるのかもわからなくなります。
setting_fileとかlang_fileとかなら用途がわかるし、
アプリケーション起動時に使われるんだなと予想することもできます。
数行でファイルを開いて閉じるようなプログラムなら『file』でも問題ありません。
大文字やアンダースコア等に意味を含める。
クラスのメンバ変数にアンダースコアを付けてローカル変数と区別する使い方もできる。
全て大文字のスネークケースであれば定数の意味になります。
- _username:クラスのメンバ変数
- username:ローカル変数
- USER_NAME:定数
誤解されない名前
filter,length,limitのようにプログラミングに使うには意味が曖昧なものが多いです。
曖昧な名前をつけると作成者本人にはわかるが、
他のユーザーが期待しているものと違ってしまうことがあります。
名前を決める時は複数の候補を挙げて、より明確な名前を選択するようにしましょう。
get(),set()などはメンバ変数を返すだけの軽量アクセサであるという規約が慣れ親しまれているが、それを全く別の用途で使用するとユーザーに混乱を与えてしまう可能性があります。
上下の限界値を決める時にはmax_,min_を使用すると良いです。
包含的範囲であればfirst,last、包含/排他的範囲であればbegin,endを使用しましょう。
ブール値に名前をつける場合はis,hasを使用すると理解しやすいですね。
コードを整形する
単純にパッと見て読みやすいコードは理解も早まるが、
読みにくいコードはそれだけで読み手に負担をかけてしまう。
VScodeを使用しているならShift+Alt+Fである程度フォーマットしてくれる。
コードを書いた後に必ずフォーマットを行うように心がけておけば
プロジェクト内でコードの一貫性をある程度保証することができる。
改行やインデントを揃えてキレイにするのは非常に手間がかかるので
ある程度はIDEの機能を利用したほうが良いです。
コメントでするべきことを知る
コメントの書き方でその人の実力がある程度図れると言っても過言ではないと思います。
始めてプロジェクトに参加した際に『コメントとコードは1:1で』と言われたのを愚直に守っていましたが、リーダブルコードを読んで考え方が変わりました。
読めばすぐわかるような事をコメントに記載しない。
これは完全に無駄なコメントです。今すぐ止めましょう。
例えばコンストラクタに対して『// コンストラクタ』のようなコメントです。
マジ無意味です。。。
自分の考えを記録する。
『このデータだとハッシュテーブルよりもバイナリツリーのほうが40%速かった。』
『このクラスは汚くなっている。サブクラスを作成したほうが良いかもしれない』
のような情報がコメントに記載されていると余計な手間を掛けることもなく、
改善の方向性もすぐに決めることができる。
特に1つ目のコメントはパフォーマンス改善を依頼された別のプログラマーには非常に有益な情報となるはず。
コードの欠陥にコメントを付ける
例:『TODO:もっと高速なアルゴリズムを採用する』
プロジェクトに入るとたまに『TODO:』というコメントを見かけることがある。
これは『今はやらないが、後でやること』を書いている。
このコメントがあれば万が一修正無しでレビューを行っても即座に問題に気がつくはず。
レビュアーは間違いなく『TODOになってるけど大丈夫?』と聞いてくるはずだ。
このようなコメントが入っていれば自分がプロジェクトから外れた場合でも
引き継いだプログラマがすぐにコメントに気がついて修正を行ってくれる。
自分がハマった罠を告知する
『メモリ開放しないと10分以上待たされた後にエラーとなる』
上記のようなコメントがあればメモリ開放のし忘れは絶対にしないはずです。
このように使用者に注意を促すようなコメントはとても助かりますし、
余裕のある時に改善を行うための目安にもなります。
ついでに原因調査した結果をコメントに書いてあると
スムーズに修正に入ることもできますよね。
コメントは正確で完結に
以下の点に注意しましょう。
代名詞を避ける
コメントに代名詞を使うと読み手に混乱を招く場合がある。
コードを読み進めれば代名詞が何を指しているか理解できるかもしれませんが、
それではコメントを書く意味がありません。
入出力の実例を追加する
コメントと一緒に実例が記載されていると親切です。
細かい説明をされるよりも実例がある方が遥かに短時間で理解できます。
制御フローを読みやすくする
ガード節で関数から早く戻り値を返却しましょう。
下記はPythonで記述しています。
1 2 3 4 5 |
def exists(arr, value): if arr is None or value is None: return False if arr.count == 0: return False return value in arr |
第一引数はリスト、第二引数は文字列です。
文字列がリストの中にあるか確認して結果を返します。
リストの中身を調べる前に、引数を確認し、ふさわしくない引数が来ている場合は処理を行わずに速やかに結果を返しています。
このような書き方を行うとインデントが深くなるのを避ける事ができますし、
何より処理がスッキリとして読みやすくなります。
巨大な式を分解する
一度に大量のコードを詰め込んでしまうと読み手に優しくない
コードができあがってしまいます。
以下の方法で対処していきます。
要約変数を使う
大きなコードの塊を変数に代入してしまう方法です。
1 2 3 4 5 |
if request.user.id == document.owner_id: # ユーザーはこの文章を編集できる if request.user.id != document.owner_id: # 文章は読み取り専用 |
上記のコードは難しくないが、もう少し簡単に表現することができます。
1 2 3 4 5 6 7 |
# 一度要約変数に格納する user_own_document = (request.user.id == document.owner_id) if user_own_document: # ユーザーはこの文章を編集できる if not user_own_document: # 文章は読み取り専用 |
先に見せたコードより行数が増えているが、
後に見せたコードの方がわかりやすいと思います。
より簡潔な書き方を模索する
抽象的な話になってしまいますが、コードを書いてる最中に
「なんか複雑なコードになってしまった。。。」
という事があると思います。
そう思った時は初めから考え直してみてください。
高確率でもっと単純な方法が見つかるはずです。
視点を変えてみるのも大事です。
アドバイスを貰える方がそばにいる場合は相談してみてください。
変数と読みやすさ
不要な変数はコードを読む時に邪魔になってしまいます。
なので意味のない変数はどんどん削除していきましょう。
役に立たない一時変数を削除する
以下のコードを見てください。
1 2 3 4 5 6 |
import datetime if __name__ == "__main__": now = datetime.datetime.now() apply_date = now print(apply_date) |
このコードは変数nowに現在時刻をapply_dateにさらに格納し、
その後変数の内容を出力しています。
このコードにおいてnowとapply_dateは必要あるでしょうか?
1 2 3 4 |
import datetime if __name__ == "__main__": print(datetime.datetime.now()) |
こうすれば一時的な変数を使用しなくても問題ないコードになります。
無関係な下位問題を抽出する
大切な考え方は以下の3点です。
- 「このコードの高レベルの目標は何か?」と考えること。
- 「高レベルの目標に直接的に効果があるのか?
あるいは、無関係の下位問題を解決しているのか?」と考えること。 - 無関係の下位問題を解決しているコードが相当量あれば
それらを抽出して別の関数にすること。
これらを踏まえた上で下記のコードを見てみます。
このコードの高レベル目標は『与えられた地点から最も近い位置を見つける』です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 与えられた緯度経度に最も近い'array'の要素を返す。 // 地球が完全な球体であることを前提としている。 var findClosestLocation = function (lat, lng, array) { var closest; var closest_dist = Number.MAX_VALUE; for (var i = 0; i < array.length; i += 1) { // 2つの地点をラジアンに変換する。 var lat_rad = radians(lat); var lng_rad = radians(lng); var lat2_rad = radians(array[i].latitude); var lng2_rad = radians(array[i].longitude); // 「球面三角法の第二余弦定理」の公式を使う。 var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) + Math.cos(lat_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng_rad)); if (dist < closest_dist) { closest = array[i]; closest_dist = dist; } } return closest; }; |
このコードの中で高レベル目標と関係ないコードはあるでしょうか。
『球面三角法の第二余弦定理』を使って計算している部分ですね。
これは計算の方式がわからなくても問題ない部分です。
spherical_distanceという関数にすると、findClosestLocationは本来の高レベル目標である『与えられた地点から最も近い位置を見つける』に集中することができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
var findClosestLocation = function (lat, lng, array) { var closest; var closest_dist = Number.MAX_VALUE; for (var i = 0; i < array.length; i += 1) { var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longitude); if (dist < closest_dist) { closest = array[i]; closest_dist = dist; } } return closest; }; |
まとめ
今回はリーダブルコードの大事な部分をまとめました。
読むだけで読みやすいコードが書けるようになるわけではないので、
書いてあったことを少しづつ実践できると良いと思います。
今回は以上となります。最後まで読んでいただきありがとうございます。