Salta ai contenuti

Code Smells nello Sviluppo di XOOPS

I code smells sono indicatori di potenziali problemi nel codice. Non significano necessariamente che il codice sia rotto, ma suggeriscono aree che potrebbero beneficiare del refactoring.

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]
// Smell: Method does too much
function processArticleSubmission($data) {
// 100+ lines di validazione, salvataggio, notifica, etc.
}
// Solution: Extract into focused methods
function processArticleSubmission(array $data): Article
{
$this->validateInput($data);
$article = $this->createArticle($data);
$this->saveArticle($article);
$this->notifySubscribers($article);
return $article;
}
// Smell: Class does everything
class ArticleManager {
public function create() { ... }
public function delete() { ... }
public function sendEmail() { ... }
public function generatePDF() { ... }
public function exportToExcel() { ... }
public function validateUser() { ... }
public function checkPermissions() { ... }
// ... 50 more methods
}
// Solution: Split into focused classes
class ArticleService { ... }
class ArticleExporter { ... }
class ArticleNotifier { ... }
class PermissionChecker { ... }
// Smell: Too many parameters
function createArticle($title, $content, $author, $category, $tags, $status, $publishDate, $featured, $image) { ... }
// Solution: Use parameter object
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 { ... }
// Smell: Type checking with switch
function getDiscount($userType) {
switch ($userType) {
case 'regular':
return 0;
case 'premium':
return 10;
case 'vip':
return 20;
default:
return 0;
}
}
// Solution: Use polymorphism
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; }
}
// Smell: Fields only used in certain situations
class Article {
private $tempCalculatedScore;
public function search($terms) {
$this->tempCalculatedScore = $this->calculateScore($terms);
// ... use score
}
}
// Solution: Pass as parameter or return value
class Article {
public function getSearchScore(array $terms): float {
return $this->calculateScore($terms);
}
}
// Smell: One class changed for many different reasons
class Article {
public function save() { ... } // Database change
public function toJson() { ... } // API format change
public function validate() { ... } // Business rule change
public function render() { ... } // UI change
}
// Solution: Separate responsibilities
class Article { ... } // Domain object only
class ArticleRepository { public function save() { ... } }
class ArticleSerializer { public function toJson() { ... } }
class ArticleValidator { public function validate() { ... } }
// Smell: One change requires many file edits
// Changing date format requires editing:
// - ArticleController.php
// - ArticleView.php
// - ArticleAPI.php
// - ArticleExport.php
// Solution: Centralize
class DateFormatter {
public function format(DateTime $date): string {
return $date->format($this->config->get('date_format'));
}
}
// Smell: Unreachable or unused code
function processData($data) {
if (true) {
return $this->handleData($data);
}
// This never executes
return $this->legacyHandler($data);
}
// Old unused method still in codebase
function oldMethod() {
// Not called anywhere
}
// Solution: Remove dead code
function processData($data) {
return $this->handleData($data);
}
// Smell: Same logic in multiple places
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);
}
}
// Solution: Extract common behavior
trait ActiveRecordsTrait {
public function getActive(): array {
$criteria = new CriteriaCompo();
$criteria->add(new Criteria('status', 'active'));
return $this->getObjects($criteria);
}
}
// Smell: Method uses another object's data more than its own
class Invoice {
public function calculateTotal(Customer $customer) {
$total = 0;
foreach ($this->items as $item) {
$total += $item->price;
}
// Uses customer data extensively
if ($customer->isPremium()) {
$total *= (1 - $customer->getDiscountRate());
}
if ($customer->getCountry() === 'US') {
$total *= 1.08; // Tax
}
return $total;
}
}
// Solution: Move behavior to the object with the data
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;
}
}

Quando noti un code smell:

  1. Identifica - Quale smell è?
  2. Valuta - Quale è l’impatto?
  3. Pianifica - Quale tecnica di refactoring si applica?
  4. Testa - Assicurati che i test esistano prima del refactoring
  5. Effettua il Refactoring - Apporta piccoli cambiamenti incrementali
  6. Verifica - Esegui i test dopo ogni cambio
  • Clean Code Principles
  • Code Organization
  • Testing Best Practices