Archive for mai, 2008

Pourquoi choisir la composition plutôt que l’héritage

Julien on mai 30th 2008

Un ami m'a récemment demandé si je pensais qu'il était acceptable d'avoir un héritage ayant une profondeur de 10 classes. Je ne pense pas que cela le soit. Si vous prenez une classe en bas de la hiérarchie, une modification sur n'importe laquelle des 9 classes supérieures peut introduire une régression. Cela peut rapidement devenir un sacré cauchemar à maintenir!

La plupart du temps, si vous avez une hiérarchie de ce type dans votre application, c'est que vous avez choisi de privilégier l'héritage sur la composition. C'est probablement aussi un "code-smell" indiquant que votre classe fait trop de chose. Afin d'éviter cela, laissez moi vous montrer comment il est possible d'étendre une classe sans utiliser l'héritage.

Partons du principe que nous commençons avec le code suivant. Je sais que c'est un example très basique, mais cela sera suffisant pour notre démonstration.

  1. abstract class TripBase
  2. {
  3. private readonly string _from;
  4. private readonly string _to;
  5.  
  6. public string From
  7. {
  8. get { return _from; }
  9. }
  10.  
  11. public string To
  12. {
  13. get { return _to; }
  14. }
  15.  
  16. protected TripBase(string from, string to)
  17. {
  18. _from = from;
  19. _to = to;
  20. }
  21.  
  22. public abstract DateTime CalculateEstimatedArrivalTime();
  23. }
  24.  
  25. class CarTrip : TripBase
  26. {
  27. public CarTrip(string from, string to)
  28. : base(from, to)
  29. {
  30. }
  31.  
  32. public override DateTime CalculateEstimatedArrivalTime()
  33. {
  34. return DateTime.Now.AddHours(15);
  35. }
  36. }
  37.  
  38. class PlaneTrip : TripBase
  39. {
  40. public PlaneTrip(string from, string to)
  41. : base(from, to)
  42. {
  43. }
  44.  
  45. public override DateTime CalculateEstimatedArrivalTime()
  46. {
  47. return DateTime.Now.AddHours(2);
  48. }
  49. }

Nous avons une classe abstraite, BaseTrip, et nous voulons l'étendre en ayant 2 sous-classes: CarTrip et PlaneTrip. Dans une application réelle, nous aurions un algorithme pour calculer ou récupérer le temps de transport. Il serait d'ailleurs probablement très différent pour chaque classe. Dans notre exemple, afin de simplifier, nous retournons juste une valeur codée en dur.

Ce code n'est pas mauvais, loin de là. Cependant il est également loin d'être parfait. Par exemple, sommes-nous sur que le calcul du temps de transport entre 2 villes est la responsabilité de nos classes CarTrip et PlaneTrip? En effet, si nous voulons ajouter une nouvelle classe MotocycleTrip qui aurait des propriétés différentes mais le même algorithme de calcul de temps de transport que CarTrip, nous sommes condamné à extraire une nouvelle super-classe abstraite que nous appelerions par exemple RoadTrip... Cela semble beaucoup de travail pour pas grand chose...

Voici une version refactorisée de ces classes, utilisant la composition plutôt que l'héritage:

  1. class Trip
  2. {
  3. private readonly string _from;
  4. private readonly string _to;
  5. private readonly ITransportationMode _transportationMode;
  6.  
  7. public string From
  8. {
  9. get { return _from; }
  10. }
  11.  
  12. public string To
  13. {
  14. get { return _to; }
  15. }
  16.  
  17. protected Trip(string from, string to, ITransportationMode transportationMode)
  18. {
  19. _from = from;
  20. _to = to;
  21. _transportationMode = transportationMode;
  22. }
  23.  
  24. public DateTime CalculateEstimatedArrivalTime()
  25. {
  26. return DateTime.Now.Add(_transportationMode.CalculateTransportationTimeBetween(_from, _to));
  27. }
  28. }
  29.  
  30. internal interface ITransportationMode
  31. {
  32. TimeSpan CalculateTransportationTimeBetween(string from, string to);
  33. }
  34.  
  35. class CarTransportationMode : ITransportationMode
  36. {
  37. public TimeSpan CalculateTransportationTimeBetween(string from, string to)
  38. {
  39. return new TimeSpan(15, 0, 0);
  40. }
  41. }
  42.  
  43. class PlaneTransportationMode : ITransportationMode
  44. {
  45. public TimeSpan CalculateTransportationTimeBetween(string from, string to)
  46. {
  47. return new TimeSpan(2, 0, 0);
  48. }
  49. }

En quelques mots, j'ai extrait les calculs dans leurs propres classes qui implémentent l'inferface ITransportationMode. Les instances sont ensuite injectées par le constructeur (mais nous pourrions également les injecter par un setter ou une méthode).

