Revue SOLID
Auteur : Guerth Castro (github.com/GuerthCastro). Sous licence MIT.
Processus de revue
Lorsqu'un code est fourni pour examen :
- Identifiez le langage et la couche (entité de domaine C#, gestionnaire d'application, service Angular, composant React, etc.).
- Évaluez chacun des cinq principes indépendamment.
- Produisez un rapport structuré en utilisant le format ci-dessous.
- Classez les violations par sévérité : Critique (brise l'architecture) > Majeure (dégrade la maintenabilité) > Mineure (préférence de style).
- Fournissez une version refactorisée concrète pour chaque violation Critique ou Majeure.
N'alourdissez pas le rapport. Si un principe est véritablement satisfait, indiquez PASS et passez à la suite.
Les cinq principes
S : Principe de Responsabilité Unique
Une classe, une seule raison de changer.
Violations à rechercher :
- Une classe fait à la fois de l'accès aux données et de la logique métier.
- Un service envoie des e-mails, valide des données et écrit dans la base de données.
- Un composant récupère des données, les formate et gère l'état de l'interface utilisateur.
- Un gestionnaire orchestre plusieurs opérations non liées.
- Classes god classe avec dix méthodes ou plus couvrant plusieurs domaines.
VIOLATION. OrderService valide, persiste et notifie. L'appel de notification est celui qui
n'a pas sa place.
public class OrderService(IOrderRepository repository, IEmailService email)
{
public async Task<bool> AddItem(long orderId, long productId)
{
Order order = await repository.GetById(orderId);
if (order.ItemCount >= order.MaxItems)
{
return false;
}
await repository.AddItem(orderId, productId);
await email.SendOrderUpdated(orderId);
return true;
}
}
Correctif : extraire un service de notification et laisser le gestionnaire coordonner les deux appels.
O : Principe Ouvert/Fermé
Ouvert à l'extension, fermé à la modification.
Violations à rechercher :
- Chaînes
if/else ifou blocsswitchsur des chaînes de type ou des énumérations qui s'allongent à chaque fonctionnalité. - Ajouter un nouveau type de rapport nécessite de modifier le service de rapport existant.
- Ajouter une nouvelle méthode de paiement nécessite de modifier le gestionnaire de commandes.
- Dépendances concrètes là où une abstraction conviendrait.
VIOLATION. Un nouveau format d'exportation signifie modifier cette méthode.
public byte[] ExportInvoice(string format, IEnumerable<InvoiceLine> lines)
{
if (format == "pdf")
{
return GeneratePdf(lines);
}
if (format == "excel")
{
return GenerateExcel(lines);
}
throw new NotSupportedException(format);
}
Correctif : une abstraction IInvoiceExporter avec les implémentations PdfInvoiceExporter et ExcelInvoiceExporter,
résolues par clé depuis le conteneur.
L : Principe de Substitution de Liskov
Les sous-types doivent être substituables à leurs types de base.
Violations à rechercher :
- Une classe dérivée lève
NotImplementedExceptionpour un membre hérité. - Une substitution modifie le comportement attendu : une opération silencieuse sans effet, une forme de retour différente, un nouveau type d'exception.
- Une interface déclare des membres que certaines implémentations ne peuvent pas implémenter de manière significative.
- Cast vers un type concret pour accéder à un comportement :
if (repository is SqlOrderRepository sql).
I : Principe de Ségrégation des Interfaces
Aucune classe ne devrait être forcée d'implémenter des membres qu'elle n'utilise pas.
Violations à rechercher :
- Interfaces obèses avec quinze membres ou plus dont les implémentations n'en utilisent que trois.
- Un
IRepository<T>générique forçantGetAll,GetById,Create,Update,Delete,Paginate,SearchetBulkInsertsur chaque entité. - Une interface de service mélangeant lectures et écritures alors que les consommateurs n'ont besoin que d'un seul côté.
VIOLATION. Un consommateur de rapports en lecture seule est forcé de voir Create, Update et Delete.
public interface IOrderRepository
{
Task<Order> GetById(long id);
Task<IEnumerable<Order>> GetAll();
Task Create(Order order);
Task Update(Order order);
Task Delete(long id);
}
Correctif : diviser en IOrderReader et IOrderWriter. Les composer là où les deux sont nécessaires.
D : Principe d'Inversion des Dépendances
Dépendre des abstractions, pas des concrétions.
Violations à rechercher :
newinstanciant un service ou un dépôt à l'intérieur de la logique métier.- Une dépendance directe à
SqlConnectionouNpgsqlConnectionà l'intérieur d'un service. - La couche Application important des types Infrastructure.
- Classes de service statiques contenant un état mutable.
VIOLATION. Le gestionnaire construit son propre dépôt et sa propre connexion concrets.
public class GetOrderHandler
{
public async Task<OrderDto> Handle(long id)
{
OrderRepository repository = new(new SqlConnection("..."));
Order order = await repository.GetById(id);
return order.MapToDto();
}
}
Correctif : injecter IOrderRepository via le constructeur principal et l'enregistrer dans le
conteneur à la racine de composition.
Format du rapport
Produire cette structure pour chaque revue :
## Revue SOLID : [NomClasse ou nomfichier]
**Langage/Couche :** Gestionnaire d'application C#, Service Angular, etc.
### S : Responsabilité Unique
Statut : PASS / VIOLATION (Critique, Majeure, Mineure)
Constat : [ce que la classe fait au-delà de sa responsabilité unique]
Correctif : [action concrète]
### O : Ouvert/Fermé
Statut : PASS / VIOLATION
Constat : ...
Correctif : ...
### L : Substitution de Liskov
Statut : PASS / VIOLATION / N/A (pas d'héritage)
Constat : ...
### I : Ségrégation des Interfaces
Statut : PASS / VIOLATION / N/A (pas d'interface)
Constat : ...
### D : Inversion des Dépendances
Statut : PASS / VIOLATION
Constat : ...
Correctif : ...
### Violations prioritaires
1. [Critique] DIP : le gestionnaire instancie un dépôt concret
2. [Majeure] SRP : le service envoie également l'e-mail de notification
### Version refactorisée
[bloc de code complet corrigé]
Violations de la stratification Clean Architecture
Au-delà de SOLID, signaler ces problèmes transversaux :
- Une entité de domaine importe des types d'Application ou d'Infrastructure (violation de la direction des dépendances).
- Un contrôleur contient de la logique métier qui appartient à un gestionnaire.
- Un dépôt retourne des DTO au lieu d'entités de domaine. Le mappage appartient à la couche Application.
- L'Infrastructure appelle une API externe directement au lieu d'implémenter une interface déclarée dans Application ou Domaine.
- Un gestionnaire touche à
HttpContext, faisant fuiter une préoccupation d'infrastructure dans la couche application. - Un enregistrement DI dans le mauvais assembly, forçant une couche à référencer le mauvais assembly pour compiler.