💡 Key Takeaways
- Rule I Follow #1: Functions Should Do One Thing (But I Define "One Thing" Differently)
- Rule I Follow #2: Meaningful Names Are Non-Negotiable
- Rule I Follow #3: Comments Explain Why, Not What
- Rule I Follow #4: Keep Functions and Classes Small (With Nuance)
私は中規模のフィンテック企業でシニアソフトウェアアーキテクトとして14年間他の人のコードを見つめてきたのですが、私がクリーンコードの信者でなくなった正確な瞬間をお話ししましょう。それは2019年3月の火曜日、午前2時47分のことでした。誰かが叔父ボブの本のあらゆるルールに従うために完璧に機能しているモジュールを3日間もリファクタリングしていたため、私たちの決済処理システムがダウンしました。皮肉なことに、その「クリーンアップ」の過程でバグが発生しました。
💡 主なポイント
- 私が従うルール #1: 関数は一つのことをするべき (しかし「一つのこと」を私は異なる定義をする)
- 私が従うルール #2: 意味のある名前は譲れない
- 私が従うルール #3: コメントは「なぜ」を説明し、「何を」説明するものではない
- 私が従うルール #4: 関数とクラスを小さく保つべき (ニュアンスを持って)
その夜、私はコード品質に関する考え方が変わりました。クリーンコードの原則が間違っているとは言いません—それから遠く離れています。しかし、10,000件以上のプルリクエストをレビューし、47人の開発者を指導し、23回の主要な製品リリースを行ったことで、いかなるルールに対する教条主義的な遵守は、技術的債務の別の形に過ぎないことを学びました。いくつかのクリーンコードルールは絶対的な金の価値があります。他のものは?それは条件依存しているのがせいぜいで、最悪の場合は積極的に害を及ぼします。
ここで私が実際に生産コードで行っていること、そしてそれを行う理由です。
私が従うルール #1: 関数は一つのことをするべき (しかし「一つのこと」を私は異なる定義をする)
関数の単一責任原則は、私が信心深く従っている最も価値のあるルールです。しかし、ここで私が教科書から逸脱する理由は、私は「一つのこと」を行数や操作の数で測定しないからです。私はそれを概念の一貫性で測定します。
前四半期に、私は長さが8行の関数をレビューしましたが、SRPを見事に違反していました。それはユーザー入力を検証し、検証結果をログに記録し、キャッシュを更新していました。8行に押し込められた3つの異なる責任。先月書いた45行の関数と比較してください。それは複雑なデータベーストランザクションを調整していて、「一つのこと」(支払いトランザクションの完了)を行っていますが、その一つのことは一緒に属する複数のステップを必要とします。
私のリトマステストはこれです:この関数が何をするかを、「そして」という単語を使わずに一文で説明できるか?もし「この関数は入力を検証し、そしてメールを送信する」と言う必要があるなら、それは二つのことをしています。しかし「この関数は払い戻しリクエストを処理する」と言う場合、それには自然に検証、データベースの更新、および通知が含まれます—それは正しい抽象レベルで一つのことです。
実際には、これは私の関数の平均が純粋主義者が推奨する10〜15行ではなく、25〜30行になることを意味します。しかし、これらの関数におけるバグ率は、以前の過剰に抽出されたコードよりも40%低いです。なぜですか?関連する操作を一緒に保つことで、システムを理解する際の認知負荷が軽減されるからです。すべてが小さな関数に分割されると、ビジネスロジックを理解するのではなく、ファイル間をジャンプするのにより多くの時間を費やします。
ここでの本当の勝利はテスト可能性です。一つの概念的なことを行う関数は、たとえそれが40行であっても、テストするのが容易です。依存関係をモックし、関数を呼び出し、結果を確認します。完了。すべてを5行の関数に抽出した場合、ユニットテストは意味を持たなくなるため、結局は統合テストに至ります。
私が従うルール #2: 意味のある名前は譲れない
私はこの丘で死ぬでしょう: 変数と関数の名前は、あなたが書く最も重要なドキュメントです。私は名前が悪いためだけにプルリクエストを拒否したことがありますし、再び同じことをします。
"教条主義的にいかなるルールセットを遵守することは、技術的債務の別の形に過ぎません。最高のコードは最もクリーンなものではなく、信頼性が高く、チームによって保守できるコードです。"
2ヶ月前、あるジュニアデベロッパーが`processData()`という関数を持つコードを提出しました。私は、なぜそうなのかを説明する10分間のLoomビデオを添えて返しました。その関数は、Luhnアルゴリズムに対して支払いカード番号を検証していました。正しい名前は`validateCardNumberChecksum()`です。はい、それは長いです。はい、それはより具体的です。それこそがポイントです。
私の命名階層は、数千回のコードレビューを通じて洗練されています:
- ブール変数: いつも is/has/can/should で始めます。「active」ではなく「isActive」、「permission」ではなく「hasPermission」です。
- 関数: 動作には動詞、クエリには名詞を使用します。「calculateTotalPrice()」ではなく「totalPrice()」、「getUserById()」ではなく「user()」です。
- クラス: 動作ではなく概念を表す名詞です。「PaymentProcessor」ではなく「ProcessPayments」です。
- 定数: 真の定数にはSCREAMING_SNAKE_CASEを、変更される可能性のある設定にはcamelCaseを使用します。
影響は測定可能です。18ヶ月前に私たちのチームで厳格な命名規則を実施した後、平均PRレビュー時間は3.2時間から1.8時間に短縮されました。なぜなら、レビュアーはコードが何をしているのかを解読する時間を減らし、それが正しく行われているかを評価する時間を増やすからです。
私はまた「省略形禁止」ルールを厳守しており、正確に3つの例外があります: `id`, `url`, および `api`。その他はすべて綴ります。「usr」は「user」になります。「btn」は「button」になります。「calc」は「calculate」になります。11時にデバッグ中の人が「tmpBfr」が何を意味するのかを推測する必要がなくなるとき、その追加のキーストロークは価値があります。
私が従うルール #3: コメントは「なぜ」を説明し、「何を」説明するものではない
私はキャリアの中で二つの極端を見てきました: コメントがゼロのコードベースと、すべての行にコメントがあるコードベースです。どちらも間違っていますが、過剰にコメントされたコードの方が実際には悪化します。なぜなら、それはメンテナンスの負担を生み出し、しばしば誤解を招くからです。
| クリーンコードのルール | 従うべき時 | 無視すべき時 | 実際の影響 |
|---|---|---|---|
| 関数は小さくあるべき | トラフィックが多いコードパス、頻繁に変更されるモジュール | 複雑な調整ロジック、トランザクション処理 | 早すぎる分割はナビゲーションのオーバーヘッドを生む |
| コードにコメントが無い | 自己説明可能なビジネスロジック | 複雑なアルゴリズム、規制要件、明白でない最適化 | コンテキストが欠如するとデバッグに何時間もかかる |
| DRY (自分を繰り返すな) | コアビジネスロジック、データ変換 | 似ているが文脈的に異なるコード | 過剰な抽象化は脆弱な依存関係を生む |
| プリミティブオブセッションを避ける | ドメインモデル、APIの境界 | 単純な内部ユーティリティ、パフォーマンスに重要なパス | 過度なラッピングは認知負荷を増す |
私のルールはシンプルです: コードが何をしているのかを説明しているなら、そのコードはおそらく悪いです。特定の決定を下した理由を説明しているなら、それは良いコメントです。ここでの実際の例はo