W3docs

Principes du code propre en Java

Les principes du code propre en Java : méthodes courtes, noms significatifs, responsabilité unique et mutation minimale.

Le code propre est un code que la prochaine personne — souvent vous-même, six mois plus tard — peut lire, comprendre et modifier sans crainte. Le compilateur accepte presque n'importe quoi ; le code propre s'adresse à l'autre public, les humains qui le maintiennent. Aucun des principes ci-dessous n'est une invention spécifique à Java, mais Java vous fournit des outils concrets — méthodes courtes, champs final, records, exceptions, types significatifs — pour les appliquer. Ce chapitre passe en revue ceux qui portent leurs fruits chaque jour.

Des noms qui révèlent l'intention

Un bon nom répond à la question pourquoi la valeur existe, pas seulement quel type elle est. Si un nom nécessite un commentaire pour l'expliquer, c'est que le nom est mauvais. Évitez les lettres seules (sauf dans les boucles courtes), évitez les abréviations que vous seul comprenez, et préférez un nom descriptif plus long à un nom court et cryptique — votre IDE le complète automatiquement de toute façon.

// Unclear: what is d? what unit? what is the magic 86400000?
int d = (t2 - t1) / 86400000;

// Clear: the names and a constant carry the meaning
long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
long elapsedDays = (endMillis - startMillis) / MILLIS_PER_DAY;

Les booléens se lisent mieux comme des questions ou des états : isActive, hasNext, shouldRetry. Les méthodes qui font quelque chose reçoivent des noms de verbe (calculateTotal) ; les méthodes qui retournent quelque chose reçoivent des noms nominaux ou de getter (total, getTotal). Pour les conventions de nommage au niveau du langage qui sous-tendent ces choix, consultez les conventions de nommage Java et les meilleures pratiques de nommage Java.

Des méthodes courtes avec une responsabilité unique

Une méthode doit faire une seule chose à un seul niveau d'abstraction. Lorsque vous vous retrouvez à ajouter un commentaire comme // now validate the input, ce bloc souhaite généralement devenir sa propre méthode bien nommée. Les méthodes courtes sont plus faciles à nommer, tester et réutiliser — et le site d'appel se lit comme une phrase.

// Before: one method juggling validation, calculation, and formatting
String receipt(Order order) {
  if (order == null || order.items().isEmpty())
    throw new IllegalArgumentException("empty order");
  int total = 0;
  for (var i : order.items()) total += i.price() * i.qty();
  return "Total: $" + total / 100 + "." + (total % 100);
}

// After: each step is a named method; receipt() now reads top-down
String receipt(Order order) {
  requireNonEmpty(order);
  int total = totalCents(order);
  return formatCents(total);
}

Les clauses de garde maintiennent les méthodes plates. Gérez les cas limites et retournez tôt au lieu d'envelopper le chemin heureux dans des blocs if qui s'approfondissent.

Préférer l'immuabilité et la mutation minimale

L'état mutable partagé est à l'origine de la plupart des bugs de concurrence et de beaucoup de confusion ordinaire. Utilisez par défaut des champs et des variables locales final ; ne passez au mutable que lorsque vous en avez une raison. Les records Java font des objets de valeur immuables une seule ligne, générant un constructeur canonique, equals, hashCode et toString.

// An immutable value object with validation in the compact constructor
record Money(long cents, String currency) {
  Money {
    if (cents < 0) throw new IllegalArgumentException("cents must be >= 0");
  }
  Money plus(Money other) { return new Money(cents + other.cents, currency); }
}

Remarquez que plus retourne un nouveau Money plutôt que de muter this. Les objets immuables sont sûrs à partager entre les threads, sûrs à utiliser comme clés de map, et impossibles à corrompre après la construction. Pour aller plus loin, consultez les records Java, les classes immuables et les meilleures pratiques d'immuabilité ; le mot-clé final couvre le verrouillage des champs et des variables.

Échouer rapidement et utiliser les exceptions, pas les codes d'erreur

Validez les arguments à la frontière et lancez une exception immédiatement lorsque quelque chose ne va pas, afin que l'échec apparaisse près de sa cause plutôt que trois couches plus bas sous forme d'une NullPointerException déroutante. Utilisez les exceptions pour signaler les erreurs ; ne retournez pas null ni des valeurs sentinelles magiques que chaque appelant doit se rappeler de vérifier.

