この記事は、Zennにも投稿しています。
はじめに
プログラミングの原則に「名前重要(Naming is important)」というものがあります。書籍『プリンシプル オブ プログラミング』でも、KISSやDRYと並ぶ原則のひとつとして紹介されています。
この原則を学んだあと、以前チーム開発で書いたIoTのコード(Raspberry Piで測った温度・湿度を、ソケット通信でPCに送るプログラム)を読み返してみると、名前のせいで読みにくくなっているところがたくさん見つかりました。
この記事では、実際のコードを例に、どんな名前が読みにくくなるのかと、どう直せばよいのかをまとめます。
名前は「コードを読む人へのUI」
関数や変数の名前は、コードを読む人にとっての画面(UI)のようなものです。
- 名前で役割がわかれば、中身を読まなくても先に進める
- 名前で役割がわからなければ、中身を1行ずつ読んで確かめるしかない
コードは書く時間よりも読む時間のほうがずっと長いので、名前が悪いと、そのたびに読む人の時間と集中力が奪われます。読む人には、数か月後の自分も含まれます。
また、良い名前が付けられないのは、その部品の役割を自分でもよくわかっていないサインでもあります。名前に悩んだときは、設計を見直すきっかけになります。
見直した名前と、直し方
1. 記号のような接尾辞をやめる
data_s_json = json.dumps(data_s_list)
data_s = data_s_json.encode('utf-8')
socket_r_s.send(data_s)
_s は「send(送る側)」、_r は「receive(受け取る側)」の意味で付けていました。書いた本人は覚えていても、初めて読む人は「s って何だろう?」と考えるところから始めなければいけません。
このように、読む人が頭の中で「_s → 送る側のこと」と変換しないと理解できない名前は避けます。すべての変数に data_ が付いているのも、「データであること」しか伝えておらず、中身の違いがわかりません。
# readings: センサーの測定値のリスト
payload = json.dumps(readings).encode("utf-8") # 送信するバイト列
connection.sendall(payload)
「何のデータか」を名前にすると、_s のような記号を付けなくても区別できるようになりました。
2. 名前に「手段」ではなく「目的」を書く
def recv_data1024(socket1, client_address1):
data_r = socket1.recv(1024)
...
recv_data1024 は、「1024バイト受け取る」という実装の手段を名前にしています。ところがこの関数は、実際には「受け取る」「JSONを読み取る」「ファイルにロックをかける」「CSVに書き込む」まで行っていて、名前と中身が合っていません。また、受け取るサイズを変えたら、名前まで変えなければいけなくなります。
名前には「どうやるか」ではなく、「何のためにやるか」を書きます。
def handle_client(connection, client_address):
"""1台の機器からの接続を処理する"""
readings = receive_readings(connection)
append_readings_to_csv(readings, CSV_PATH)
あわせて、1つの関数にまとめていた処理を、「受け取る」と「保存する」に分けました。1つの名前で説明できない関数は、やることが多すぎるというサインです。
引数の socket1 や client_address1 の 1 も、2つ目があるわけではないので必要ありません。
3. 実際の役割と違う名前を付けない
def server_test(server_v1=SERVER, waiting_port_v1=WAITING_PORT):
... # クライアントからの接続を待ち受け続ける
server_test は、最初に動作確認用として作った関数の名前が、そのまま残っていました。実際には本番で使う「サーバーを起動する」関数です。名前に test とあると、「テスト用だから消してもいい」と誤解されるおそれがあります。
def run_server(host=SERVER_HOST, port=SERVER_PORT):
...
役割が変わったら、名前も一緒に変えます。
4. 意味のない接尾辞や省略をやめる
hostname_v = SERVER
waiting_port_v = WAITING_PORT
tempe = row.get("temperature", row.get("temp_dht_1", "N/A"))
humid = row.get("humidity", row.get("humid_dht_1", "N/A"))
_v は「value(値)」のつもりでしたが、変数はすべて値なので、何の情報も増えていません。tempe のような省略も、temp(一時的な)と見分けにくかったり、検索したときに見つけにくかったりします。
server_host = SERVER_HOST
server_port = SERVER_PORT
temperature = reading["temperature"]
humidity = reading["humidity"]
少し長くなっても、省略せずに、声に出して読める名前にしておくほうが、読む人の負担は小さくなります。
5. 何を返すのかがわかる名前にする
これは、個人で作った温度の見守りシステムのコードです。
def check_temperature(temp):
ref_temp = 35
if temp > ref_temp:
return True
else:
return False
check_temperature という名前からは、「温度をチェックする」ことはわかっても、True が返ってきたときに何を意味するのかがわかりません。「正常だから True」とも「危険だから True」とも読めてしまいます。
真偽値を返す関数は、is_〜 や has_〜 のように、質問の形の名前にすると、True の意味がはっきりします。
SAFE_TEMPERATURE_LIMIT = 35 # これを超えたら危険とみなす温度(℃)
def is_dangerous_temperature(temperature):
return temperature > SAFE_TEMPERATURE_LIMIT
if is_dangerous_temperature(temperature):
send_alert_mail()
if 文が、そのまま「もし危険な温度なら、警告メールを送る」と読めるようになりました。ref_temp も、「何の基準か」がわかる定数の名前に変えています。
名前を確かめる方法:名前から説明に戻せるか
付けた名前が良いかどうかは、次の手順で確かめられます。
- その部品がやることを、文章で説明する
- 説明から名前を付ける
- 名前だけを見た人が、元の説明を思い浮かべられるかを考える
例えば「危険な温度かどうかを判定する関数」の場合です。
| 名前 | 名前から思い浮かぶ説明 | 判定 |
|---|---|---|
check_temperature |
温度をチェックする(何を返す?) | 説明に戻れない |
temperature_alert |
温度の警告(警告を送る?警告の内容?) | 誤解されやすい |
is_dangerous_temperature |
危険な温度かどうか | 説明に戻れる |
名前から説明に戻したときに、元の意味とずれたり、別の意味にも取れたりする名前は、もう一度考え直します。
まとめ
| 避けたい名前 | 例 | 直し方 |
|---|---|---|
| 読む人が頭の中で変換しないとわからない |
data_s_list、socket_r_s
|
「何のデータか」を名前にする(readings) |
| 手段を表している | recv_data1024 |
目的を表す(handle_client) |
| 役割と合っていない | server_test |
役割が変わったら名前も変える(run_server) |
| 情報が増えない接尾辞・省略 |
hostname_v、tempe
|
省略せず、読める名前にする(server_host、temperature) |
| 真偽値の意味がわからない | check_temperature |
質問の形にする(is_dangerous_temperature) |
昔のコードを読み返して一番感じたのは、「書いたときの自分にしか通じない名前」が多かったということです。名前を付けるときは、初めてこのコードを読む人の立場で考えてみてください。
参考資料
- 上田勲『プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則』秀和システム、2016年(2.7「名前重要」)
- PEP 8 – Style Guide for Python Code(Naming Conventions):Pythonの命名の慣例