Cette implémentation améliore le code de plusieurs façons:

  • Le calcul du temps de transport est réalisé dans une nouvelle classe, cela ne pollue donc plus la classe Trip. Il est beaucoup plus facile d'ajouter de nouveaux algorithmes de calculs, il suffit d'ajouter de nouvelles implémentation de ITransportationMode. Il est donc possible de les faire évoluer indépendamment de la classe Trip, ce qu'on appelle en anglais la "Separation of Concerns" (honnêtement, je ne sais pas comment traduire ce terme!).
  • Nous pouvons maintenant configurer notre classe Trip. Avant cela, il était nécessaire de créer une nouvelle instance d'une sous-classe de TribBase pour modifier la façon dans le calcul était fait. Maintenant il nous suffit juste de changer l'instance de ITransportationMode tout en gardant le même objet Trip.
  • Nous pouvons avoir plusieurs sous-classes de Trip qui utiliseraient le même algorithme sans introduire une nouvelle classe abstraite (comme RoadTrip par exemple). En conséquent, la composition aide à garder nos hiérarchies de classes aussi plates que possible.

Filed in Alt.net Foundations | No responses yet

Le singleton nul

Julien on mai 27th 2008

Je le dirai honnêtement: je n'aime pas le pattern appelé singleton. Ce pattern était très utile il y a quelques années, avant l'émergence de techniques comme dependency injection (injection de dépendances) ou dependency lookup (recherche de dépendances). Malheureusement pour moi, ce pattern est probablement celui qui est le plus (mal!) utilisé dans le développement logiciel. Je l'admets volontiers, le singleton peut être utile dans de nombreux scénarios, mais sérieusement... pas partout! (Si vous n'avez aucune idée de ce dont je parle, le singleton est un pattern qui permet de s'assurer qu'il n'existera qu'une seule instance d'une classe donnée. Cette instance est créée sur demande, la première fois que la classe est utilisée)

Les principaux problèmes avec les singletons sont évidents:

  • Vous ne contrôlez pas le moment auquel l'instance de la classe est créée. Si vous utilisez beaucoup de singletons dans vos projets, vous n'aurez aucune idée de l'ordre dans lequel les objets sont créés. Ce n'est pas un gros problème pour les petits logiciels, mais c'est plus inquiétant lorsque vous avez quelques centaines de millier de lignes de code...
  • 99% du temps, l'implémentation utilisé pour le singleton nous lie à une classe spécifique. En conséquent, vous serez obligé de refactorisé le singleton ou d'utiliser des framewoks comme TypeMock si vous voulez tester votre code. (Vous pouvez également avoir un singleton qui offrira une point d'accès à la seule et unique instance d'une seconde classe en la castant en une interface. Problème: a moins que cette seconde classe soit aussi un singleton, rien ne garanti qu'il n'existe qu'une seule instance...)
  • Les dépendances ne sont pas évidentes (ce qui peut également être un problème avec l'utilisation du pattern dependency lookup). Si vous avez un type A qui utilise un singleton de type B, rien dans la signature de A ne fera apparaitre la dépendance au grand jour. Par exemple, ce peut être un problème si vous n'êtes pas au courant qu'utiliser A peut provoquer quelques dizaines d'appels à la base de données par l'intermédiaire de B!

Cependant, j'ai récemment découvert un pattern pire que le singleton: je l'appelle le singleton nul ! C'est un concept très simple: le singleton peut échouer à s'instancier si une dépendance (un fichier de config, une base de données, un object distant, etc) n'est pas disponible. Dans ce cas, l'instance retournée par le singleton peut être nul. Voyons un exemple:

  1. class NullSingleton
  2. {
  3. private static NullSingleton _instance;
  4.  
  5. public static NullSingleton Instance
  6. {
  7. get
  8. {
  9. if (_instance == null)
  10. {
  11. if (CanAccessMyDependency())
  12. {
  13. _instance = new NullSingleton();
  14. }
  15. }
  16.  
  17. return _instance;
  18. }
  19. }
  20.  
  21. private static bool CanAccessMyDependency()
  22. {
  23. // check if you can connect to the database for instance.
  24. }
  25.  
  26. private NullSingleton()
  27. {
  28. }
  29.  
  30. }

Qu'est ce qui est problématique avec cela?

  • Si la dépendance n'est pas disponible, le singleton nul vérifiera à chaque accès le statut de la dépendance. Ce sera très probablement très lent (généralement c'est un appel en dehors du process).
  • A chaque fois que vous accédez à l'instance du singleton, il faut vérifier si la référence n'est pas nulle. Ce qui ajoute un bruit considérable et force des traitements supplémentaires.
  • Enfin, c'est contraire aux attentes usuelles à propos des singletons (c'est à dire que la classe devrait toujours retourner 1 et 1 seule instance, et non pas 0 ou 1 instance). Le comportement n'est donc pas évident, ce qui provoquera des bugs!

Faites vous une faveur: n'utilisez pas un "singleton nul" :-)

Filed in Qualité du code | No responses yet

Resharper 4: la beta est disponible.

