Checklist de revue de code Android : de l’architecture à la publication

Un bug détecté lors de la revue de code coûte quelques minutes. Le même bug découvert après le déploiement sur le Play Store affecte votre taux d’utilisateurs sans plantage, votre classement dans le magasin et parfois votre week-end. Google réduit la visibilité de toute application qui dépasse un taux de plantage perçu par l’utilisateur de 1,09 %, faisant de la revue le dernier point de contrôle discret avant que cette exposition ne devienne publique.

Cette checklist de revue de code Android est celle que nos réviseurs utilisent lorsqu’un client ouvre une pull request sur une base de code Android. Elle formalise les bonnes pratiques de revue de code que notre équipe a affinées lors des audits d’applications en production. Une checklist de revue de code solide commence par des principes universels tels que l’architecture en couches, la dénomination et la couverture des tests. Sur Android, ces principes rencontrent un ensemble spécifique de modes d’échec liés au cycle de vie, au threading et au chemin de publication sur le Play Store.

Avant le début de la revue

Une revue de code Android productive commence avant que quiconque n’ouvre le code. Le réviseur a besoin de contexte, d’une compilation propre et d’une portée gérable. Nous confirmons trois points au préalable :

  • Portée et objectif. La correction d’un bug, une branche de fonctionnalité, un refactoring et un audit de candidat à la publication méritent chacun un angle différent. Nous demandons à l’auteur ce qui a changé, quels risques cela comporte et quelle est la priorité.
  • CI est au vert. Lint, Detekt, Ktlint et les tests unitaires doivent passer avant qu’un humain ne touche au code.
  • Le jeu de modifications est révisable. L’étude classique de Cisco sur la revue de code par les pairs a montré que la détection des défauts chute nettement au-delà de 400 lignes par session. Une revue de 3 000 lignes devient une simple formalité.

Nous lisons également d’abord le ticket et les messages de commit. La moitié du bruit dans la plupart des revues provient des réviseurs qui demandent « pourquoi ? » alors que la réponse se trouvait dans Jira.

Checklist de revue de code Android : de l’architecture à la publication

Revue de l'architecture

L’architecture est la couche où les petites erreurs se transforment en réécritures sur plusieurs trimestres. La plupart des problèmes que nous trouvons lors des audits clients remontent à l’un de ces trois schémas : la logique métier qui s’infiltre dans l’interface utilisateur, l’injection de dépendances contournée ou un “ViewModel” qui est tranquillement devenu un objet tout-puissant.

Séparation des couches

La logique de domaine appartient aux cas d’utilisation ou aux intermédiaires. Activity, Fragment et les fonctions composables doivent rester exemptes de règles métier. Les dépôts gèrent les sources de données, et les ViewModels coordonnent l’état. Une activité qui appelle directement Retrofit devient toujours un candidat au refactoring.

Injection de dépendances

Hilt ou Koin doivent gérer le graphe. L’instanciation manuelle des dépendances à l’intérieur des classes de production est un signal d’alarme, en particulier pour tout ce qui doit être testé isolément. Une DI centralisée rend également la modernisation du code hérité moins coûteuse par la suite, car le remplacement des implémentations devient un changement au niveau du module au lieu d’une chasse dans toute la base de code.

Drapeaux rouges de modularisation

Les modules de fonctionnalités doivent dépendre de :core et :domain. Les dépendances inter-fonctionnalités transitent par une couche partagée. Les dépendances circulaires devraient faire échouer la compilation. Nous signalons également les anti-modèles architecturaux que nous observons le plus souvent dans les revues de code assistées par IA : des composables qui se compilent mais violent le contrat de stabilité de Compose, des dépôts qui contournent l’architecture en couches, et des ViewModels qui absorbent la logique que la couche des cas d’utilisation devrait posséder.

Idiomes Kotlin dans le code Android

