💡 Key Takeaways
- 1. Names Should Reveal Intent, Not Require Archaeology
- 2. Functions Should Do One Thing and Do It Well
- 3. Comments Should Explain Why, Not What
- 4. Keep Your Code DRY, But Not Bone Dry
マーカス・チェン、フォーチュン500企業とスタートアップで14年間スケーラブルなシステムを構築してきた主任ソフトウェアエンジニア
💡 重要なポイント
- 1. 名前は意図を明らかにすべきで、考古学を必要とするべきではない
- 2. 関数は1つのことをし、それをうまく行うべきである
- 3. コメントは何をするのかではなく、なぜそうするのかを説明すべきである
- 4. コードをDRYに保つべきだが、骨の髄までDRYにするべきではない
3年前、私はキャリアの選択に疑問を持たせるようなコードベースを引き継ぎました。前のチームは非常に速く機能を提供していました。しかし、主要なサービスファイルを開いたとき、4200行の絡み合ったロジック、temp2やfinalFinalと名付けられた変数、17の異なることを行う関数を見つけました。1時間で修正できる簡単なバグ修正が3日もかかりました。このプロジェクトは私に重要なことを教えてくれました: 規律のない速度は、29%のAPRで複利的に増える技術的負債を生み出します。
それ以来、私はクリーンコードに夢中になりました。5000万人のユーザーにサービスを提供するレガシーシステムをリファクタリングし、80人以上の開発者を指導し、これらの原則を採用することでチームが生産性を高めるのを見てきました。データは説得力があります: クリーンコード原則を実践するチームはバグ密度を40-60%削減し、新しい開発者のオンボーディング時間を半分にします。さらに重要なのは、彼らは自身のコードベースと常に戦っているわけではないので、長期的には機能をより早く出荷しているということです。
クリーンコードは、教条的であることや、単なるルールに従うことではありません。未来の自分、チームメート、そして生産がダウンしているときにあなたの作業を保守する次の開発者への敬意です。ここに、私がコードを書く方法と、私がリードしてきたチームがソフトウェアを提供する方法を変えた10の原則があります。
1. 名前は意図を明らかにすべきで、考古学を必要とするべきではない
現在の会社で初めてコードをレビューしたとき、processData()という関数に出会いました。それが実際に何をしているのかを理解するのに45分かかりました: ユーザー入力を検証し、通貨の値を変換し、3つのデータベーステーブルを更新し、2通りの異なるメールを送信し、分析イベントをログに記録しました。この複雑さについて名前は何も明らかにしていませんでした。
良い命名はクリーンコードの基盤です。私たちは書くよりもずっと多くのコードを読むからです。研究によれば、開発者はコードを理解するのに58%の時間を費やし、実際にそれを書くのは42%です。曖昧な名前は、その読み取り時間に課せられる税金であり、そのコードに触れるすべての開発者に掛け算されます。
私の命名フレームワークはここにあります: 変数または関数の名前は、実装を読むことなしに3つの質問に答えるべきです。それは何を表していますか?それは何をしますか?それはなぜ存在しますか?dと呼ばれる変数はこれらの質問のいずれにも答えませんが、daysSinceLastModificationと呼ばれる変数はすべてに答えます。
関数については、動詞-名詞のパターンに従っています。getUserById()は明確ですが、get()は無意味です。handleUserData()はあいまいです—どうやってハンドルしますか?ブール値の変数については、述語を使用します: isActive、hasPermission、canEdit。これらは条件文に自然に読み取れます: if (isActive && hasPermission)対if (active && permission)。
誰かがコードベースの半分でcustomerをcustと略し、残りの半分でcstmrとすると、チームは数百時間を無駄にするのを見てきました。一貫性は非常に重要です。早い段階で命名規約を確立し、コードレビューやリンティングルールを通じてそれを強制してください。真夜中にデバッグしていて、tmp_val_2が何を表しているかを解読しなくて済むとき、未来の自分は感謝するでしょう。
私が使う実践的な技術の一つは、良い名前を即座に思いつけない場合、変数や関数が何をするのかを記述したコメントを書き、そのコメントを名前に変えることです。名前が長すぎる(4-5語を超える)場合、それは通常その関数がやりすぎており、分割する必要があるという信号です。
2. 関数は1つのことをし、それをうまく行うべきである
単一責任の原則は、単なる学問的理論ではなく、保守可能なコードと保守が難しいコードとの差です。このことを経験したのは、ユーザー登録、メール確認、支払い処理、分析トラッキングを処理する300行の関数をデバッグしているときでした。バグを見つけるのに6時間かかり、修正には5分しかかかりませんでした。
"コードを読む時間と書く時間の比率は10対1を超えています。新しいコードを書く努力の一環として、古いコードを常に読んでいます。読みやすくすることは、書きやすくすることでもあります。" — ロバート・C・マーチン
関数は1つのことをし、それをうまく行い、そのことだけを行うべきです。しかし、「1つのこと」とは何がカウントされますか?私の経験則はこうです: 関数が何をするのかを「そして」という言葉を使わずに1文で説明できない場合、それはやりすぎです。validateUserInput()は1つのことをしますが、validateUserInputAndSaveToDatabase()は2つのことをし、分割されるべきです。
私は10-20行の長さの関数を目指しています。ある開発者はこれが極端だと考えますが、小さな関数には大きな利点があります。それらはテストが容易であり、複雑なシナリオを設定せずに1つの振る舞いを検証できます。再利用も容易であり、小さく集中した関数はより大きな操作のビルディングブロックになります。理解しやすく、スクロールなしで関数全体を把握できます。
大きな関数をリファクタリングする際は、コードが抽象レベルを変更する自然な境界を探します。入力を検証し、データを変換し、データベースに保存する関数は、3つの異なるレベルで操作しています。各レベルを独自の関数に抽出します: validateOrderData()、transformOrderForStorage()、saveOrder()。元の関数はこれら3つの関数を順番に呼び出すコーディネーターになります。
このアプローチは、エラーハンドリングをクリーンにします。50行に及ぶ入れ子のtry-catchブロックを使う代わりに、各小さな関数が自分のエラーを適切に処理します。コーディネーター関数は、実装の詳細に執着することなく、高レベルのエラーシナリオを処理できます。
私はこの原則が私のチームに与えた影響を測定しました。厳格な関数サイズの制限を採用した後、バグ修正までの平均時間が4.2時間から1.8時間に減少しました。新しいチームメンバーは、システム全体を理解せずに個々の関数を理解できるため、40%早く生産的になりました。
3. コメントは何をするのかではなく、なぜそうするのかを説明すべきである
キャリアの初期に、私は良いコードはたくさんのコメントを意味すると考えていました。私は// counterを1増やすのようなことをcounter++の上に書いていました。私のシニア開発者が私を呼び寄せ、「もしあなたのコードが何をするのかを説明するためにコメントが必要なら、あなたのコードは十分に明確ではない」と言いました。
| 側面 | 汚いコード | クリーンコード | 影響 |
|---|---|---|---|
| 関数名 | processData(), doStuff(), handleIt() | validateAndTransformUserInput(), sendWelcomeEmail() | 理解時間が70%削減 |
| 関数の長さ | 200-500行、複数の責任 | 10-20行、単一責任 | バグ密度が40-60%削減 |
| 変数名 | temp2, finalFinal, x, data | userEmailAddress, validatedOrderTotal | オンボーディング時間が半分に短縮 |
| コメント | 何をするかを説明する | なぜその決定をしたのかを説明する | メンテナンス時間が50%削減 |
| コードの重複 | 5つ以上のファイルで同じロジックをコピー | 再利用可能な関数に抽出 | 変更に1つの編集が必要(5つ以上ではなく) |
コメントは、なぜその決定をしたのかを説明するべきであって、コードが何をするのかを説明するべきではありません。コード自体は、良い命名と明確な構造を通じて自己説明的であるべきです。私は// ユーザーをループするというコメントをforループの上に見ると、それはノイズだと感じます。ループはすでにユーザーをループしていることを示しているからです。しかし、// 配列は通常5-10項目であり、2分探索のオーバーヘッドは価値がないため、ここでは線形探索を使用するというコメントは価値があります。それはコードからは明らかでない決定を説明します。
私はコメントを使用して、明白でないビジネスルールを文書化したり、サードパーティライブラリバグの回避策を説明したり、なぜ「明白な」解決策を使用していないのかを明確にします。例えば: // Safari 12はサービスワーカーでそれをサポートしていないため、ここではasync/awaitを使用できません。また、8%のユーザーがまだSafari 12を使用しています。このコメントは、次の開発者が壊れていない何かを「修正」するのを防ぎます。
警告コメントは、別の正当な使用例です。// 警告: このタイムアウトを変更すると、支払い処理のレート制限に影響します。変更する前にチケット#1234を確認してください。これは、善意の開発者が予期しない影響を及ぼす変更を行うのを防ぎます。