本書の第1部**「表面上の改善」**にある3章のサマリーをまとめる。
鍵となる考え
名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する
3.1 例:filter()
# filterでは曖昧
results = Database.all_objects.filter("year <= 2011")
# 「選択」するのであればselect
results = Database.all_objects.select("year <= 2011")
# 「除外」するのであればexclude
results = Database.all_objects.exclude("year <= 2011")
3.2 例:Clip(text, length)
# 段落の内容を切り抜く関数
# textの最後を切り落として、「...」をつける
# 読み手の疑問:「最後からlength文字を削除する(remove)」or「最大length文字まで切り詰める(truncate)」?
def Clip(text, length):
...
# 明確にしよう(clip -> truncate)
# まだ明確化できる
def Truncate(text, length):
...
# さらに明確化(length -> max_length)
# これで終わりではない
# max_lengthは「バイト数」or「文字数」or「単語数」?
def Truncate(text, max_length):
...
# 名前に単位をつけよう(max_length -> max_chars)
def Truncate(text, max_chars):
...
3.3 限界値を含めるときはminとmaxを使う
限界値を明確にするには、名前の前にmax_やmin_をつけよう
# 定数の名前が曖昧
CART_TOO_BIG_LIMIT = 10
if shopping_cart.num_items() > CART_TOO_BIG_LIMIT:
Error("カートにある商品数が多すぎます。")
# 定数の名前をより明確に
MAX_ITEMS_IN_CART = 10
if shopping_cart.num_items() > MAX_ITEMS_IN_CART:
Error("カートにある商品数が多すぎます。")
3.4 範囲を指定するときはfirstやlastを使う
print integer_range(start=2, stop=4)
# これが印字するのは[2, 3] or [2, 3, 4]?
# 包含的な範囲(終端を範囲に含める)ならば、first, lastを使うのがいい
# min, maxを使って表すこともできる
set.PrintKeys(first="Bart", last="Maggie")
3.5 包含/排他的範囲にはbeginとendを使う
#下よりも上の方が簡単。ここに使う仮引数にはbeginとendであることが多い
PrintEventsInRange("OCT 16 12:00am", "OCT 17 12:00am")
PrintEventsInRange("OCT 16 12:00am", "OCT 16 11:59:59.9999pm")
3.6 ブール値の名前
ブール値の変数やブール値を返す関数の名前を選ぶときには、trueとfalseの意味を明確にしなければいけない。
# これはパスワードを「これから」読み取る必要があるのか、「すでに」読み取っているのか?
bool read_password = true;
# 代わりにこれらを使った方がいい
bool need_password = true;
bool user_is_authenticated = true;
ブール値の変数名は、頭にis, has, can, shouldなどをつけわかりやすくすることが多い。
SpaceLeft()
がブール値を返すのであれば、HasSpaceLeft()
という名前の方がいい。
# 名前を否定形にするのは避ける方がいい
bool disable_ssl = false;
# 肯定系の方が声に出して読みやすい。それに短い。
bool use_ssl = true;
3.7 ユーザの期待に合わせる
ユーザが先入観を持っているために誤解を招いてしまうことがある。
「負けを認めて」誤解されない名前に変えた方がいい。
// getMean()はデータが大量にあればものすごいコストになる。
// しかし、多くのプログラマにとってはgetで始まるメソッドは、
// メンバの値を返すだけの「軽量アクセサ」であるという規約に慣れ親しんでいる!
public class StatisticsCollector {
public void addSample(double x) { ... }
public double getMean() {
// 全てのサンプルをイテレートして total / num_samples を返す
}
}
// コストの高さが事前にわかるように以下のように変えるべき
// あるいはコストの高くない実装に変えるべき
public class StatisticsCollector {
public void addSample(double x) { ... }
public double computeMean() {
// 全てのサンプルをイテレートして total / num_samples を返す
}
}
# 以下のコードには見つけにくいバグが存在する。
# 技術的に正しいし、ユニットテストも全て成功しているが、
# 要素数が100万個のリストをShrinkList()に渡したら終了まで1時間以上かかる!
void ShrinkList(list<Node>& list, int max_size) {
while (list.size() > max_size) {
FreeNode(list.back());
list.pop_back();
}
}
# list.size()の計算量がO(n)であることを作者が知らなかった故の「バグ」。
# リンクトリストのノード数を事前計算せずに順番にカウントしているので、
# ShrinkList()全体の計算量がO(n^2)になっている
# size()がcountSize()やcountElements()だったら問題は起きなかったはず。
# 最新のC++標準では、size()の計算量をO(1)にすることが定められている。
3.8 複数の名前を検討する
複数の候補から最終的に決める前に、それぞれ長所について話し合うのが普通。
高トラフィックのウェブサイトでは、ウェブサイトの変更によってビジネスがどのくらい改善できるかを調べる「実験」をすることが多い。以下は実験用の設定ファイルである。
experiment_id: 100
description: "フォントサイズを14ptに上げる"
traffic_fraction: 5%
設定ファイルには属性と値のペアが15個ほど定義されている。
同じような実験をするときには、設定ファイルの大部分をコピペしなくてはいけない。
experiment_id: 101
description: "フォントサイズを13ptに上げる"
[ 以下、experiment_id 100と同じ ]
既存の設定ファイルを他の実験でも使えるようにしたい(これはプロトタイプ継承パターンと呼ばれる手法)。すると以下のように書ける。
experiment_id: 101
the_other_experiment_id_I_want_to_reuse: 100
[ 以下、変更が必要な情報だけ書き換える ]
ここで考えなければいけないのは、**the_other_experiment_id_I_want_to_reuse(再利用したい実験のID)の名前を何にするか?**である。
4つの名前を検討してみよう。
- template
- reuse
- copy
- inherit
新しい機能を追加したのは僕たち自身だが、この機能を知らない人が見たらどうなるかを想像しなければいけない。誤解される可能性を考える。
1. template
experiment_id: 101
template: 100
これはテンプレートだ、なのか、このテンプレートを使っている、なのかがわかりにくい。
また、テンプレートという言葉は抽象的なものに何かを埋め込んで、具体的なものにするために使うもの。
テンプレートに使う実験のことを本物の実験でない抽象的なものと誤解する人がいるかもしれない。
この状況ではtemplateを使うには意味が曖昧すぎる。
2. reuse
experiment_id: 101
reuse: 100
悪くないが、文字だけ見ると、この実験は100回再利用できると誤解される可能性もある。
reuse_id
に変える方がいいが、この実験の再利用idは100だと誤解されうる。
3. copy
experiment_id: 101
copy: 100
これだけでは、この実験を100回コピーする、なのか、これは100回目のコピーだ、なのかがわからない。
他の実験を参照している言葉だということを明確にするにはcopy_experiment
という名前に変えるといい。
4. inherit
experiment_id: 101
inherit: 100
inherit
はプログラマには馴染みのある言葉だ。
何かを継承するというのは新たに変更を加えるという意味。
クラスを継承すれば、そのクラスのメソッドとメンバが全て手に入り、それらを変更したり新しく追加したりできる。
現実の世界でも、身内の財産を継承するというのは、その財産を自分で好きなように保有したり売却したりできるということだ。
ただし、他の実験から継承していることは明確にしておくために、inherit_from
やinherit_from_experiment_id
に変えると良い。
検討の結果、最善の名前はcopy_experiment
とinherit_from_experiment_id
ということに。
理由は、何が起きるかを明確に表していて、誤解を生む可能性が低いからだ。
3.9 まとめ
・最善の名前は誤解されない名前。filter, length, limitは曖昧
・名前を決める前に反対意見を考えるなどして、誤解されない名前かどうか想像する
・上下の限界値を決めるときはmax_, min_を頭に
・包含的範囲ならfirstやlastを使う
・包含/排他的範囲ならbeginとendがイディオムなので使う
・ブール値に名前をつけるときはisやhasなどを使う
・disable_sslのような否定形は避ける
・単語に対するユーザの期待にも注意(get()やsize()には軽量なメソッドが期待されている)