Kotlin offre aux réviseurs des outils que Java n’avait pas. La plupart des bases de code Android modernes mélangent les deux langages, surtout après une migration. Pour la partie Java d’une base de code mixte, notre liste de contrôle pour la revue de code Java couvre les vérifications spécifiques au langage. Ce qui suit cible les modèles Kotlin qui recoupent le cycle de vie Android et le multithreading.

Nous recherchons ces idiomes :

  • val par défaut, var uniquement lorsque l’état mute réellement
  • Sécurité contre les null sans !!. Les appels sécurisés, l’opérateur Elvis et requireNotNull avec un message significatif sont les valeurs par défaut
  • Classes scellées ou interfaces scellées pour l’état de l’interface utilisateur. Une data class avec sept champs nullables est un candidat à la refactorisation
  • Fonctions de portée utilisées pour clarifier l’intention plutôt que pour imbriquer quatre niveaux de profondeur
  • Collections choisies en fonction du modèle d’accès. Un balayage linéaire d’une List à l’intérieur d’une boucle coûteuse devrait être un Set

Un exemple précis du modèle que nous voulons voir :

sealed interface OrderListUiState {
    data object Loading : OrderListUiState
    data class Ready(val orders: List) : OrderListUiState
    data class Error(val message: String) : OrderListUiState
}

class OrderListViewModel(
    private val repository: OrderRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(OrderListUiState.Loading)
    val uiState: StateFlow = _uiState.asStateFlow()

    fun refresh() {
        viewModelScope.launch {
            _uiState.value = runCatching { repository.getOrders() }
                .fold(
                    onSuccess = { OrderListUiState.Ready(it) },
                    onFailure = { OrderListUiState.Error(it.message.orEmpty()) }
                )
        }
    }
}

L’équivalent hérité utilise généralement LiveData<Order>?, une chaîne d’erreur nullable et un !! sur value. Le travail du réviseur est de détecter ce modèle avant qu’il ne soit fusionné.

Revue de Jetpack Compose

Compose a changé ce qu’un réviseur recherche. Les modèles XML et Fragment sont toujours pertinents pour les anciens écrans, mais le nouveau code met l’accent sur le coût de recomposition, le hoisting d’état et la gestion des effets secondaires.

Pour le nouveau code Compose, nous vérifions :

  • L’état est remonté au bon niveau. Un TextField ne devrait pas posséder son propre état si l’écran parent en a besoin
  • Aucune logique métier dans les fonctions @Composable. Les Composables décrivent l’interface utilisateur étant donné un état. Ils n’appellent pas de dépôts et n’effectuent pas d’E/S
  • LaunchedEffect, DisposableEffect et SideEffect sont utilisés correctement. Une coroutine lancée dans le corps d’un composable sans LaunchedEffect fuit lors de la prochaine recomposition
  • remember et derivedStateOf protègent les calculs coûteux de la recomputation
  • LazyColumn et LazyRow utilisent des clés stables. Un LazyColumn sans key = { it.id } réorganise les composables à chaque changement de données
  • Des aperçus existent pour les états de chargement, de succès et d’erreur

Pour les écrans basés sur XML, la règle reste inchangée : ViewBinding au lieu de findViewById, des mises en page maintenues plates, aucune logique métier dans onCreateView.

Revue du travail asynchrone

Les bogues asynchrones sont la classe de défauts Android la plus coûteuse que nous rencontrons lors des audits. Ils sont intermittents en assurance qualité, déterministes en production et fréquemment invisibles dans les journaux. Le travail du réviseur est de les faire apparaître au moment de la revue.

Ce que nous recherchons :

  • Aucun GlobalScope dans le code de production. viewModelScope, lifecycleScope, ou un scope injecté lié à un composant Hilt sont les bons choix
  • Les Dispatchers correspondent au travail et sont injectés afin que les tests puissent les substituer
  • L’annulation est respectée. Le travail de longue durée utilise des API de suspension annulables ou vérifie isActive dans les boucles
  • Aucun runBlocking sur le fil principal. Jamais
  • StateFlow pour l’état de l’interface utilisateur, SharedFlow pour les événements uniques, Flow froid pour les lectures de dépôt
  • Les exceptions traversent les limites des coroutines en toute sécurité. CoroutineExceptionHandler et supervisorScope sont des choix délibérés que le réviseur doit voir utilisés intentionnellement

