Question Que fait le mot clé d'assertion Java et quand doit-il être utilisé?


Quels sont certains exemples de la vie réelle comprendre le rôle clé des assertions?


497
2018-05-03 13:11


origine


Réponses:


Assertions (par le biais du affirmer mot-clé) ont été ajoutés dans Java 1.4. Ils sont utilisés pour vérifier l'exactitude d'un invariant dans le code. Ils ne devraient jamais être déclenchés dans le code de production, et sont indicatifs d'un bug ou d'une mauvaise utilisation d'un chemin de code. Ils peuvent être activés lors de l'exécution via le -ea option sur le java commande, mais ne sont pas activés par défaut.

Un exemple:

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}

367
2018-05-03 14:14



Supposons que vous êtes supposé écrire un programme pour contrôler une centrale nucléaire. Il est assez évident que même l'erreur la plus mineure pourrait avoir des résultats catastrophiques, donc votre code doit être sans bug (en supposant que la JVM est sans bug pour le bien de l'argument).

Java n'est pas un langage vérifiable, ce qui signifie: vous ne pouvez pas calculer que le résultat de votre opération sera parfait. La raison principale de ceci sont des pointeurs: ils peuvent pointer n'importe où ou nulle part, par conséquent ils ne peuvent pas être calculés pour être de cette valeur exacte, du moins pas dans une étendue de code raisonnable. Compte tenu de ce problème, il n'y a aucun moyen de prouver que votre code est correct dans son ensemble. Mais ce que vous pouvez faire est de prouver que vous trouvez au moins tous les bogues quand cela arrive.

Cette idée est basée sur la Conception par contrat (DbC) paradigme: vous définissez d'abord (avec une précision mathématique) ce que votre méthode est censée faire, et ensuite vérifiez cela en le testant pendant l'exécution réelle. Exemple:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

Bien que cela soit assez évident pour fonctionner correctement, la plupart des programmeurs ne verront pas le bug caché à l'intérieur de celui-ci (indice: l'Ariane V s'est écrasé à cause d'un bug similaire). Maintenant DbC définit que vous devez toujours vérifier l'entrée et la sortie d'une fonction pour vérifier qu'elle fonctionne correctement. Java peut le faire via des assertions:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
    assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
  final int result = a + b;
    assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
  return result;
}

Si cette fonction échoue maintenant, vous le remarquerez. Vous saurez qu'il y a un problème dans votre code, vous savez où il se trouve et vous savez ce qui l'a causé (similaire aux exceptions). Et ce qui est encore plus important: vous arrêtez l'exécution juste quand cela arrive pour empêcher tout autre code de fonctionner avec de fausses valeurs et potentiellement causer des dommages à tout ce qu'il contrôle.

Les exceptions Java sont un concept similaire, mais elles ne parviennent pas à tout vérifier. Si vous voulez encore plus de contrôles (au prix de la vitesse d'exécution), vous devez utiliser des assertions. Cela va gonfler votre code, mais vous pouvez finalement livrer un produit à un temps de développement étonnamment court (plus tôt vous corrigez un bug, plus le coût est bas). Et en plus: s'il y a un bug dans votre code, vous le détecterez. Il n'y a aucun moyen de glisser un bug et de causer des problèmes plus tard.

Ce n'est toujours pas une garantie pour le code sans bug, mais il est beaucoup plus proche de cela, que les programmes habituels.


284
2017-10-17 11:31



Les assertions sont un outil en phase de développement permettant d'intercepter les bogues dans votre code. Ils sont conçus pour être facilement supprimés, de sorte qu'ils n'existent pas dans le code de production. Les assertions ne font donc pas partie de la "solution" que vous livrez au client. Ce sont des vérifications internes pour s'assurer que les hypothèses que vous faites sont correctes. L'exemple le plus courant consiste à tester la valeur null. Beaucoup de méthodes sont écrites comme ceci:

void doSomething(Widget widget) {
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

Très souvent, dans une méthode comme celle-ci, le widget ne devrait jamais être nul. Donc, si c'est null, il y a un bug dans votre code quelque part que vous avez besoin de traquer. Mais le code ci-dessus ne vous le dira jamais. Donc, dans un effort bien intentionné pour écrire du code "sûr", vous cachez également un bogue. Il est préférable d'écrire du code comme ceci:

/**
 * @param Widget widget Should never be null
 */
void doSomething(Widget widget) {
  assert widget != null;
  widget.someMethod(); // ...
    ... // do more stuff with this widget
}

De cette façon, vous serez sûr d’attraper ce bug tôt. (Il est également utile de spécifier dans le contrat que ce paramètre ne doit jamais être nul.) Veillez à activer les assertions lorsque vous testez votre code pendant le développement. (Et persuader vos collègues de le faire aussi est souvent difficile, ce que je trouve très ennuyant.)

Maintenant, certains de vos collègues s'opposeront à ce code, arguant que vous devriez toujours mettre dans la vérification nulle pour empêcher une exception dans la production. Dans ce cas, l'assertion est toujours utile. Vous pouvez l'écrire comme ceci:

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

De cette façon, vos collègues seront heureux que la vérification de null soit là pour le code de production, mais pendant le développement, vous ne cachez plus le bogue quand le widget est nul.

Voici un exemple concret: j'ai écrit une fois une méthode qui compare deux valeurs arbitraires pour l'égalité, où l'une ou l'autre valeur peut être nulle:

/**
 * Compare two values using equals(), after checking for null.
 * @param thisValue (may be null)
 * @param otherValue (may be null)
 * @return True if they are both null or if equals() returns true
 */
public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = thisValue.equals(otherValue);
  }
  return result;
}

