Code iOS : fini de jouer !
Les applications mobiles iOS font aujourd'hui partie de nos systèmes d'information. Pour assurer leur constante évolution et un Time To Market performant, le développement se doit d'être industrialisé mais surtout pérenne !
Cependant, nombre d'applications démarrées il y a quelques années souffrent aujourd'hui des mêmes symptômes :
- Maintenance rendue délicate car les concepteurs y ont fait leurs premiers pas en Objective-C
- Evolution compromise car elles ont vu se succéder autant de développeurs aux pratiques différentes qu'elles comptent d'écrans
- Régressions et manque de performances induits par des modifications rapides sans refactoring...
Chez OCTO, nous savons que faire un projet mobile de qualité n'est pas simple. Dans cet article, nous allons aborder certains points clés qui reviennent lors de nos audits d'applications iOS. Et si l'ergonomie la plus simple est souvent la meilleure garante d'une expérience utilisateur de qualité, vous verrez qu'elle l'est également en ce qui concerne la pérennité du code !
Simplifier l'appréhension globale du projet
Lorsque nous développons, nous nous basons sur notre IDE et ce que nous y lisons, le code, mais aussi l'organisation générale. Pour rendre la compréhension plus aisée, limiter le nombre d'informations à mémoriser et réduire la probabilités de faire des erreurs, une seule solution : simplifier cette lecture à l'extrême (Keep It Simple Stupid !).
Standardiser l'organisation des sources
L'efficacité d'une équipe de développement dépend de la connaissance qu'elle a de son projet. Ce n'est pas au développeur de pâtir d'une mauvaise organisation, c'est à lui de l'améliorer ! Dès l'ouverture du projet, la hiérarchie doit être évidente et mettre en évidence la séparation des responsabilités des classes.
Afin de simplifier la gestion des frameworks externes, nous recommandons d'utiliser CocoaPods qui permet de les externaliser dans un projet dépendant.
Aérer son code
Tout comme l'organisation des sources, la présentation du code permet d'influer sur sa clarté et sa compréhension. Une répartition par blocs logiques espacés permet une compréhension d'ensemble avant même d'avoir lu une ligne de code ! Un exemple simple permet de le montrer :
Dans le premier cas, notre cerveau reconnait immédiatement un poème, sait facilement extraire le contenu de chaque strophe. Il ne reste qu'à lire pour en comprendre le sens. Dans le deuxième cas, seuls les connaisseurs sauront identifier les Correspondances de Baudelaire. Ils devront d'ailleurs sans doute le relire plusieurs fois avant d'en comprendre le sens.
Cet effort de segmentation/présentation doit se retrouver dans :
- l'organisation des sources à l'aide de pragma ordonnés (dans les .h et .m)
- la séparation du code en méthodes unitaires courtes (moins de 20 lignes)
- la séparation du contenu de ces méthodes en blocs logiques
Notons que pour réaliser la décoration des pragmas, ainsi que pour toute action répétitive effectuée, nous utilisons des snippets Xcode.
Utiliser un format de code cohérent
Dans beaucoup de projets, le passage de nombreux développeurs est visible. En effet, ces derniers n'ont pas les mêmes pratiques : alignement, style d'accolades, espacements... Autant de facteurs qui freinent la bonne compréhension du code par d'autres développeurs.
Bonne nouvelle pour le formatage : il existe maintenant des solutions pour partager un même format dans une équipe et l'appliquer avec Xcode avec un simple raccourci clavier ! Nous avons par exemple développé Xcode Formatter qui se base sur l'outil uncrustify pour avoir un style de code cohérent.
Trouver les éléments rapidement
Les raccourcis claviers ⌘-Maj-F et ⌘-Maj-O sont très pratiques pour retrouver du code ou ouvrir une ressource dans son projet. Néanmoins, ils ne doivent pas être le seul moyen d'accéder à l'information !
Rendre les fichiers accessibles
Comme présenté précédemment, nous utilisons des groupes sur Xcode pour séparer les types de classes et les ressources. Lier chacun de ces groupes à un répertoire du même nom sur le système de fichiers permet de simplifier l'accès et la maintenance de ces données, notamment les images qui évoluent souvent.
Dans ces répertoires, il est important de garder une cohérence dans les fichiers. Une erreur que nous rencontrons souvent est de séparer les xib des classes controllers ou views auxquelles elles sont rattachées. Placer les fichiers xib à côté de leur classe référente et utiliser le même nom pour ces fichiers simplifie grandement leur recherche et édition.
Utiliser des méthodes et variables membres privées
Ou mettre les méthodes privées, les attributs privés, faut-il utiliser des ivars ? Nous proposons une pratique répandue pour laisser dans le .h uniquement les éléments publiques : définir les attributs et méthodes privés dans le .m. Ainsi :
- les méthodes et attributs publiques sont définis dans le .h
- les méthodes et attributs privés sont définis dans une extension dans le .m
Avec les différentes pratiques présentées précédemment, il est simple de comprendre l'organisation du projet, d'une source, savoir où se trouvent les éléments.
Rendre la lecture évidente
La complexité à appréhender un code n'est pas (souvent) reliée à la complexité de l'algorithme utilisé, mais bien à l'effort nécessaire pour en extraire la logique. Dans cette optique, tout code pouvant générer de l'incompréhension pour un nouveau développeur doit être supprimé ou refactoré.
Supprimer tout code mort !
Par code mort, nous entendons les implémentations par défaut non utilisées et les lignes de commentaires inutiles. Ces quelques lignes dont "on pourrait avoir besoin plus tard" pourront toujours être retrouvées via le gestionnaire de sources ou peuvent être transformées en FIXME ou TODO.
Eviter les "magic" number / string
Si l'ordre des sections d'une tableView est clair au moment de son développement, il le sera beaucoup moins lors d'une correction d'anomalie un mois plus tard ou pour un autre développeur. Il en est de même pour toutes les valeurs codées en dur et répétées dans une application qui doivent être présentées avec des constantes.
Ne pas afficher d'informations perturbatrices
Nous avons souvent noté une mauvaise utilisation des logs à des fins de debug, qui restent dans le code ensuite et sont visibles en production. Il s'agit de code mort qui doit être supprimé ! Il convient d'ailleurs de définir dans le fichier .pch de l'application une méthode de log qui ne sera utilisée qu'en mode DEBUG :
#ifdef DEBUG #define DLog(...) NSLog(__VA_ARGS__) #else #define DLog(...) /* */ #endif
Cette méthode ne doit être utilisée que temporairement ou pour fournir des logs d'information utiles au développeur qui parcoure l'application. Pour le debug, on préfèrera utiliser des breakpoints dynamiques ou entrer directement des commandes de debug dans la console.
Enfin, et c'est l'un des points les plus importants pour la qualité, le nombre de warnings et d'erreurs d'analyse statique doit être à 0 ! Garder des warnings non corrigés empêche de voir les erreurs ajoutées lors de modifications et pousse l'équipe à ne pas s'en soucier alors que c'est un point très important. Les warnings remontés sont définis dans l'onglet "Build Settings" de l'application. Certains valent d'ailleurs la peine d'être activés en plus de ceux initialement utilisés par Xcode (par exemple "Hidden Local Variables"). Ce site peut aider à choisir les différents flags à activer.
S'assurer que le code sera compris
Si la première priorité d'un développeur mobile est de livrer une application de qualité, la seconde est de produire un code fonctionnel compréhensible par n'importe quel autre développeur !
Adopter une nomenclature explicite et cohérente
Les noms de variables se doivent d'être cohérents et autoportants ! Pour cela, voici quelques règles qu'il est judicieux de respecter :
- ne pas utiliser d'abréviations
- indiquer le type de l'objet et sa fonction dans sa nomenclature
- respecter les conventions Objective-C (par exemple : "xxxDatasource" = objet qui répond à un protocole pour fournir des données à une vue != NSArray)
- définir ses propres conventions pour améliorer la lisibilité, par exemple nommer les booleans avec un verbe : "isXXX", "shouldXXX", "hasXXX"
De la même manière, les noms de méthodes doivent a minima posséder un verbe et un complément.
Le plus important n'est pas de reproduire les exemples donnés ici, mais bien d'être cohérent dans son application.
Créer des fichiers auto-documentés
Le premier niveau de documentation, c'est le code lui-même ! Nous avons déjà parlé de nomenclature. Dans un second temps, il est possible de séparer les longues instructions monolignes en plusieurs lignes courtes utilisant des variables intermédiaires.
De la même manière, les fichiers xib peuvent rapidement devenir délicats à maintenir si aucun effort n'est réalisé pour nommer les vues. On prendra donc systématiquement la bonne habitude de créer des xib autodocumentés.
Ecrire une documentation utile
La documentation n'a pas été inventée pour excuser les mauvaises pratiques de nomenclature ! Elle est nécessaire pour clarifier des comportements complexes et doit apporter de la valeur au lecteur. Il est conseillé de choisir un formalisme (les plus répandus sont Doxygen et Appledoc) et de s'y tenir.
Nous préconisons de documenter :
- l'entête des classes : expliquer ce que la classe réalise
- les méthodes dont le fonctionnement / l'action n'est pas évidente
- les blocs logiques de code lorsqu'ils sont courts (extraire une méthode si ces blocs excèdent une dizaine de lignes)
Si après refactor un code est toujours mal compris par un autre développeur, il doit être documenté.
Ne pas optimiser prématurément
Il est souvent tentant de prévoir des optimisations pour la mémoire, les performances d'affichage. Or ces optimisations sont souvent minimes mais complexifient grandement la compréhension du code. Nous conseillons de toujours développer la solution la plus simple, puis analyser ensuite les performances avant d'optimiser.
La meilleure optimisation du code réside dans sa mutualisation : éviter la duplication, la lecture et le maintien de fonctionnalités déjà écrites. Pour ce faire on utilisera à bon escient :
- les frameworks open-source existants comme AFNetworking, qui sont dédiés à des usages spécifiques, sont testés et maintenus par une communauté bien plus vaste que l'équipe de développement et offrent de très bonnes performances
- les catégories, par exemple UIImageView+AFNetworking qui permet de charger une image à partir d'une url
- la simple mutualisation de comportements dans des views ou controllers par héritage
Il faut garder à l'esprit que c'est le code le plus simple à relire et à comprendre qui doit prévaloir. Ainsi, il faut éviter le code générique tant qu'il n'est pas nécessaire.
Des règles à appliquer au quotidien
En appliquant les outils présentés précédemment, nos équipes projets se sont grandement améliorées. Le résultat en vaut la chandelle : une fois une culture de la qualité mise en place, les fonctionnalités sont développées, testées et - en cas d'erreur - corrigées bien plus rapidement. C'est un cercle vertueux que nous souhaitons voir sur chaque projet.
Néanmoins, ces pratiques nécessitent un effort pour être appliquées. Cet effort doit être continu et porté par tous les membres du projet. C'est la Boy Scout Rule : "Toujours laisser l'endroit dans un état meilleur que celui où vous l’avez trouvé". Le premier pas à franchir est de choisir de les suivre et de les afficher dans l'espace de travail :
Notre fiche récapitulative de bonnes pratiques ios
Dans un prochain article, nous aborderons comment mettre en place ces pratiques en accompagnant une équipe de développement : industrialisation, méthodes, rituels, etc. Mais également comment justifier et pousser cette culture de la qualité jusque dans les équipes métier et marketing.