Un réviseur qui peut lire viewModelScope.launch(Dispatchers.IO) { repository.fetch() } et repérer les quatre problèmes qu’il contient vaut la peine d’être conservé pour chaque version.

Revue de la mémoire et des performances

Les problèmes de performance se cachent dans de petites erreurs. Les fuites de mémoire se cachent dans des endroits qui semblent corrects isolément. Un réviseur expérimenté lit le code avec un profileur en tête.

Fuites de cycle de vie

Les objets de longue durée, tels que les singletons, les dépôts à l’échelle de l’application ou les champs statiques, ne doivent jamais détenir de référence à une Activity, un Fragment ou une View. Les classes internes au sein des propriétaires de cycle de vie sont un délinquant fréquent. Tout ce que vous enregistrez dans un callback de cycle de vie doit également être désenregistré lorsque ce cycle de vie se termine.

Annulation de ViewBinding

Dans les Fragments, la définition de _binding = null dans onDestroyView est obligatoire. Ce seul modèle empêche la fuite de mémoire la plus courante dans le code Android moderne. Le réviseur devrait le voir sur chaque Fragment qui utilise ViewBinding, sans exception.

Nous vérifions également le reste de la couche de performance dans la prose. Le chargement d’images doit utiliser une vraie bibliothèque (Coil, Glide, Picasso) plutôt qu’un décodeur fait maison qui bloque le fil principal. Les adaptateurs RecyclerView doivent utiliser DiffUtil ou ListAdapter au lieu de notifyDataSetChanged(). LeakCanary appartient à chaque build de débogage, et toute fuite ouverte qu’il révèle est corrigée avant l’expédition du changement.

Revue de sécurité

La sécurité mobile est l’un de ces domaines où un seul oubli coûte plus cher qu’une année de travail minutieux. Les rapports Verizon 2025 sur la sécurité mobile et les violations de données ont révélé que 85 % des organisations ont signalé une augmentation des attaques contre les appareils mobiles, et que 88 % des violations d’applications sont alimentées par des identifiants volés. La gestion des secrets et la portée des autorisations sont des éléments non négociables de toute liste de contrôle de revue de code pour Android.

Manifeste et autorisations

android:exported doit être explicitement défini sur chaque Activity, Service, Receiver et Provider avec un filtre d’intention. Android 12 et les versions ultérieures l’exigent, et la valeur par défaut sûre est false. Chaque entrée <uses-permission> doit correspondre à une fonctionnalité que l’application utilise réellement. networkSecurityConfig ne doit pas autoriser le trafic en clair dans les builds de production.

Secrets et stockage de données

Les clés API, les clés de signature, les secrets de client OAuth et les clés serveur Firebase appartiennent aux magasins de secrets CI. gradle.properties vérifié dans Git est un chemin de fuite que nous révélons souvent lors des engagements de services de revue de code. Les données sensibles d’exécution utilisent EncryptedSharedPreferences pour les identifiants, Android Keystore pour les clés cryptographiques et le stockage interne pour les fichiers privés de l’application. TLS 1.2 est le minimum.

R8 doit être activé pour les builds de production avec rétrécissement et obfuscation activés. Les règles ProGuard doivent être examinées pour les directives -keep trop larges, et le code de débogage uniquement doit être limité par la variante de build afin qu’il n’atteigne jamais la production.

Préparation à la sortie

Certaines préoccupations des revues ne s’appliquent qu’aux changements touchant le chemin de publication. Nous les examinons séparément car le coût d’une erreur est élevé et les équipes les détectent généralement tard.

