XOOPS開発のコードスメル
コードスメルはコード内の潜在的な問題の指標です。これは必ずしもコードが壊れていることを意味しませんが、リファクタリングの恩恵を受ける可能性のある領域を示唆しています。
一般的なコードスメル
Section titled “一般的なコードスメル”flowchart TD A[Code Smell Detected] --> B{Type?} B --> C[Bloaters] B --> D[Object-Orientation Abusers] B --> E[Change Preventers] B --> F[Dispensables] B --> G[Couplers]
C --> C1[Long Method] C --> C2[Large Class] C --> C3[Long Parameter List]
D --> D1[Switch Statements] D --> D2[Temporary Field]
E --> E1[Divergent Change] E --> E2[Shotgun Surgery]
F --> F1[Dead Code] F --> F2[Duplicate Code]
G --> G1[Feature Envy] G --> G2[Inappropriate Intimacy]ブロッカー(膨張型スメル)
Section titled “ブロッカー(膨張型スメル)”長いメソッド
Section titled “長いメソッド”// スメル: メソッドが多くのことをしているfunction processArticleSubmission($data) { // 100行以上の検証、保存、通知など}
// 解決策: フォーカスされたメソッドに抽出function processArticleSubmission(array $data): Article{ $this->validateInput($data); $article = $this->createArticle($data); $this->saveArticle($article); $this->notifySubscribers($article); return $article;}大きなクラス(神クラス)
Section titled “大きなクラス(神クラス)”// スメル: クラスが全てをしているclass ArticleManager { public function create() { ... } public function delete() { ... } public function sendEmail() { ... } public function generatePDF() { ... } public function exportToExcel() { ... } public function validateUser() { ... } public function checkPermissions() { ... } // ... さらに50個のメソッド}
// 解決策: フォーカスされたクラスに分割class ArticleService { ... }class ArticleExporter { ... }class ArticleNotifier { ... }class PermissionChecker { ... }長いパラメーターリスト
Section titled “長いパラメーターリスト”// スメル: パラメーターが多すぎるfunction createArticle($title, $content, $author, $category, $tags, $status, $publishDate, $featured, $image) { ... }
// 解決策: パラメーターオブジェクトを使用class CreateArticleCommand { public string $title; public string $content; public int $authorId; public int $categoryId; public array $tags; public string $status; public ?DateTime $publishDate; public bool $featured; public ?string $image;}
function createArticle(CreateArticleCommand $command): Article { ... }オブジェクト指向の濫用
Section titled “オブジェクト指向の濫用”Switch文
Section titled “Switch文”// スメル: Switch文による型チェックfunction getDiscount($userType) { switch ($userType) { case 'regular': return 0; case 'premium': return 10; case 'vip': return 20; default: return 0; }}
// 解決策: ポリモーフィズムを使用interface UserType { public function getDiscount(): int;}
class RegularUser implements UserType { public function getDiscount(): int { return 0; }}
class PremiumUser implements UserType { public function getDiscount(): int { return 10; }}
class VipUser implements UserType { public function getDiscount(): int { return 20; }}一時的なフィールド
Section titled “一時的なフィールド”// スメル: 特定の状況でのみ使用されるフィールドclass Article { private $tempCalculatedScore;
public function search($terms) { $this->tempCalculatedScore = $this->calculateScore($terms); // ... スコアを使用 }}
// 解決策: パラメーターまたは戻り値として渡すclass Article { public function getSearchScore(array $terms): float { return $this->calculateScore($terms); }}変更を防ぐもの
Section titled “変更を防ぐもの”発散した変更
Section titled “発散した変更”// スメル: 1つのクラスが多くの異なる理由で変更されるclass Article { public function save() { ... } // データベース変更 public function toJson() { ... } // API形式変更 public function validate() { ... } // ビジネスルール変更 public function render() { ... } // UI変更}
// 解決策: 責任を分離class Article { ... } // ドメインオブジェクトのみclass ArticleRepository { public function save() { ... } }class ArticleSerializer { public function toJson() { ... } }class ArticleValidator { public function validate() { ... } }ショットガン手術
Section titled “ショットガン手術”// スメル: 1つの変更で多くのファイルを編集する必要// 日付形式の変更には以下を編集が必要:// - ArticleController.php// - ArticleView.php// - ArticleAPI.php// - ArticleExport.php
// 解決策: 一元化class DateFormatter { public function format(DateTime $date): string { return $date->format($this->config->get('date_format')); }}除去可能なもの
Section titled “除去可能なもの”デッドコード
Section titled “デッドコード”// スメル: 到達不可能なコードまたは未使用コードfunction processData($data) { if (true) { return $this->handleData($data); } // これは実行されない return $this->legacyHandler($data);}
// まだコードベースにある未使用メソッドfunction oldMethod() { // どこからも呼び出されていない}
// 解決策: デッドコードを削除function processData($data) { return $this->handleData($data);}// スメル: 複数の場所に同じロジックclass ArticleHandler { public function getActive() { $criteria = new CriteriaCompo(); $criteria->add(new Criteria('status', 'active')); return $this->getObjects($criteria); }}
class NewsHandler { public function getActive() { $criteria = new CriteriaCompo(); $criteria->add(new Criteria('status', 'active')); return $this->getObjects($criteria); }}
// 解決策: 共通の振る舞いを抽出trait ActiveRecordsTrait { public function getActive(): array { $criteria = new CriteriaCompo(); $criteria->add(new Criteria('status', 'active')); return $this->getObjects($criteria); }}カップラー(結合型スメル)
Section titled “カップラー(結合型スメル)”フィーチャーエンビー
Section titled “フィーチャーエンビー”// スメル: メソッドが別のオブジェクトのデータを自分のデータより多く使用class Invoice { public function calculateTotal(Customer $customer) { $total = 0; foreach ($this->items as $item) { $total += $item->price; } // 顧客データを広く使用 if ($customer->isPremium()) { $total *= (1 - $customer->getDiscountRate()); } if ($customer->getCountry() === 'US') { $total *= 1.08; // 税 } return $total; }}
// 解決策: 振る舞いをデータを持つオブジェクトに移動class Customer { public function applyDiscount(float $amount): float { return $this->isPremium() ? $amount * (1 - $this->discountRate) : $amount; }
public function applyTax(float $amount): float { return $this->country === 'US' ? $amount * 1.08 : $amount; }}リファクタリングチェックリスト
Section titled “リファクタリングチェックリスト”コードスメルを見つけたときは:
- 識別 - それはどのスメルか?
- 評価 - 影響の深刻さは?
- 計画 - どのリファクタリング技術が適用される?
- テスト - リファクタリング前にテストが存在するか確認
- リファクタリング - 小さく段階的な変更を加える
- 検証 - 各変更後にテストを実行
関連ドキュメント
Section titled “関連ドキュメント”- クリーンコード原則
- コード組織
- テストのベストプラクティス