はじめに
これはプログラミング初学者であるもりぞう(私)が、オライリー・ジャパン出版の「リーダブルコード」に出てくるサンプルコードを Python 版に翻訳した上で、この本の要点を理解してみようという学習記録です。
「コードを別言語に翻訳すること」自体が目的ではなく、筆者が説明する読みやすいコードとはどのようなものかを自分なりに理解するための試みとなります。そのためPythonへの翻訳自体は ChatGPTなどのLLMを使用し、あくまでも筆者の意図に主眼を置いて読み進めていこうと思います。
試みのきっかけ
私のようにプログラミングに興味を持ち、比較的簡単な言語であるという噂のみを信じてPythonを学び始めた人間にはきっと共通してぶつかる壁があり、その一つが 「リーダブルコード」のサンプルコードが読めないということです。
「リーダブルコード」自体はいろいろな学習記事で度々おすすめされていて、はりきって購入したはいいものの、実際にページを開いてみると冒頭から C++ の言語で書かれており、Python初学者にとっては読めないコード同士を比較して「こっちの方が読みやすい」と教えられるため、筆者の意図が腹落ちしにくいという悩みがありました。
(意図そのものは地の文でわかるのですが、サンプルコードが基本的に理解できず、そのコードのどの点に筆者の主張が反映されているのかがわからないという状況です)
そのため、「自分もいつかリーダブルコードが読めるようになればいいな…」などと考えコツコツ勉強を続けていたのですが、1年ほどPythonを勉強しても一向にサンプルが読めるようになる気配がありませんでした。
それならいっそ、自分でPythonに翻訳してしまって、「読みやすいコード」とはいかなるものかを自分自身に腹落ちさせてみようと考えたのが試みのきっかけです。
記事の対象
先述した理由から、この記事は私のようにPythonをメインにプログラミングの学習を始め、ステップアップのために『リーダブルコード』を読んでみたいが、サンプルコードでのつまずきが原因で読了を断念してしまうという方向けに書いております。『リーダブルコード』のサンプルコードを見ても全く難しくないという方からしたら理解しがたい悩みかもしれませんが、ぜひ温かい目でお読みいただけますと幸いです。
1章 理解しやすいコード
まず前提として押さえておきたいことが、この章で定義されている「優れたコード」とは、他の人が読んだときに最短時間で理解できるようなコードであるということです。
その上でまず登場するコードについて、原文(1.1)のものがこちら↓
<読みやすいとされるコード>
for (Node* node = list->head; node != NULL; node = node->next)
Print(node->data);
<読みにくいとされるコード>
Node* node = list->head;
if (node == NULL) return;
while (node->next != NULL) {
Print(node->data);
node = node->next;
}
if (node != NULL) Print(node->data);
今まではそもそもどちらも読めないので躓いていたのですが、これをPythonに翻訳するとこんな風になるそうです↓
<読みやすいとされるコード>
node = list.head
while node is not None:
print(node.data)
node = node.next
<読みにくいとされるコード>
node = list.head
if node is None:
return
while node.next is not None:
print(node.data)
node = node.next
if node is not None:
print(node.data)
こうすると、たしかにPython初学者の私にとっても一目瞭然で、読みやすいとされているコードの方が認知負荷が低く、各処理の内容が直感的に理解しやすく思います。
同じく原文1.1より↓
<「簡潔」とされているコード>
return exponent >= 0
? mantissa * (1 << exponent)
: mantissa / (1 << -exponent);
<「安心」とされているコード>
if (exponent >= 0) {
return mantissa * (1 << exponent);
} else {
return mantissa / (1 << -exponent);
}
もちろん私にはどちらも読めないので、両者ともに簡潔でも安心でもありませんでした。
以下Python版↓
<「簡潔」とされているコード>
return (
mantissa * (1 << exponent)
if exponent >= 0
else mantissa / (1 << -exponent)
)
<「安心」とされているコード>
if exponent >= 0:
return mantissa * (1 << exponent)
else:
return mantissa / (1 << -exponent)
自分用メモ
1 << exponentの部分は「ビット操作」と呼ばれ、2進数の数を左に移動させるごとに、10進数に戻した際の結果が2倍ずつ増えるそう。
最初:1 ← 2進数の1(10進数でも1)
左に1回移動:10 ← 10進数だと2
左に2回移動:100 ← 10進数だと4
左に3回移動:1000 ← 10進数だと8
章の本旨ではないのでよく分からなければ
1 << exponent は「2をexponent乗する処理」くらいに考えて読み進めていい
Python版で読んでみると、安心とされているコードの方が文字通り安心して読むことができる。おそらく、if と return の位置関係として、原因(if)→結果(return)という順序で記述されているのが直感と合うのだと思う。結果として、安心とされているコードの方が理解できるまでの時間は短いといえる。
続いて『1.3 小さなことは本当にいいこと?』より
<短いが読みづらいとされているコード>
assert(!(bucket = FindBucket(key)) || !bucket->IsOccupied());
<長くなるが読みやすいとされているコード>
bucket = FindBucket(key);
if (bucket != NULL) assert(!bucket->IsOccupied());
Python版にすると↓
<短いが読みづらいとされているコード>
assert (bucket := find_bucket(key)) is None or not bucket.is_occupied()
<長くなるが読みやすいとされているコード>
bucket = find_bucket(key)
if bucket is not None:
assert not bucket.is_occupied()
自分用メモ
assert は、条件が正しいことを確認するための命令で、条件がFalseの場合はエラーになる。
この場合は、bucket.is_occupied() の結果がFalse、つまり「bucketが使用中ではない」ことを確認している。
→もしbucketが使用中ならassertが失敗してエラーになる。
たしかに、<長くなるが読みやすいとされているコード>の方が、理解できるまでの時間は短い。特に私のような初学者にとっては、or(少なくとも一方が真ならTrue)や:=(代入した結果をそのまま条件として再利用する)を使われるよりも、馴染みのあるif文だけで構成されている後者の方がはるかに理解しやすい。
優れたコードとは記述の短さではなく、理解に要する時間の短さで決定されるという分かりやすい例示である。
※同じ1.3にあるサンプルコード
//hash = (65599 * hash) + c
に関しては、現代の環境によっては必ずしも最適化とは言えないそうなので割愛する。
まとめ
学習本のサンプルのコードを一つ一つ翻訳して読むというのは、「学習のための学習」に陥り効率の面でどうなのだろうかと迷ったが、実際に取り組んでみると予想外の発見が多く、また新しい知識もたくさん学ぶことができるのでとても勉強になることが分かった。
そして何より、ずっと読み始めては諦めていた「リーダブルコード」を、たしかに噛み砕きながら読み進められているという実感は純粋に嬉しかった。
初めてのQiita投稿はとても緊張感があり、また初めてのMarkdown記法での執筆も大変勉強になった。
プログラミング初学者の駄文につき非常に読みづらい面が多かったと思いますが、最後まで読んでくれた方ありがとうございました!
本の解釈について誤り等ございましたら是非ご教授いただけますと幸いです。
次回も「リーダブルコード」第2章を翻訳しながら読み進めてまいりたいと思いますので、ご感想やおすすめの勉強方法、躓きポイントへの共感などがございましたら、ぜひコメントしていただけると学習の励みになります!
最後までお読みいただきありがとうございました!