Julien on mai 22nd 2008

Après une longue période en EAP (Early Access program), la beta de reshaper 4 est enfin disponible!
(a priori la stabilité à l'air au rendez-vous, du moins je n'ai pas eu de problème significatif pour le moment).

On trouvera notamment au menu du jour le support du C# 3 et de Linq, de nouveaux refactorings, de nouvelles suggestions, etc. En résumé, que du bon si vous travaillez déjà sous Visual Studio 2008 et .NET 3.5.

Pour ceux qui ne connaissent pas Resharper, je vous suggère vivement de regarder ce site: 31 days of resharper et de télécharger la version d'essai. Je vous garanti que l'essayer c'est l'adopter!

Filed in .NET | 2 responses so far

Pourquoi j’utilise un préfixe pour mes membres privés

Julien on mai 21st 2008

J'utilise un préfixe (A savoir: "_") pour indiquer les membres privés de mes classes. Autrement dit, une de mes classes ressemblera à cela:

  1. public class Car
  2. {
  3. private string _model;
  4. private string _owner;
  5. [...]
  6. }

Différentes notations existent ici et là, notamment "m_". Mais de nombreuses personnes ont aussi choisi de ne simplement pas démarquer les membres privés des variables locales et autres paramètres. Je suis tout à fait conscient que c'est un sujet polémique, et que les avis sont très partagés. J'aimerai toutefois expliquer pourquoi je trouve personnellement qu'il est important d'avoir cette distinction.

En guise de référence, voici ce à quoi pourrait ressembler une classe sans préfixe:

  1. public class Transportation
  2. {
  3. private TransportStatus status;
  4.  
  5. public TransportStatus Status
  6. {
  7. get { return status; }
  8. }
  9.  
  10. public void ChangeTransportStatus(TransportStatus status)
  11. {
  12. this.status= status;
  13. DoSomeStuff();
  14. }
  15. }

Il y a plusieurs problèmes avec cette classe:

  • Utiliser un préfixe permet de se passer du mot clef "this", ce qui réduit le "bruit" dans les sources. En effet, écrire "this.model" revient en quelque sorte à utiliser un préfixe ("this."). Autant utiliser quelque chose de plus court du coup!
  • Qui plus est, l'utilisation de "this" est optionnelle en C#. On se retrouve rapidement avec un mélange des genres ou certains accès sont fait avec "this". et d'autres le sont sans aucun préfixe. Si on ne connait pas bien le code, on est obligé de constamment vérifier si telle variable est privée avec l'intellisense ou en naviguant dans la classe.
  • L'absence de préfixe provoque rapidement des confusions. Par exemple, dans la fonction ChangeTransportStatus, 2 variables ont le même nom: mon paramètre et mon membre privé. Le code étant ici très simple, il est difficile de se tromper. Dans une fonction de 50 lignes, il peut y avoir potentiellement beaucoup plus d'erreurs: il est courant d'utiliser le paramètre à la place du membre...
  • J'ai également une propriété "Status" qui ressemble énormément à mon membre privé "status" dans mon exemple. L'utilisation du mot clef "this" ne règle en rien la confusion potentielle. Sans préfixe il faut donc être très attentif à l'utilisation des majuscules. Sur certaines lettres la distinction est évidente, sur d'autres, elle l'est beaucoup moins! L'expérience montre que sans préfixe, le code d'une classe utilisera plus ou moins aléatoirement la propriété ou le membre privé en fonction de la rigueur et/ou de l'humeur des développeurs. Pour peu que la propriété encapsule un peu de logique, on se retrouve rapidement avec des effets de bord indésirable.
  • Enfin, et je vous accorde que c'est un argument très discutable mais les nouveaux outils de Microsoft génère le code avec des préfixes (voir Linq to Sql par exemple). Si eux le font, c'est peut être qu'il y a du bon ;-).

Alors, prêt à utiliser un préfixe? :-)

Filed in Qualité du code, Trucs et Astuces | One response so far

Alt.net: deuxième édition!

Julien on mai 13th 2008

Après la première rencontre Alt.net française, dont Symon Rottem à fait un excellent résumé sur son blog (en anglais), Robert Pickering a récemment annoncé l'organisation de la seconde rencontre!

Elle aura donc lieu le Mercredi 4 juin, dans le quartier des halles (le lieu exact restant à définir).

J'y serai évidemment et je vous invite à nous rejoindre nombreux. Comme lors de la première rencontre, il n'y a pas de sujets spécifiques de prévu, nous irons la ou les conversations nous emmènent :-). Pour ma part, j'ai eu l'occasion de découvrir quelques nouveaux frameworks récemment, comme xUnit, Moq, Asp.net MVC ou encore Unity, soit autant de sujets potentiels!

Edit: Rendez-vous le mercredi 4 juin à 20h00 au Café des Initiés, 3, Place des Deux Ecus, 75001!

Filed in Rencontres Alt.net | 5 responses so far