Ce code délègue le travail du equals() méthode dans le cas où thisValue n'est pas nulle. Mais il assume la equals() méthode remplit correctement le contrat de equals() en gérant correctement un paramètre nul.

Un collègue s’est opposé à mon code, me disant que beaucoup de nos cours avaient des bogues. equals() méthodes qui ne testent pas null, donc je devrais mettre cette vérification dans cette méthode. C'est discutable si cela est judicieux, ou si nous devrions forcer l'erreur, afin que nous puissions le repérer et le corriger, mais je me suis reporté à mon collègue et j'ai mis un chèque nul, que j'ai marqué d'un commentaire:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = otherValue != null && thisValue.equals(otherValue); // questionable null check
  }
  return result;
}

Le chèque supplémentaire ici, other != null, n'est nécessaire que si equals() méthode ne vérifie pas null comme requis par son contrat.

Plutôt que de s'engager dans un débat stérile avec mon collègue sur la sagesse de laisser le code bogué rester dans notre base de code, j'ai simplement mis deux assertions dans le code. Ces assertions me feront savoir, lors de la phase de développement, si l’une de nos classes ne parvient pas à mettre en œuvre equals() correctement, donc je peux le réparer:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
    assert otherValue == null || otherValue.equals(null) == false;
  } else {
    result = otherValue != null && thisValue.equals(otherValue);
    assert thisValue.equals(null) == false;
  }
  return result;
}

Les points importants à garder à l'esprit sont les suivants:

  1. Les assertions sont des outils en phase de développement uniquement.

  2. Le but d'une affirmation est de vous faire savoir s'il y a un bug, pas seulement dans votre code, mais dans votre base de code. (Les assertions ici signaleront réellement des bogues dans d'autres classes.)

  3. Même si mon collègue était convaincu que nos cours étaient correctement écrits, les affirmations seraient toujours utiles. De nouvelles classes seront ajoutées qui pourraient ne pas tester null, et cette méthode peut marquer ces bogues pour nous.

  4. En développement, vous devez toujours activer les assertions, même si le code que vous avez écrit n'utilise pas d'assertions. Mon IDE est configuré pour toujours le faire par défaut pour tout nouvel exécutable.

  5. Les assertions ne changent pas le comportement du code en production, donc mon collègue est heureux que la vérification nulle soit là, et que cette méthode s'exécute correctement même si le equals()La méthode est buggée. Je suis heureux parce que je vais attraper un buggy equals() méthode en développement.

En outre, vous devez tester votre stratégie d'assertion en plaçant une assertion temporaire qui échouera, afin de vous assurer que vous êtes averti, soit par le biais du fichier journal, soit par une trace de pile dans le flux de sortie.


48
2018-01-17 21:18



Beaucoup de bonnes réponses expliquant ce que le assert le mot-clé fait, mais peu répondent à la vraie question, "quand le assert mot-clé être utilisé dans la vraie vie? "

La réponse: presque jamais.

Les assertions, en tant que concept, sont merveilleuses. Bon code a beaucoup de if (...) throw ... déclarations (et leurs proches comme Objects.requireNonNull et Math.addExact). Cependant, certaines décisions de conception ont grandement limité l'utilité de assert  mot-clé lui-même.

L'idée de conduite derrière le assert mot-clé est l'optimisation prématurée, et la caractéristique principale est de pouvoir facilement désactiver toutes les vérifications. En fait, le assert les contrôles sont désactivés par défaut.

Cependant, il est extrêmement important que des contrôles invariants continuent d'être effectués en production. C'est parce que la couverture de test parfaite est impossible, et tout le code de production aura des bugs que les assertions devraient aider à diagnostiquer et à atténuer.

Par conséquent, l'utilisation de if (...) throw ... devrait être préféré, tout comme il est nécessaire pour vérifier les valeurs des paramètres des méthodes publiques et pour jeter IllegalArgumentException.

De temps en temps, on pourrait être tenté d'écrire une vérification invariante qui prend beaucoup de temps à traiter (et est assez souvent appelée pour que cela ait de l'importance). Cependant, ces contrôles ralentiront les tests, ce qui est également indésirable. Ces contrôles chronophages sont généralement écrits sous forme de tests unitaires. Néanmoins, il peut parfois être judicieux d'utiliser assert Pour cette raison.

