はじめに
約3年前の新卒入社した際に本書を買ったが、ずっと埃をかぶっており流石にそろそろ読まねばと危機感を感じたので、重い腰を上げて読んだ証を残していきたいと思います。
私自身の備忘録つもりで書くので、あまり参考にならないかもしれないです。
一気に書くとモチベーションが続かないので、少しずつこの記事に追記していきます。
第1章 理解しやすいコード
基本ではあるが、コードを短く書くことよりも「理解するまでにかかる時間を減らす」ことに力を入れることが大切。
自分が気持ちよくなるために書くコードは趣味だけにしておけ。(自戒)
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());
bucket = FindBucket(key);
// 文中では{}を付けていないが、個人的にはなるべく付ける派なので...
if (bucket != NULL) {
assert(!bucket->IsOccupied());
}
コメントもつけることで、コードは長くなるが「理解するまでにかかる時間」を減らすことができる。
// "hash = (65599 * hash) + c" の高速版
hash = (hash << 6) + (hash << 16) - hash + c;
第2章 名前に情報を詰め込む
- 明確な単語を選ぶ
- tmp や retval などの汎用的な名前を避ける
- 具体的な名前を使って、物事を詳細に説明する
- 変数名に大切な情報を追加する
- スコープの大きな変数には長い名前を付ける
- 大文字やアンダースコアなどに意味を含める
明確な単語を選ぶ
よく使いがちな「get」は便利でだが、あまり明確な単語ではないので安牌に逃げない。
代替案 | 使用目的 | 使用例 |
---|---|---|
Fetch | インターネット | FetchPage() |
Download | ファイルなどの取得 | DownloadFile() |
Choose | データを抽出 | ChooseItem() |
Select | DBからデータを抽出 | SelectItem() |
BinaryTree クラスでの例。
class BinaryTree {
int Size(); // ← ツリーの高さ?ノード数?メモリ消費量?
...
}
class BinaryTree {
int Height(); // ツリーの高さ
int NumNodes(); // ノード数
int MemoryBytes(); // メモリ消費量
...
}
かと言って、やりすぎには気を付ける。
本書で紹介されている「カラフル」な代替案の引用。
単語 | 代替案 |
---|---|
send | deliver, dispatch, announce, distribute, route |
find | search, extract, locate, recover |
start | launch, create, begin, open |
make | create, ser up, build, generate, compose, add, new |
tmp や retval などの汎用的な名前を避ける
var euclidean_norm = function(v) {
var retval = 0.0; // ← 戻り値という情報以外何もない
for (var i = 0; i < v.length; i++) {
retval += v[i] * v[i];
}
return retval;
}
var euclidean_norm = function(v) {
var sum_squares = 0.0; // 2乗の合計と分かる
for (var i = 0; i < v.length; i++) {
sum_squares += v[i] * v[i];
// sum_squares += v[i]; ← 2乗されていないのでバグ!
}
return retval;
}
tmp
情報の一時的な保管や、生存期間が短い時にだけ使うようにする。
if (right < left) {
tmp = right;
right = left;
left = tmp;
}
下記の tmp の使い方を見ると、tmp の意図が正確に読み取れないので怠慢。
String tmp = user.name();
tmp += " " + user.phone_number();
tmp += " " + user.email();
...
template.set("user_info", tmp);
tmp = tempfile.NamedTemporaryFile();
...
SaveData(tmp, ...);
tmp の変数名から「ユーザー情報」「tempファイル」であることが読み取りにくい。
なので、下記のように書き換えるとより理解しやすくなる。
String user_info = user.name();
user_info += " " + user.phone_number();
user_info += " " + user.email();
...
template.set("user_info", tmp);
tmp_file = tempfile.NamedTemporaryFile();
...
SaveData(tmp_file, ...);
ループイテレータ
i, j, k, iter などは、インデックスやループイテレータでよく使うが、より良く名前を付けることができる。
for (let i = 0; i < clubs.size; i++) {
for (let j = 0; j < clubs[i].members.size; j++) {
for (let k = 0; k < users.size; k++) {
if(clubs[i].members[k] === users[j]){
...
これだと、ネストが深くなった際に追いにくいので、
- club_i, members_i, users_i
- ci, mi, ui
とするとバグが目立ちやすくなる。
(個人的には後者の表現が好みかも)
if (clubs[ci].members[ui] == users[mi]) // ← ui と mi が逆になっている!
if (clubs[ci].members[mi] == users[ui]) // ← OK!
抽象的な名前よりも具体的な名前を使う
例:--run_locally
このコマンドオプションだと、具体性がないので「ローカルで実行する」ということしか分からない。
そのため、下記のようにもっと具体性を持たせると良くなる。
例 | 使用用途 |
---|---|
--use_local_database | ローカルのデータベースを使用する |
--extra_logging | ログを出力する |
--dry_run | お試し実行 |
特に「--dry_run」は、Jenkins で Jenkinsfile を更新した際に、一度このオプションを付けてジョブを実行することで、処理は実行せず設定だけ反映させる 用途としてよく使用している。
名前に情報を追加する
id などは、基本的に10進数表記が基本的であるので、16進数で指定したい場合には明示的にする必要がある。
string id; // 誰も16進数であると分からない
string hex_id; // これなら誰でも理解できる
値の単位
時間やバイト数などのものは、明示的に単位を付けるとより分かりやすくなる。
var start_ms = (new Data()).getTime();
...
var elapsed_ms = (new Data()).getTime() - start_ms;
document.writeln("読み込み時間:" + elapsed_ms / 1000 + " 秒");
単位の例
一般的な名前 | 単位を追加した名前 |
---|---|
delay | delay_sec |
size | size_mb |
limit | max_kbps |
angle | degrees_cw |
その他の重要な属性を追加する
単位以外にも、明示的に安全ではないデータなどを示すために「untrustedUrl」「unsafeMessageBody」などの変数名を使用すると良い。
変数名 | 改善後 | 状況 |
---|---|---|
password | plaintext_password | password が平文なので、暗号化が必要である |
comment | unescaped_comment | 入力された comment は表示する前にエスケープが必要である |
html | html_utf8 | html の文字コードを utf-8 に変えた |
data | data_urlenc | data を URLエンコードした |
名前の長さを決める
d など短すぎる名前も良くないが、かと言って下記のような長すぎる名前も避けたい。
class NavigationControllerWrappingViewControllerForDataSourceOfExtendClassFromBaseClass {
}
スコープが小さければ短い名前でもいい
if (debug) {
map<string, int> m;
LookUpNamesNumbers(&m);
Print(m);
}
class XXX {
private:
map<string, int> m;
...
============================
...
// 間に長い行数あると、m の型や目的が把握しずらく読みにくい。
LookUpNamesNumbers(&m);
Print(m);
}
第3章 誤解されない名前
例:filter()
filter() だと「選択」するか「除外」するかで解釈が分かれるので、下記のように使い分けるのが良さそう。
目的 | 関数名 |
---|---|
選択する | select() |
除外する | exclude() |
results = Database.all_objects.select("year <= 2011");
results = Database.all_objects.exclude("year <= 2011");
例:Clip(text, length)
Clip() も「削除」するか「切り詰める」かで2通りの解釈がされてしまうので、下記のように使い分けるのが良さそう。
目的 | 関数名 |
---|---|
削除する | remove() |
切り詰める | truncate() |
def RemoveText(text, length):
# 最後から length 文字削除する処理
def Truncate(text, length):
# 最大 length 文字切り詰める処理
また、length の引数名も下記のように改善できる。
目的 | 関数名 |
---|---|
最大バイト数 | max_bytes |
最大文字数 | max_chars |
最大単語数 | max_words |
限界値を含める時は min と max を使う
限界値を含める場合には、接頭辞に max_ や min_ をつけるようにすると良さそう。
MAX_ITEMS_IN_CART = 10
if shopping_cart.num_items() > MAX_ITEMS_IN_CART:
...
範囲を指定するときは first と last を使う
範囲指定する際には first, last のようにすると解釈が分かれにくい。
print insteger_range(first=2, last=4)
包含/排他的範囲には begin と end を使う
first, last と似ているが、包含/排他的にされた範囲指定されたものには、begin, end を使うと良さそう。
PrintEventsInRange(beginDate="OCT 16 12:00AM", endDate="OCT 17 12:00AM")
ブール値の名前
- 接頭辞には is, has, can, should などを付けた方が良い。
- 変数の名前を 否定形 にするのは避けるべきである。
bool disable_ssl = false;
bool use_ssl = true; // どちらも同じ意味だがこちらの方が分かりやすい
ユーザの期待に合わせる
例:get*()
get系の関数は値を返すだけの「軽量アクセサ」である認識が強い。
そのため、二乗平均平方根など計算負荷の高い関数に getRMS() などを付けると良くないので、computeRMS() などの名前に変更するべきである。
RMS = \sqrt{ \frac{1}{n} \sum_{i=0}^{n-1}x_i^2}
public double computeRMS() {
// コストのかかる計算処理をする
}
第4章 美しさ
一貫性のある簡潔な改行位置
横に長すぎるのは美しくないので、改行位置を統一させる。
public class PerformanceTester {
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(
500, /* Kbps */
80, /* millisecs latency */);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
45000, /* Kbps */
10, /* millisecs latency */);
...
}
メソッドを使った整列
1行が長くならないように、ヘルパー関数で分割する。
DatabaseConnection database_connection;
String error;
assert(ExpandFullName(database_connection, "Doug Adams", &error == "Mr. Douglas Adams"));
assert(error == "");
assert(ExpandFullName(database_connection, "Jake Brown", &error == "Mr. Jacob Brown III"));
CheckFullName("Doug Adams", "Mr. Douglas Adams", "")
CheckFullName("Jake Brown", "Mr. Jacob Brown III", "")
void CheckFullName(String partial_name,
String expected_full_name,
String expected_error) {
// database_connection はクラスのメンバ変数になっている。
String error;
String full_name = ExpandFullName(database_connection, partial_name, &error);
assert(error == expected_error);
assert(full_name == expected_full_name);
}
縦の線をまっすぐにする
流し読みやタイポが見つけやすくなる。
CheckFullName("Doug Adams" , "Mr. Douglas Adams" , "");
CheckFullName(" Jake Brown ", "Mr. Jake Brown III", "");
CheckFullName("John" , "" , "more than one result");
location = request.POST.get("location");
phone = equest.POST.get("phone"); // ← タイポしているのが見つけやすい
url = request.POST.get("url");
command[] = {
...
{ "timestamping", &opt.timestamping, cmd_boolean },
{ "tries", &opt.ntry, cmd_number_inf },
{ "useragent", NULL, cmd_spec_useragent },
...
}
一貫性と意味のある並び
- ある場所で「A, B, C」と並んでいたら、他の場所でも「A, B, C」の並びにする
- 「最重要」なものから重要度順に並べる
- アルファベット順に並べる
宣言をブロックにまとめる
コメント行や空行をいれることで見やすくなる。
class FrontendServer {
public:
FrontendServer();
void ViewProfile(HttpRequest* request);
void CloseDatabase(string location);
void SaveProfile(HttpRequest* request);
~FrontendServer();
void OpenDatabase(string location, string user);
}
class FrontendServer {
public:
FrontendServer();
~FrontendServer();
// ハンドラ
void ViewProfile(HttpRequest* request);
void SaveProfile(HttpRequest* request);
// データベースのヘルパー
void OpenDatabase(string location, string user);
void CloseDatabase(string location);
}
第5章 コメントすべきことを知る
コメントするべきでは「ない」こと
コードからわかることをコメントに書かない。
// Account クラスの定義
class Account {
public:
// コンストラクタ
Accout()
// profitに新しい値を設定する
void SetProfit(double profit);
...
ただ、一目見て分かりずらいものにコメントを付けるのはあり。
# 2番目の'*'以降をすべて削除する
name = '*'.join(line.split('*')[:2])
「監督のコメンタリー」を入れる
下手に最適化しようとして無駄に時間を使うなら、コメントを付けることで時間を使う必要がなくなる。
// このデータだとハッシュテーブルよりもバイナリツリーのほうが40%早かった。
// 左右の比較よりもハッシュの計算コストのほうが高いようだ。
// ヒューリスティックだと単語が漏れることがあるが仕方ない。100%は難しい。
// このクラスは汚くなってきている。
// サブクラス 'ResourceNode' を作って整理したほうがいいかもしれない。
コードの欠陥にコメントを付ける
欠陥があるコードには、以下のような記法でコメントを付けると良い。
記法 | 典型的な意味 |
---|---|
TODO: | あとで手を付ける |
FIXME: | 既知の不具合があるコード |
HACK: | あまりキレイじゃない解決策 |
XXX: | 危険!大きな問題がある |