【読書メモ】リーダブルコード

リーダブルコードを読んで

印象に残った部分だけかいつまんでまとめました。

汎用的な言葉は避ける

stopとかsizeとか。 短いループでの、後の再利用のない''temp''などは逆に分かりやすい。 Get()は何をしているのかよくわからない。Fetch()とかDownload()とかだとわかりやすい。

ループイテレータ

i,j,kなどでもいいが、users_i,members_iとか、何に対するイテレータか目印をつけると良い 。

単位のあるものは、変数名に記入

double delay→double delay_msや、int dataSize→dataSize_mbなど。 ただし、明示的にしなくても問題ない場合は短く記入。

必要な単語を加え、不要な単語は投げ捨てる。

名前の長さや言葉はさまざまな観点から選択する必要がある。 たとえば、スコープの短い変数(すぐそこで使って、その後は捨てるようなもの)は、nとかtempでよい。 ConvertToString()はToString()でも十分伝わる。逆にプロジェクト固有の単語は省略したり削ると危ない。

配列の中で、始端と終端の範囲を言葉によって分ける

array[0] array[1] array[2] array[3] array[4]
a b c d e
print integer_range(start=2,stop=4); 

これが印字するのは[c,d]?[c,d,e]? 包含的な範囲を示すなら、(終端を範囲に含むのなら)firstとlastを使う。

set.Printleys(first="a",last="d"); 

逆に、排他的(終端を範囲に含めない)選択では、beginとendを使う。

set.Printkeys(begin="a",end="e"); 

「59分59秒」を避ける

たとえば、日付指定でデータ内のイベントをすべて印刷したいとする。

PrintEventsInRange("OCT 16 12:00am","OCT 17 12:00am"); 

PrintEventsInRange("OCT 16 12:00am","OCT 16 11:59:59:9999pm"); 

どちらがわかりやすいかは明確。

「何がtrueで何がfalse」なのかを明確に

たとえば、次のような変数。

bool read_password = true; 

パスワードをこれから読み取る必要があるのか? パスワードをすでに読み取っているのか? がわからない。 ブール値の変数では、is,has,ca,shouldを接頭語として使うと分かりやすい。 need_passwordとか、user_is_authenticatedなどでもよい。 あとは、なるべく肯定形で書く。

bool disable_ssl = false; 
bool use_ssl = true; 

二重否定は混乱の元。

親しみやすさと軽量さは両立させる

GetやSizeなどの、標準ライブラリでサポートされている名前を含めて使う時は注意が必要。 関数の利用者は、その名前の親しみやすさから気軽に使ってしまう。

似ているコードは似せて見せる

シルエットを意識するとよい。 4つの引数を持つTcpConnectionSimulatorクラスを使って、3つのインスタンスを作るとする。

1.接続速度(KBps) 2.平均遅延時間(msec) 3.遅延時間(msec) 4.パケットロス率(percent,%)

public class PerformanceTester 
{ 
public statc final TcpConnectionSimulator wifi = new TcpConnectionSimulator( 
500, /*Kbps*/ 
80, /*msec latency*/ 
200,/*jitter*/ 
1/*packet loss %*/); 

public static final TcpConnectionSimulator t3_fibar =  
new TcpConnectionSimulator( 
45000, 
10, 
0, 
0); 

public static final TcpConnectionSimulator cell = new TcpConnectionSimulator( 
100, 
400, 
250, 
5); 
} 

「コーディングの方針として、80文字折り返し」というプロジェクトの場合、黄色の部分が他と違うため、 どうしても見にくくなってしまう。

たとえば、こうしてみる
public class PerformanceTester 
{ 
public statc final TcpConnectionSimulator wifi =  
new TcpConnectionSimulator( 
500, /*Kbps*/ 
80, /*msec latency*/ 
200,/*jitter*/ 
1/*packet loss %*/); 

public static final TcpConnectionSimulator t3_fibar =  
new TcpConnectionSimulator( 
45000, 
10, 
0, 
0); 

public static final TcpConnectionSimulator cell =  
new TcpConnectionSimulator( 
100, 
400, 
250, 
5); 
} 

でも、縦に長くなって見づらくなる欠点もある。 こういうときは、簡潔に書くことを念頭に置くべき。

public class PerformanceTester 
{ 
//TcpConnectionSimulator(throughput, latency, jitter, packet_loss) 
//                                       [kbps]        [ms]    [ms]   [percent] 

public statc final TcpConnectionSimulator wifi =  
new TcpConnectionSimulator(500, 80, 200,1); 

public static final TcpConnectionSimulator t3_fibar =  
new TcpConnectionSimulator(45000, 10, 0, 0); 

public static final TcpConnectionSimulator cell =  
new TcpConnectionSimulator(100, 400, 250, 5); 
} 

