この業界は「読みやすいコードを書け」を宗教にしてしまった。そして多くの宗教と同じように、信者たちは本来のメッセージを忘れてしまった。
チームメイトがチケットを取った。チェックアウトに割引フィールドを追加するだけ。1日の作業、テスト込みで2日。
3日後、彼は40ファイルに触るPRを持ってきた。決済モジュールをストラテジーパターンにリファクタリングしていた。インターフェースを作成。「将来もっとプロバイダーが必要になったら?」という理由でファクトリーも追加。
チケットに必要だったもの:
checkout.tsx — 入力フィールド追加api/checkout.ts — discountパラメータ追加DBマイグレーション — カラム追加実際に納品されたもの:
DiscountStrategy インターフェースPercentageDiscountStrategyFixedAmountDiscountStrategyDiscountStrategyFactoryDiscountValidatorService見積もり: 1-2日。実際: 5日。
プロバイダーは2つだった。3年間ずっと2つだった。
彼は本気で正しいことをしていると思っていた。 一方、PMはなぜ単純なフィールドにスプリントの半分がかかったのか聞いていた。
Uncle Bobの本には良いアドバイスがある。関数は小さくあるべき。名前は意味のあるものに。それはいい。
でも私たちはガイドラインを戒律に変えてしまった。「これは役に立つか?」と問うのをやめ、「これはルールに従っているか?」と問い始めた。
UserNameValidator、UserEmailValidator、UserAgeValidator、そしてCompositeUserValidatorを作成するUserValidatorFactory。3つのフィールドをバリデーションするために。
3つのフィールドをチェックするから「3つのこと」をしている、だから単一責任原則に違反していると言う人がいるだろう。
ユーザー入力のチェックは1つのことだ。 そうでないと主張するための精神的曲芸こそが問題の本質だ。
関数を抽出 — 再利用性を得て、局所性を失う。
インターフェースを作成 — 柔軟性を得て、直接性を失う。
パターンを適用 — 「既知の解決策」を得て、シンプルさを失う。
抽象化を追加 — 将来への備えを得て、現在の明快さを失う。
これらはどれも無料ではない。クリーンコード信者はそうでないふりをする。
何かをリファクタリングする前に:
「このコードは今、問題を起こしているか?」
理論的にではなく。仮想の未来でもなく。今。
リファクタリングすべき時:
リファクタリングすべきでない時:
今問題がないなら — 放っておいて何かを作れ。
calculateTotalPrice > calc。常に価値がある。ジュニアの頃はクリーンコード=良いエンジニアだと思っていた。ミドルの頃はデザインパターン=良いエンジニアだと思っていた。今はメンテナンスできる動くソフトウェアを届けること=良いエンジニアだと思っている。
プルリクエストで演技するのをやめろ。リリースしろ。
リリースされた「まあまあ」のコードは、リリースされない「完璧な」コードに毎回勝つ。
— blanho
みんな箱と矢印を描いている。誰もコードをリリースしていない。システム設計は重要だ、でもTwitterが思うほどじゃない。
Cloudflareは「あのCDN会社」からフルクラウドプラットフォームになった。ほとんどのスタートアップはまずそっちを見るべき。
みんなOpenAIをデフォルトにする。Grabは速くて安い1Bモデルを作った。レンタルをやめるべきはいつか?
// "クリーン"なコード(5ファイルで129行)
interface Validator<T> {
validate(value: T): ValidationResult;
}
class UserNameValidator implements Validator<string> { ... }
class UserEmailValidator implements Validator<string> { ... }
class UserAgeValidator implements Validator<number> { ... }
class CompositeUserValidator implements Validator<User> { ... }
class UserValidatorFactory { ... }
// 実際に必要だったもの(1ファイルで8行)
function validateUser(user: User) {
const errors = []
if (!user.name) errors.push('Name is required')
if (!user.email.includes('@')) errors.push('Invalid email')
if (user.age < 18) errors.push('Must be 18+')
return errors
}