ModèleÀ éviterÀ préférer
Valeur manquantereturn null;Optional<T> ou lancer une exception
Mauvais argumentreturn -1;throw new IllegalArgumentException(...)
État impossiblevaleur par défaut silencieusethrow new IllegalStateException(...)
Nettoyage des ressourcesfinally manueltry-with-resources
static User findUser(String id) {
  Objects.requireNonNull(id, "id must not be null");   // fail fast
  return repository.lookup(id)
      .orElseThrow(() -> new NoSuchElementException("no user: " + id));
}

Objects.requireNonNull transforme une NPE vague en aval en un message précis au point d'entrée. Pour modéliser « peut être absent » sans null, lisez Java Optional ; pour concevoir des types d'erreurs, consultez les meilleures pratiques des exceptions et les exceptions personnalisées.

DRY, mais sans sur-abstraction

DRY (Don't Repeat Yourself) signifie qu'une seule connaissance vit en un seul endroit. Lorsque la même constante ou le même calcul apparaît deux fois, extrayez-le. Mais résistez à l'erreur inverse : deux extraits qui se ressemblent simplement aujourd'hui peuvent diverger demain. Le code dupliqué est moins cher à corriger que la mauvaise abstraction. Extrayez lorsque le sens est partagé, pas seulement la syntaxe.

// Knowledge duplicated: the threshold lives in two places
if (order.total() >= 5000) freeShip = true;     // here
if (cart.total() >= 5000) showBadge = true;     // and here

// One source of truth
static final int FREE_SHIPPING_THRESHOLD_CENTS = 5000;
boolean qualifiesForFreeShipping(int totalCents) {
  return totalCents >= FREE_SHIPPING_THRESHOLD_CENTS;
}

Un exemple concret : un panier d'achat propre

Ce programme rassemble les principes dans un petit fichier exécutable : un record LineItem immuable qui se valide lui-même, des méthodes à but unique avec des noms révélant l'intention, des constantes nommées au lieu de nombres magiques, une clause de garde et une égalité basée sur les valeurs. Aucun commentaire n'est nécessaire pour expliquer ce qu'il fait — les noms s'en chargent.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • La sortie se lit exactement comme le domaine qu'elle modélise — 2 x Notebook @ $12.50 = $25.00 — car chaque méthode et chaque champ est nommé pour son but. Vous n'avez pas eu besoin d'un seul commentaire pour suivre le calcul du panier ; les noms révélant l'intention ont assuré la documentation.
  • Le sous-total est $30.97 et la livraison est $5.99, pas gratuite : la clause de garde shippingCents a comparé le sous-total à la constante nommée FREE_SHIPPING_THRESHOLD_CENTS (5000), et 3097 est en dessous. Le nombre magique vit exactement en un seul endroit, donc la règle est impossible à rendre incohérente.
  • value equality: true prouve que le record nous a donné equals par valeur gratuitement — deux articles Pen construits séparément sont égaux parce que leurs données sont égales. Écrire ce binôme equals/hashCode à la main est du code passe-partout que vous n'avez plus à maintenir.
  • rejected bad item: quantity must be >= 0 montre l'échec rapide en action : le constructeur compact a validé l'argument et lancé IllegalArgumentException au moment de la construction, donc une quantité -1 ne peut jamais entrer dans le système en tant que données invalides silencieuses.
  • Chaque méthode utilitaire — subtotalCents, shippingCents, formatCents — fait une seule chose, donc main se lit de haut en bas comme une histoire. Les méthodes courtes à responsabilité unique sont ce qui rend l'ensemble du programme lisible d'un coup d'œil plutôt qu'un mur de logique imbriquée.

Exercice

Pratique
Pourquoi faire de 'LineItem' un record avec validation dans son constructeur compact est-il considéré comme plus propre qu'une classe mutable ordinaire avec des setters ?
Pourquoi faire de 'LineItem' un record avec validation dans son constructeur compact est-il considéré comme plus propre qu'une classe mutable ordinaire avec des setters ?
Was this page helpful?