縦の線をまっすぐにする

CheckFullName("doug Adams","Mr.Doglas Adams",""); 
CheckFullName("Jake Brown","Mr. Jake Brown III",""); 
CheckFullName("No such Guy","","no match found"); 
CheckFullName("John","","more than one result"); 

わかりにくい。

CheckFullName("doug Adams" ,"Mr.Doglas Adams"   ,""); 
CheckFullName("Jake Brown"  ,"Mr. Jake Brown III"  ,""); 
CheckFullName("No such Guy" ,""                            ,"no match found"); 
CheckFullName("John"             ,""                           ,"more than one result"); 

わかりやすい・・・? 要検討。

宣言をブロックにまとめる

これは、関数の宣言などで、void型はvoid型にまとめるとか、機能ごとにまとめるといった宣言方法をとったほうが良い。

コードを段落に分割する

具体的には、何か1つのアクションをするごとに1行開けるといった方法がよく用いられる。

下手にコメントを入れるよりも、関数名をわかりやすくしたほうがよい。

//レジストリキーのハンドルをReleaseする。実際のレジストリは変更しない。 
void DeleteRegistry(Registry* key); 

void ReleaseRegistryHundle(Registry* key); 

このほうが、分かりやすいし、混乱もない。

「監督のコメンタリー」を入れる

コメントは自分の考え、コードの悪い点、具体的な例を入力するとよい。後で聞かれそうなことを書く。

//このデータではハッシュテーブルよりもバイナリツリーのほうが40%早かった。 

とか。

//このクラスは汚くなってきている。 
//サブクラスを作成して整理したほうがいいだろう。 

とか。 コードの欠陥については、

記法 
意味 
TODO: 
あとで手をつける 
FIXME: 
既知の不具合がある 
HACK: 
あまりキレイじゃない解決策 
XXX: 
大きな問題アリ 

といった感じでコメントを入れておいてもいい。プロジェクトによるかも。

定数にもコメントをつけるといい

なぜその値を持っているかの背景が存在しているなら、記入する価値がある。

NUM_THREADS = 8 #値は「<=2 * num_processors」

で十分。 逆に、

PI = 3.141592653589793 #PIは円周率

このコメントはいらない。

読み手のことを考えて、質問されそうなことにコメントをつける

コメントとは、プロジェクトを熟知していない人のためにある。(もちろん、1週間後の自分に対しても。)

//メールを送信する外部サービスを呼び出している(1分でタイムアウト) 
void Sendmail(string to,string subject,string body); 
//実行時間はO(タグの数*タグの深さの平均)なので、ネストの深さに注意 
def FixBrokenHTML(html):… 

ファイルやクラスには全体像のコメントを書く

コードブロックにコメントをつけて概要をまとめる。関数単位でまとめられるならそっちのほうがいい。

入出力のコーナーケースに実例を使う

//'v'の「要素<pivot」が「要素>=pivot」の前に来るように配置しなおす。 
//それから、「v[i]<pivot」になる最大の'i'を返す(なければ-1を返す) 
int Partition(vector(int)* v,int pivot); 

これだけでは、視覚化がしづらい。1文加えると良い

//実例:Partition([8 5 9 8 2], 8)の結果は[5 2| 8 9 8]で、1を返す。 

よくわからない引数を説明するときのみ、名前付き引数コメントで、何を処理しているか明確化する。

Connect(10,false); 

数値とブール値が渡されているが、何なのかよくわからない。

Connect(/* timeout_ms = */ 10, /* use_encryption = */ false);  

if文の条件には基本的に肯定形を使う。「trueのとき」のことを念頭に置いて条件分岐を読み進めるのは気が散る。 一方、否定形でも短くて注意を払わせたいときもある。

if not file: 
#error logを出力 
else: 
… 

三項演算子 「(条件)? a : b」 は、読み手が分かりやすくなるとき以外は使わない

time_str += (hour>=12) ? "pm" : "am"; 

if(hour>=12) 
{ 
time_str += "pm"; 
} 
else 
{ 
time_str += "am"; 
} 

三項演算子が分かりやすい数少ないパターン。基本的にはif/elseでよい。 do/whileをなるべく避ける。他のコードと違い、条件が下にあるため、読む流れが変わってしまう。 ほとんどの場合、while(条件){}で書き直せる。

