D'autres ont très bien résumé pourquoi lancer tôt . Laissez-moi me concentrer sur le pourquoi attraper la partie tardive , pour laquelle je n'ai pas vu d'explication satisfaisante à mon goût.
SO POURQUOI DES EXCEPTIONS?
Il semble y avoir une grande confusion autour de la raison pour laquelle des exceptions existent. Permettez-moi de partager le grand secret ici: la raison des exceptions et la gestion des exceptions est ... ABSTRACTION .
Avez-vous vu un code comme celui-ci:
static int divide(int dividend, int divisor) throws DivideByZeroException {
if (divisor == 0)
throw new DivideByZeroException(); // that's a checked exception indeed
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
try {
int res = divide(a, b);
System.out.println(res);
} catch (DivideByZeroException e) {
// checked exception... I'm forced to handle it!
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Ce n'est pas comment les exceptions devraient être utilisées. Un code comme celui-ci existe dans la vie réelle, mais il s’agit plutôt d’une aberration et constitue vraiment une exception (jeu de mots). La définition de la division par exemple, même en mathématiques pures, est conditionnelle: c'est toujours le "code de l'appelant" qui doit gérer le cas exceptionnel de zéro pour restreindre le domaine d'entrée. C'est moche. C'est toujours pénible pour l'appelant. Néanmoins, dans de telles situations, le modèle check-then-do est la solution naturelle à suivre:
static int divide(int dividend, int divisor) {
// throws unchecked ArithmeticException for 0 divisor
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
if (b != 0) {
int res = divide(a, b);
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Alternativement, vous pouvez aller en plein commando sur le style POO comme ceci:
static class Division {
final int dividend;
final int divisor;
private Division(int dividend, int divisor) {
this.dividend = dividend;
this.divisor = divisor;
}
public boolean check() {
return divisor != 0;
}
public int eval() {
return dividend / divisor;
}
public static Division with(int dividend, int divisor) {
return new Division(dividend, divisor);
}
}
static void doDivide() {
int a = readInt();
int b = readInt();
Division d = Division.with(a, b);
if (d.check()) {
int res = d.eval();
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Comme vous le voyez, le code de l'appelant a le fardeau de la pré-vérification, mais ne fait aucune gestion des exceptions après. Si un ArithmeticException
appel vient de divide
ou eval
, c’est vous qui devez gérer les exceptions et corriger votre code, car vous avez oublié le code check()
. Pour les mêmes raisons, attraper un NullPointerException
est presque toujours la mauvaise chose à faire.
Maintenant, il y a des gens qui disent vouloir voir les cas exceptionnels dans la signature méthode / fonction, c'est-à-dire pour étendre explicitement le domaine de sortie . Ce sont eux qui favorisent les exceptions vérifiées . Bien entendu, la modification du domaine de sortie devrait obliger tout code appelant direct à s'adapter, ce qui serait effectivement réalisé avec des exceptions vérifiées. Mais vous n'avez pas besoin d'exceptions pour ça! C'est pourquoi vous avez des Nullable<T>
classes génériques , les classes de cas , les types de données algébriques et types d'union . Certaines personnes OO pourraient même préférer retourner null
pour des cas d'erreur simples comme ceci:
static Integer divide(int dividend, int divisor) {
if (divisor == 0) return null;
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
Integer res = divide(a, b);
if (res != null) {
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Techniquement, des exceptions peuvent être utilisées aux fins décrites ci-dessus, mais voici le point: il n'existe aucune exception à une telle utilisation . Les exceptions sont pro abstraction. Les exceptions concernent l'indirection. Les exceptions permettent d'étendre le domaine "résultat" sans rompre les contrats clients directs et sans reporter le traitement des erreurs sur "ailleurs". Si votre code génère des exceptions qui sont gérées par les appelants directs du même code, sans aucune couche d'abstraction entre les deux, alors vous le faites FAUX.
COMMENT ATTRAPER TARD?
Donc nous en sommes là. Je me suis débrouillé pour montrer que l'utilisation d'exceptions dans les scénarios ci-dessus n'était pas la façon dont les exceptions étaient censées être utilisées. Il existe cependant un véritable cas d'utilisation où l'abstraction et l'indirection offertes par la gestion des exceptions sont indispensables. Comprendre un tel usage aidera également à comprendre la recommandation de prise tardive .
Ce cas d'utilisation est: Programmation contre les ressources abstraites ...
Oui, la logique métier doit être programmée contre les abstractions , pas les implémentations concrètes. Le code de "câblage" IOC de niveau supérieur instanciera les implémentations concrètes des ressources abstraites et les transmet à la logique métier. Rien de nouveau ici. Mais les implémentations concrètes de ces ressources abstraites peuvent potentiellement générer leurs propres exceptions spécifiques à l'implémentation , n'est-ce pas?
Alors, qui peut gérer ces exceptions spécifiques à la mise en œuvre? Est-il alors possible de gérer des exceptions spécifiques aux ressources dans la logique métier? Non, ce n'est pas. La logique applicative est programmée contre les abstractions, ce qui exclut la connaissance des détails de ces exceptions spécifiques à l'implémentation.
"Aha!", Vous pourriez dire: "mais c'est pourquoi nous pouvons sous-classer les exceptions et créer des hiérarchies d'exceptions" (consultez M. Spring !). Laissez-moi vous dire que c'est une erreur. Tout d'abord, tout livre raisonnable sur la POO dit que l'héritage concret est mauvais, mais que cette composante essentielle de la machine virtuelle, la gestion des exceptions, est étroitement liée à l'héritage concret. Ironiquement, Joshua Bloch n'aurait pas pu écrire son livre Effective Java avant de pouvoir acquérir l'expérience d'une machine virtuelle Java fonctionnelle, n'est-ce pas ? Il s’agit plus d’un livre de «leçons apprises» pour la prochaine génération. Deuxièmement, et plus important encore, si vous attrapez une exception de haut niveau, comment allez-vous la GÉRER?PatientNeedsImmediateAttentionException
: devons-nous lui donner une pilule ou lui amputer les jambes!? Que diriez-vous d'une instruction switch sur toutes les sous-classes possibles? Là va ton polymorphisme, là va l'abstraction. Tu as le point.
Alors, qui peut gérer les exceptions spécifiques aux ressources? Ce doit être celui qui connaît les concrétions! Celui qui a instancié la ressource! Le code "câblage" bien sûr! Regarde ça:
La logique métier est codée contre les abstractions ... AUCUNE GESTION D'ERREUR DE RESSOURCE CONCRÈTE!
static interface InputResource {
String fetchData();
}
static interface OutputResource {
void writeData(String data);
}
static void doMyBusiness(InputResource in, OutputResource out, int times) {
for (int i = 0; i < times; i++) {
System.out.println("fetching data");
String data = in.fetchData();
System.out.println("outputting data");
out.writeData(data);
}
}
En attendant, quelque part ailleurs, les implémentations concrètes ...
static class ConstantInputResource implements InputResource {
@Override
public String fetchData() {
return "Hello World!";
}
}
static class FailingInputResourceException extends RuntimeException {
public FailingInputResourceException(String message) {
super(message);
}
}
static class FailingInputResource implements InputResource {
@Override
public String fetchData() {
throw new FailingInputResourceException("I am a complete failure!");
}
}
static class StandardOutputResource implements OutputResource {
@Override
public void writeData(String data) {
System.out.println("DATA: " + data);
}
}
Et enfin le code de câblage ... Qui gère les exceptions de ressources concrètes? Celui qui sait à leur sujet!
static void start() {
InputResource in1 = new FailingInputResource();
InputResource in2 = new ConstantInputResource();
OutputResource out = new StandardOutputResource();
try {
ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
}
catch (FailingInputResourceException e)
{
System.out.println(e.getMessage());
System.out.println("retrying...");
ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
}
}
Maintenant supporte avec moi. Le code ci-dessus est simpliste. Vous pouvez par exemple dire que vous avez un conteneur d'entreprise / Web d'entreprise avec plusieurs portées de ressources gérées par conteneur IOC, et que vous avez besoin de nouvelles tentatives automatiques et de la réinitialisation des ressources de session ou de portée de demande, etc. La logique de câblage des portées de niveau inférieur peut être configurée en usine créer des ressources, donc ne pas être au courant des implémentations exactes. Seules les portées de niveau supérieur sauraient réellement quelles exceptions ces ressources de niveau inférieur peuvent générer. Maintenant, attends!
Malheureusement, les exceptions n'autorisent l'indirection que sur la pile d'appels, et différentes étendues avec leurs différentes cardinalités s'exécutent généralement sur plusieurs threads différents. Aucun moyen de communiquer à travers cela avec des exceptions. Nous avons besoin de quelque chose de plus puissant ici. Réponse: message async passant . Catch chaque exception à la racine de la portée de niveau inférieur. Ne rien ignorer, ne rien laisser passer. Cela ferme et supprime toutes les ressources créées sur la pile d'appels de l'étendue actuelle. Ensuite, propagez les messages d'erreur aux niveaux supérieurs en utilisant des files de messages / canaux dans la routine de traitement des exceptions, jusqu'à atteindre le niveau où les concrétions sont connues. C'est le gars qui sait comment gérer ça.
SUMMA SUMMARUM
Donc, selon mon interprétation, attraper en retard signifie attraper des exceptions à l'endroit le plus commode O VOUS NE BRISSEZ PAS L'ABSTRACTION . Ne pas attraper trop tôt! Attrapez les exceptions au niveau de la couche où vous créez l'exception concrète en lançant des occurrences des abstractions de ressources, la couche qui connaît les concrétions des abstractions. La couche "câblage".
HTH. Bonne codage!