Ne pas utiliser assert simplement parce qu'il est plus propre et plus joli que if (...) throw ... (et je dis ça avec beaucoup de peine, parce que j'aime bien et jolie). Si vous ne pouvez pas vous aider, et pouvez contrôler comment votre application est lancée, alors n'hésitez pas à utiliser assert mais toujours permettre des assertions dans la production. Certes, c'est ce que j'ai tendance à faire. Je pousse pour une annotation lombok qui causera assert agir plus comme if (...) throw .... Votez pour cela ici.

(Rant: les développeurs JVM étaient un tas de codeurs affreux et prématurément optimisés.) C'est pourquoi vous avez entendu parler de tant de problèmes de sécurité dans le plugin Java et JVM.Ils ont refusé d'inclure des vérifications de base et des assertions dans le code de production. payer le prix.)


17
2018-06-03 04:29



Voici le cas d'utilisation le plus courant. Supposons que vous activiez une valeur enum:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
}

Tant que vous gérez tous les cas, vous allez bien. Mais un jour, quelqu'un va ajouter de la figue à votre énumération et oublier de l'ajouter à votre déclaration de commutateur. Cela produit un bug qui peut être difficile à attraper, car les effets ne seront pas ressentis avant que vous ayez quitté l'instruction switch. Mais si vous écrivez votre commutateur comme ceci, vous pouvez l'attraper immédiatement:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}

11
2018-01-17 07:29



Les assertions sont utilisées pour vérifier les conditions préalables et ne doivent jamais échouer. Le code correct ne doit jamais échouer une assertion; quand ils se déclenchent, ils devraient indiquer un bug (heureusement à un endroit proche de l'emplacement du problème).

Un exemple d'assertion peut être de vérifier qu'un groupe particulier de méthodes est appelé dans le bon ordre (par exemple, que hasNext() est appelé avant next() dans un Iterator).


9
2018-05-03 13:17



Que fait le mot-clé assert en Java?

Regardons le bytecode compilé.

Nous conclurons que:

public class Assert {
    public static void main(String[] args) {
        assert System.currentTimeMillis() == 0L;
    }
}

génère presque exactement le même bytecode que:

public class Assert {
    static final boolean $assertionsDisabled =
        !Assert.class.desiredAssertionStatus();
    public static void main(String[] args) {
        if (!$assertionsDisabled) {
            if (System.currentTimeMillis() != 0L) {
                throw new AssertionError();
            }
        }
    }
}

Assert.class.desiredAssertionStatus() est true quand -ea est passé sur la ligne de commande, et false sinon.

Nous utilisons System.currentTimeMillis() pour s'assurer qu'il ne sera pas optimisé (assert true; fait).

Le champ synthétique est généré de sorte que Java doit seulement appeler Assert.class.desiredAssertionStatus() une fois au moment du chargement, et il met ensuite le résultat en cache. Voir également: Quelle est la signification de "statique synthétique"?

Nous pouvons vérifier cela avec:

javac Assert.java
javap -c -constants -private -verbose Assert.class

Avec Oracle JDK 1.8.0_45, un champ statique statique a été généré (voir aussi: Quelle est la signification de "statique synthétique"?):

static final boolean $assertionsDisabled;
  descriptor: Z
  flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

avec un initialiseur statique:

 0: ldc           #6                  // class Assert
 2: invokevirtual #7                  // Method java/lang Class.desiredAssertionStatus:()Z
 5: ifne          12
 8: iconst_1
 9: goto          13
12: iconst_0
13: putstatic     #2                  // Field $assertionsDisabled:Z
16: return

et la méthode principale est:

 0: getstatic     #2                  // Field $assertionsDisabled:Z
 3: ifne          22
 6: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
 9: lconst_0
10: lcmp
11: ifeq          22
14: new           #4                  // class java/lang/AssertionError
17: dup
18: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return

Nous concluons que:

  • il n'y a pas de support de niveau bytecode pour assert: c'est un concept de langage Java
  • assert pourrait être émulé assez bien avec les propriétés du système -Pcom.me.assert=true remplacer -ea sur la ligne de commande, et un throw new AssertionError().

6
2018-04-03 20:53



Un exemple du monde réel, d'une classe Stack (de Assertion dans les articles Java)

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}

6
2018-05-03 13:17