あと、次に示す処理でどうなるのかの判断が難しい。

do{ 
continue; 
} 
while(false); 
//ちなみに、これは1回のみ実行される 

ネストは浅く保ち、精神的なスタックを減らす

if(user_result == true) 
{ 
    if(permission_result != true) 
    { 
        reply.WriteErrors("error reading permissions"); 
        reply.Done(); 
        return; 
    } 
} 
reply.WriteErrors(""); 
reply.Done(); 

このネストを削減するにはどうすればいいだろう? 解決策としては、早めにreturnをしてネストを削除する。

if(user_result != true) 
{ 
    reply.WriteErrors(user_result); 
    reply.Done(); 
    return; 
} 
if(permission_result != true) 
{ 
    repry.WriteErrors(permission_result); 
    reply.Done(); 
    return; 
} 
reply.WriteErrors(""); 
reply.Done(); 

for文などで、容易にreturnを使えない場合はcontinueを使う

巨大な式は飲み込みやすい大きさに分割する。 分割する際は、説明変数を用いる。

if line.split(':')[0].strip() == "root"; 
… 

username = line.sprit(':')[0].strip() 
if username == "root"; 
… 

ド・モルガンの法則を使う

if(!(a && !b)) 

if(!a || b) 

変数のスコープscopeを縮めることは、いつだっていい事だ

というか、人間はメモリ容量が少ない。

ブロックスコープをうまく使う

変数は使う直前で宣言する。そして、何度も同じ変数に代入するのはやめる。 以下はC++のコード。

if(...) 
    { 
    int x = 1; 
    } 
x++; 
#コンパイルエラー 

逆にJavaScriptなどでは関数全体にこぼれ出たりするので注意。 定義の位置を下げる。 これはレガシープログラマがやりがちなことと被る。 (昔のCでは変数の宣言が先頭にある必要があったので。) ふつうは使う直前でのみ宣言して、使ったら早々に放棄する。 C#などではガベージコレクションが管理をしてくれるので放置。

変数は1度だけ書き込む

できれば1度だけ書き込み、永久に変更されない変数が使いやすい。

static const int NUM_THREADS = 10; 

この変数に対して多くのことを考える必要はない。 では、どうすれば1度だけ書き込むように構成するのか? 少しずつ何度もコードを改良する。 いっぺんに変数の動きを減らすのは難しい。

まず、パッと見で不要な変数を削除する(どうでもいいbool型の変数とか)

次に、whileやfor文中で何度も変更されている変数に着目する。 for文中でさらにif文がネストされていて、 書き換えて判定→書き換えて判定→書き換えて判定... といったものを繰り返しているのであれば、while(true)やfor(var i = 0;true;i++)などで ループ内の文章を減らすことで可読性が向上する。

無関係の下位問題を抽出する

機能を分離してメソッドを作るときの一番の指標となる。 1.関数やコードブロックを見て「このコードの高レベルの目標は何か?」を自問する  →たとえば、「与えられたデータから一致する人を割り出す」など 2.コードの各行に対して、「高レベルの目標に直接効果があるのか、あるいは、無関係の下位問題を解決しているのか」について自問する  →たとえば、「与えられたデータがバイナリであった場合、テキストに変換する」など 3.無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にまとめる

なぜこのようなことをするのか?理由は二つある コードが独立しているため、改善やテストがしやすい 他のプロジェクトで再利用しやすい

この手法は多くのプログラマが実行できていない。 一番の理由は、「やりすぎ」の境界線がよくわかってないから。 関数を追加すると、若干ではあるが確実に読みにくさのコストが発生する。 そのコストを上回るようなコスト削減を見込めないのであれば、それは追加すべき関数じゃない。

一度にひとつのことを

これは無関係の下位問題を抽出するときにも効果を発揮する。 複数の下位問題を抽出しているのであれば、機能単位でさらに分割してしまえばよい。 そして、プログラムのデフラグをする。 1つのプログラムの中でAをやってBをやって、またAに飛んで・・・・というのはわかりにくい。 A→B→C→Dという流れを作っておく。 機能を分割するにはどうすればうまくいく?

言葉でプログラムを説明してみる

1行がそのまま関数になる。

コードは小さく保つこと

プログラマが思っているより、運用はめんどくさい。

書く前にライブラリを調べろ

いま実装しようとしている機能の大半は必要ない。 なにか(主に、ログなど)を出力させるプログラムは、ターミナルやコマンドプロンプトに丸投げしよう。