Les bonnes résolutions de début d'année peuvent s'émousser quand mai arrive... C’est donc le bon moment de rappeler quelques bonnes pratiques sous Xamarin.Forms et MAUI !
Il est vrai qu’il n’y a pas de “bible” dont les “saintes écritures” permettraient d’édicter des lois normatives absolues. Et c’est peut-être une bonne chose d’ailleurs, car figer la connaissance s’oppose au progrès par essence. Les “bonnes pratiques” sont ainsi plutôt des conseils parfois avisés, parfois guidés par des méthodes ou modes et n’ont donc rien d’impératif. Il reste néanmoins intéressant d’en connaître et d’en appliquer certaines. Au moins pour une bonne raison : créer de la cohérence dans votre code. Quand vous suivez une règle d’écriture votre code est facile à relire, les erreurs ressortent plus facilement. Au passage on gagne souvent un peu car les “bonnes pratiques” sont au pire sans effet au mieux pourvoyeuses d’avantages. Il ne faut pas les confondre avec les design patterns (patrons de conception) qui ne sont pas plus absolus mais dont les fondements sont généralement beaucoup plus solides et documentés et dont l’objet est plus souvent architectural. Les bonnes pratiques restent plus en surface et beaucoup ne concernent que l’écriture du code.
Soyons clairs, le but de cet article n’est pas d’être exhaustif, mais plutôt de rappeler l’existence des bonnes pratiques et l’intérêt d’en suivre certaines. J’espère aussi vous donnez ici envie d’aller fouiller le Web pour en trouver d’autres, les soupeser, vous faire votre avis, et en adopter de nouvelles.
Ce sont les petites choses auxquelles penser et à faire quand on écrit du code. Elles apportent du confort, systématisent des actions pour les rendre plus claires, bref il y a de tout, et a liste qui suit n’est qu’une poignée d’exemples pour vous inciter à en trouver d’autres afin d’adapter des pratiques utiles mais qui collent aussi avec votre “style” d’écriture de code.
XAML n’est pas forcément compilé, quand il l’est l’avantage est de lever des loups avant même l’exécution de l’app… Pour ce sujet je vous renvoie à un papier récent que j’ai écrit “
La compilation XAML”.
Le Code Behind et ses limites
Le code behind, le code derrière une entité Xaml, n’a pas tous les droits lorsqu’on développe selon MVVM.
Ces limitations sont nombreuses et mieux vaut lister ce qui est autorisé !
- Créer l’instance du ViewModel dans le Constructeur
- Assigner le DataContext généralement au ViewModel
- Appeler la méthode d’initialisation du ViewModel s’il y en a une
- Gérer le cycle de vie (OnAppearing, OnDisappearing)
- Traiter des problèmes d’UI purs (adapter la taille d’un objet selon la résolution par exemple)
Tout le reste est interdit, comme ça c’est facile à retenir !
Initialisation des ViewModels
Selon les recommandations de Microsoft et afin de simplifier l’appel des méthodes async (ce qui est interdit dans un constructeur), les ViewModels devraient proposer systématiquement une méthode Initialize() à appeler après la création de l’instance (voir ci-avant). Le mieux est de créer une interface exposant cette méthode et de faire descendre tous les ViewModels de l’app d’un ancêtre qui implémente cette interface. Cela permet aux View d’instancier leur ViewModel et d’appeler leur méthode d’initialisation avec un maximum de découplage (la variable recevant l’instance étant bien entendu déclarée avec le type de l’interface et non celui du ViewModel).
Nommer les ViewModels
En dehors de les classer correctement dans un répertoire isolé (comme les vues), leur nom doit être systématiquement celui de la Vue avec le suffixe “ViewModel”. C’est une convention qui permet facilement de retrouver les ViewModels, voir d’appliquer des méthodes de couplage automatique entre les Vues et leurs ViewModels (il suffit de créer une instance qui porte le même nom que la Vue+”ViewModel”).
Utiliser les commandes plutôt les gestionnaires d’événement des objets XAML
// à ne pas faire
<button Text="Diviser par 2" Clicked="onClick" />
// ok, géré par le ViewModel
<button Text="Diviser par 2" ClickedCommand="{Binding DivideBy2Command}"
Si on regarde ces deux bouts de code on comprend que dans un cas on demande au code behind de la vue de traiter le click alors que dans le second cas c’est le ViewModel qui s’en charge. Ici il ne s’agit pas de stylistique mais bien d’une obligation relative à MVVM.
Dans le Xamarin Community Toolkit on trouve notamment un Behavior qui transforme un Event en une commande. Cela s’avère très pratique car seuls les Clics ou équivalents ont une commande associée dans les objets XAML. Beaucoup d’objets offrent des événements supplémentaires qui n’ont pas forcément de commande associée. Grâce à ce Behavior tout évènement peut être transformer en appel de commande dans le ViewModel.
Nommer les commandes
Au passage on notera que les commandes se terminent par le suffixe “Command” comme dans l’exemple précédent.
Utiliser des Collections observables avec les ListView et apparentés
On utilise des Observable Collections avec tous les objets listes pour une bonne raison : ces collections spéciales ont été conçues pour cet usage et ajoute des événements qui préviennent de l’ajout de la suppression d’un élément de la liste (ou si la liste entière est rafraichie), ce qui permet à l’objet visuel de se mettre à jour automatiquement. Malheureusement il n’existe pas d’événement prévenant quand un élément voit son contenu modifié. Ce cas doit être géré par le développeur.
Appels aux méthodes Async
// Mauvais
public PointsListView()
{
viewModel =
new PointListViewModel(pointService, Device, SelectedModule, ObjectType);
Task.Run(async()=> await viewModel.PopulateMenuOptions());
}
// Correct
public PointsListView()
{
}
protected override async void OnAppearing()
{
viewModel =
new PointListViewModel(pointService, Device, SelectedModule, ObjectType);
await viewModel.PopulateMenuOptions();
}
Dans l’exemple de code ci-dessus on voit le constructeur d’une Vue faire un appel à une méthode Async de son ViewModel après avoir crée l’instance. On voit aussi la façon correct d’implémenter un tel appel en utilisant OnAppearing à la place du constructeur qui ici reste vide.
En dehors du faire que l’Async est interdit dans le constructeur (ce qui oblige d’ailleurs dans le mauvais code à bloquer le thread principal avec un Task.Run() !) le constructeur d’une Vue doit s’intéresser uniquement à l’initialisation graphique de celle-ci.
Les mauvaises pratiques
Il est difficile là aussi de les classer car nous avons vu que les bonnes pratiques venaient en réponse à de mauvaises. Et les répéter ici n’aurait pas d’intérêt. On peut toutefois en citer quelques unes qui n’ont pas été évoquées plus haut.
Une Vue, Un ViewModel
C’est une peu la “manif pour tous” du développeur : un ViewModel ne doit être relié qu’à une seule Vue. Techniquement plusieurs Vue pourraient se liée en même temps au même ViewModel, par exemple pour présenter les mêmes données de façon différentes. Il y a des donc des cas limites où cela peut se faire, lorsque le ViewModel ne fait aucun traitement mais juste de l’adaptation des données du Model pour alimenter les Vues. D’ailleurs MVVM permet même de lier dans un cas directement les Vues au Model sans passer par un ViewModel. Pour de l’affichage pur c’est possible. Mais dès qu’il y a traitement il faut un ViewModel et que celui-ci ne s’occupe que d’une seule Vue à la fois.
On pourra lire à ce sujet “Connecting View Models to Views”.
Séparer les Templates XAML de App.xaml
Les Templpates XAML doivent être rangés dans des fichiers XAML uniques et bien identifiés. Ceci permet notamment d’avoir toute la série de fichiers habituels avec leur nommage habituel (toto.xaml, toto.xaml.cs, totoViewModel.cs). Mais la raison principale est que le App.Xaml devient très vitre engorgé de toutes ces déclarations, il devient pénible à manipuler, à faire évoluer, à déboguer etc.
On ne passe pas les plats !
Pour plein de bonnes raisons il faut s’interdire de passer aussi bien les instances de Vues que de ViewModels à d’autres pages ou d’autres ViewModels. Si le besoin s’en faire sentir c’est que vous devez revoir l’architecture de votre code…
Une chose et pas son contraire
Pour les ViewModel on conseille fortement de fabriquer leur nom à partir de celui de la Vue avec le suffixe “ViewModel”, cela fait partie des bonnes pratiques listées plus haut.
Et bien pour les Vue il ne faut surtout pas ajouter '”View” à la fin de leur nom !
Ce qui est bon dans un cas ne l’est pas dans l’autre, ici il ne s’agit pas d’enfumage façon “pensée complexe”, mais de raisons pratiques bien identifiées et raisonnables. Par exemple le nom du ViewModel d’une Vue appelée “HorlogeView.xaml” deviendrait “HorlogeViewViewModel”. Le bégaiement de “View” n’apporte rien et gêne la lecture. Si on ne devait retenir qu’une raison ça serait celle-là… (d’autant du fait qu’une Vue doit déjà posséder un nom explicite qui suppose que c’est une Vue).
Pas de suffixe Async
On est ici dans le même type de problème : Ne pas ajouter le suffixe “Async” aux méthodes Async… Pourquoi ? Microsoft que le déconseille car c’est facile de l’oublier et de créer tout un tas de méthodes aux noms incohérents et trompeurs. C’es bête comme raison mais c’est fondé. De toute façon, sauf à être maso et utilise le bloc-notes pour saisir du code C#, Intellisense dans Visual Studio vous informera de l’appel à une méthode Async (s’il manque la déclaration, s’il manque le await, etc). Les bonnes pratiques imposent aussi de plus en plus à coder le maximum de méthodes en asynchrone pour la fluidité des apps.
Conclusion
Comme je le faisais remarquer en introduction, loin est mon intention de faire de cet article un livre sur les bonnes pratiques ! Dans l’espace d’un papier je peux en revanche tenter de vous rappeler qu’appliquer les bonnes pratiques comporte de nombreux avantages… et vous donner l’envie d’aller chercher d’autres références sur le Web pour enrichir votre liste personnelle de bonnes pratiques. Celles qui vous seront utiles dans votre contexte et celles que vous saurez appliquer avec rigueur et pas une fois de temps en temps.
Le sujet n’est pas juste vaste, c’est un puit sans fond ! Vous appâter et vous inciter restera donc le but fort modeste de cet article !
Stay Tuned !