Nous vérifions les points suivants :

  • minSdkVersion, targetSdkVersion et compileSdkVersion sont alignés avec les exigences actuelles du Play Store. Google augmente annuellement les minimums de targetSdkVersion, et manquer la date limite bloque les nouvelles mises en ligne
  • L’artefact de publication est un Android App Bundle (.aab), le format de téléchargement requis pour les nouvelles applications
  • R8 réduit visiblement le bundle dans la CI. Un saut de 20 Mo sur un petit changement mérite cinq minutes d’investigation
  • Les profils de base sont régénérés lorsque le code critique au démarrage change
  • Crashlytics, Firebase Performance Monitoring et les rapports de pré-lancement de Play Console sont intégrés à la variante de publication
  • Les chaînes sont traduites pour chaque langue dans laquelle l’application est publiée. La règle lint pour les traductions manquantes devrait échouer les builds de publication au lieu d’émettre un avertissement que l’équipe apprend à ignorer

Outils d'analyse statique

Les outils automatisés détectent les problèmes sur lesquels les humains ne devraient pas passer de temps. Quatre d’entre eux appartiennent à chaque pipeline CI Android. Ils se chevauchent, se complètent, aucun ne remplace les autres. Les directives de revue de code fonctionnent mieux lorsque ces outils gèrent les vérifications mécaniques et que les réviseurs se concentrent sur l’architecture et l’intention.

Outil
Ce qu'il fait
Où il excelle
Outil

Android Lint

Ce qu'il fait

Vérificateur natif du plugin Gradle Android. Analyse Kotlin, Java, XML et les ressources pour les problèmes au niveau de la plateforme.

Où il excelle

Permissions, API obsolètes, problèmes de mise en page, traductions manquantes, problèmes de manifeste.

Outil

Detekt

Ce qu'il fait

Analyse statique pour Kotlin. Analyse l’AST Kotlin, donc il comprend les coroutines, les classes scellées et les fonctionnalités modernes du langage.

Où il excelle

Défauts de code, complexité cyclomatique, conventions de nommage, ensembles de règles personnalisées.

Outil

Ktlint

Ce qu'il fait

Formateur et vérificateur de style opinionné pour Kotlin. Configuration minimale, valeurs par défaut raisonnables.

Où il excelle

Cohérence du formatage, ordre des imports, longueur des lignes. S’associe à Detekt plutôt que de le remplacer.

Outil

R8

Ce qu'il fait

Shrinker de code, optimiseur et obfuscateur de Google. Remplace ProGuard.

Où il excelle

Réduction de la taille des builds de publication, élimination du code mort, obfuscation des identifiants.

Associez-les aux inspections intégrées d’Android Studio, et les avertissements apparaissent directement dans la revue où les réviseurs les voient réellement.

Pourquoi un regard neuf est important

La revue de code interne est une culture. La revue de code externe est un service. La liste de contrôle ci-dessus est plus efficace lorsqu’elle est appliquée par quelqu’un d’extérieur à l’équipe qui a écrit le code, car les réviseurs internes normalisent les modèles auxquels la base de code a vécu pendant des mois.

Trois moments où l’intervention d’un réviseur externe rentabilise l’investissement :

  • Avant une version majeure, lorsque l’équipe est trop proche du code pour repérer ce à quoi elle s’est habituée.
  • Après avoir hérité d’une base de code d’un précédent fournisseur, d’un freelancer ou d’une acquisition.
  • Lorsque du code assisté par IA a été fusionné rapidement et que l’équipe a besoin d’une passe de renforcement avant que le trafic de production ne frappe.

Si vous avez une base de code Android sur laquelle vous souhaitez faire porter un regard expérimenté, contactez-nous et nous évaluerons une revue par rapport à cette liste de contrôle.

Découvrez comment nous avons audité une application de cartographie réseau multiplateforme avant sa sortie et aidé à atteindre 90 % de maintenabilité du code

Veuillez saisir votre adresse courriel professionnelle n'est pas un courriel professionnel