web-dev-qa-db-fra.com

Est-ce qu'avoir une déclaration de retour juste pour satisfaire la syntaxe est une mauvaise pratique?

Considérons le code suivant:

public Object getClone(Cloneable a) throws TotallyFooException {

    if (a == null) {
        throw new TotallyFooException();
    }
    else {
        try {
            return a.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
    //cant be reached, in for syntax
    return null;
}

Le return null; est nécessaire, car une exception peut être interceptée. Cependant, dans ce cas, nous avons déjà vérifié si elle était nulle (et supposons que nous connaissons la classe que nous appelons prend en charge le clonage). Nous savons donc que l'instruction try n'échouera jamais.

Est-ce une mauvaise pratique de mettre l'instruction de retour supplémentaire à la fin juste pour vérifier la syntaxe et éviter les erreurs de compilation (avec un commentaire expliquant qu'il ne sera pas atteint), ou existe-t-il un meilleur moyen de coder quelque chose comme celui-ci afin que le déclaration de retour est inutile?

79
yitzih

Une manière plus claire sans déclaration de retour supplémentaire est la suivante. Je ne voudrais pas attraper CloneNotSupportedException non plus, mais laissez-le aller à l'appelant.

if (a != null) {
    try {
        return a.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}
throw new TotallyFooException();

Il est presque toujours possible de modifier l'ordre pour aboutir à une syntaxe plus simple que celle que vous aviez au départ.

132
Kayaman

Il peut certainement être atteint. Notez que vous imprimez uniquement le stacktrace dans la clause catch.

Dans le scénario où a != null et il sera une exception, le return null/ sera atteint. Vous pouvez supprimer cette déclaration et la remplacer par throw new TotallyFooException();.

En général*, si null est un résultat valide d'une méthode (c'est-à-dire l'utilisateur attend} et qu'il signifie quelque chose), il le renvoie en tant que signal pour "données non trouvées" ou exception survenue est non une bonne idée. Autrement, je ne vois pas de problème à ne pas retourner null.

Prenons par exemple la méthode Scanner#ioException :

Renvoie le dernier IOException lancé par le fichier Lisible sous-jacent de ce scanner. Cette méthode retournenull _ ​​si aucune exception de ce type n'existe.

Dans ce cas, la valeur retournée null a un sens clair, lorsque j'utilise la méthode, je peux être sûr d'avoir obtenu null uniquement parce qu'il n'y avait pas une telle exception et non pas parce que la méthode avait tenté de faire quelque chose et qu'elle avait échoué.

*Notez que vous souhaitez parfois renvoyer null même lorsque la signification est ambiguë. Par exemple le HashMap#get :

Une valeur renvoyée de null _ ​​n'indique pas nécessairement que la carte ne contient pas de mappage pour la clé; il est également possible que la carte mappe explicitement la clé sur} _ null. L'opération containsKey peut être utilisée pour distinguer ces deux cas.

Dans ce cas, null peut indiquer que la valeurnull a été trouvée et renvoyée, ou que la table de hachage ne contient pas la clé demandée.

98
Maroun

Est-ce une mauvaise pratique de mettre l'instruction de retour supplémentaire à la fin juste pour vérifier la syntaxe et éviter les erreurs de compilation (avec un commentaire expliquant qu'elle ne sera pas atteinte)

Je pense que return null est une mauvaise pratique pour le terminus d’une branche inaccessible. Il vaut mieux jeter un RuntimeException ( AssertionError serait également acceptable) car pour arriver à cette ligne, quelque chose s'est très mal passé et que l'application est dans un état inconnu. La plupart des choses semblables sont (comme ci-dessus) parce que le développeur a oublié quelque chose (les objets peuvent être nuls et non clonables). 

Je n'utiliserais probablement pas InternalError à moins que je ne sois très sûr que le code soit inaccessible (par exemple, après un System.exit()) car il est plus probable que je commette une erreur que la machine virtuelle.

J'utiliserais uniquement une exception personnalisée (telle que TotallyFooException) si accéder à cette "ligne inaccessible" signifie la même chose que partout où vous lâchez cette exception.

26

Vous avez attrapé la CloneNotSupportedException qui signifie que votre code peut le gérer. Mais une fois que vous l'avez détectée, vous ne savez pratiquement pas quoi faire lorsque vous atteignez la fin de la fonction, ce qui implique que vous ne pouvez pas la gérer. Vous avez donc raison de dire que c'est une odeur de code dans ce cas et, à mon avis, cela signifie que vous n'auriez pas dû attraper CloneNotSupportedException.

14
djechlin

Je préférerais utiliser Objects.requireNonNull() pour vérifier si le paramètre a n'est pas nul. Il est donc clair, lorsque vous lisez le code, que le paramètre ne doit pas être nul.

Et pour éviter les exceptions cochées, je lancerais la CloneNotSupportedException comme une RuntimeException.

Pour les deux, vous pouvez ajouter un texte agréable dans le but de préciser pourquoi cela ne devrait pas arriver ou être le cas.

public Object getClone(Object a) {

    Objects.requireNonNull(a);

    try {
        return a.clone();
    } catch (CloneNotSupportedException e) {
        throw new IllegalArgumentException(e);
    }

}
12
Kuchi

Dans ce genre de situation, j'écrirais

public Object getClone(SomeInterface a) throws TotallyFooException {
    // Precondition: "a" should be null or should have a someMethod method that
    // does not throw a SomeException.
    if (a == null) {
        throw new TotallyFooException() ; }
    else {
        try {
            return a.someMethod(); }
        catch (SomeException e) {
            throw new IllegalArgumentException(e) ; } }
}

Fait intéressant, vous dites que "l'instruction try n'échouera jamais", mais vous avez néanmoins pris la peine d'écrire une instruction e.printStackTrace(); qui, selon vous, ne sera jamais exécutée. Pourquoi?

Peut-être que votre conviction n’est pas aussi fermement ancrée. Cela est bon (à mon avis), puisque votre conviction ne repose pas sur le code que vous avez écrit, mais plutôt sur l’espoir que votre client ne violera pas la condition préalable. Mieux vaut programmer les méthodes publiques de manière défensive. 

À propos, votre code ne sera pas compilé pour moi. Vous ne pouvez pas appeler a.clone() même si le type de a est Cloneable. Au moins le compilateur d'Eclipse le dit. Expression a.clone() donne une erreur

La méthode clone () n'est pas définie pour le type Cloneable.

Ce que je ferais pour votre cas spécifique est

public Object getClone(PubliclyCloneable a) throws TotallyFooException {
    if (a == null) {
        throw new TotallyFooException(); }
    else {
        return a.clone(); }
}

PubliclyCloneable est défini par

interface PubliclyCloneable {
    public Object clone() ;
}

Ou, si vous avez absolument besoin que le type de paramètre soit Cloneable, les éléments suivants au moins sont compilés.

public static Object getClone(Cloneable a) throws TotallyFooException {
//  Precondition: "a" should be null or point to an object that can be cloned without
// throwing any checked exception.
    if (a == null) {
        throw new TotallyFooException(); }
    else {
        try {
            return a.getClass().getMethod("clone").invoke(a) ; }
        catch( IllegalAccessException e ) {
            throw new AssertionError(null, e) ; }
        catch( InvocationTargetException e ) {
            Throwable t = e.getTargetException() ;
            if( t instanceof Error ) {
                // Unchecked exceptions are bubbled
                throw (Error) t ; }
            else if( t instanceof RuntimeException ) {
                // Unchecked exceptions are bubbled
                throw (RuntimeException) t ; }
            else {
                // Checked exceptions indicate a precondition violation.
                throw new IllegalArgumentException(t) ; } }
        catch( NoSuchMethodException e ) {
            throw new AssertionError(null, e) ; } }
}
7
Theodore Norvell

Les exemples ci-dessus sont valables et très Java. Cependant, voici comment je répondrais à la question du PO sur la façon de gérer ce retour:

public Object getClone(Cloneable a) throws CloneNotSupportedException {
    return a.clone();
}

Il n'y a aucun avantage à vérifier a pour voir si elle est nulle. Ça va au NPE. L'impression d'une trace de pile n'est également pas utile. La trace de la pile est la même quel que soit le lieu où elle est traitée.

Il est inutile de créer le code avec des tests null inutiles et une gestion des exceptions inutile. En supprimant le courrier indésirable, le problème de retour est sans objet.

(Notez que l'OP a inclus un bogue dans la gestion des exceptions; c'est pourquoi le retour était nécessaire. L'OP ne se serait pas trompé avec la méthode que je propose.)

6
Tony Ennis

Est-ce qu'avoir une déclaration de retour juste pour satisfaire la syntaxe est une mauvaise pratique?

Comme d'autres l'ont mentionné, dans votre cas, cela ne s'applique pas réellement.

Pour répondre à la question, cependant, les programmes de type Lint ne l'ont certainement pas compris! J'ai vu deux personnes différentes se battre pour cela dans une déclaration de substitution.

    switch (var)
   {
     case A:
       break;
     default:
       return;
       break;    // Unreachable code.  Coding standard violation?
   }

L'un d'eux s'est plaint de ce que pas avoir la pause était une violation des normes de codage. L'autre s'est plaint que (avoir} _ c'était l'un parce que c'était du code inaccessible.

J'ai remarqué cela parce que deux programmeurs différents revérifiaient le code avec la pause ajoutée, puis supprimée, ajoutée puis supprimée, en fonction de l'analyseur de code exécuté le jour même.

Si vous vous retrouvez dans cette situation, choisissez-en un et commentez l'anomalie, qui est la bonne forme que vous vous êtes montrée. C'est le meilleur et le plus important à emporter.

5
Jos

Ce n'est pas «juste pour satisfaire la syntaxe». C'est une exigence sémantique du langage que chaque chemin de code conduise à un retour ou à un lancer. Ce code n'est pas conforme. Si l'exception est interceptée, un retour suivant est requis.

Pas de "mauvaise pratique" à ce sujet, ni de satisfaire le compilateur en général.

En tout cas, que ce soit la syntaxe ou la sémantique, vous n’avez pas le choix.

5
user207421

Je voudrais réécrire ceci pour avoir le retour à la fin. Pseudocode:

if a == null throw ...
// else not needed, if this is reached, a is not null
Object b
try {
  b = a.clone
}
catch ...

return b
2
Pablo Pazos

Le retour nul; est nécessaire puisqu’une exception peut être interceptée, Cependant, dans un tel cas, puisque nous avons déjà vérifié si elle était nulle (et supposons que nous savons que la classe que nous appelons prend en charge le clonage), nous, donc Sachez que l'instruction try n'échouera jamais.

Si vous connaissez les détails des entrées impliquées de manière à ce que l'instruction try ne puisse jamais échouer, à quoi sert-il de l'avoir? Évitez la variable try si vous savez avec certitude que les choses vont toujours réussir (même s’il est rare que vous puissiez être absolument sûr pendant toute la durée de vie de votre base de code).

Dans tous les cas, le compilateur n'est malheureusement pas un lecteur d'esprit. Il voit la fonction et ses entrées, et compte tenu des informations dont il dispose, il a besoin de cette instruction return au bas de l'écran.

Est-ce une mauvaise pratique de mettre la déclaration de retour supplémentaire à la fin juste pour satisfaire la syntaxe et éviter les erreurs de compilation (avec un commentaire expliquant qu'elle ne sera pas atteinte), ou existe-t-il un meilleur moyen de coder quelque chose comme ceci pour que la déclaration de retour supplémentaire ne soit pas nécessaire?

Bien au contraire, je suggérerais que c'est une bonne pratique d'éviter tout avertissement du compilateur, par exemple, même si cela coûte une autre ligne de code. Ne vous inquiétez pas trop du nombre de lignes ici. Établissez la fiabilité de la fonction en effectuant des tests, puis poursuivez. En prétendant que vous pouvez omettre la déclaration return, imaginez revenir à ce code un an plus tard, puis essayez de décider si cette déclaration return au bas de la page causera plus de confusion que certains commentaires détaillant les détails de la raison pour laquelle elle a été omise en raison d'hypothèses vous pouvez faire sur les paramètres d'entrée. Très probablement, la déclaration return sera plus facile à traiter.

Cela dit, en particulier à propos de cette partie:

try {
    return a.clone();
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}
...
//cant be reached, in for syntax
return null;

Je pense qu'il y a quelque chose de légèrement étrange avec l'état d'esprit de gestion des exceptions ici. Vous voulez généralement avaler des exceptions sur un site où vous pouvez faire quelque chose de significatif.

Vous pouvez considérer try/catch comme un mécanisme de transaction. try apportant ces modifications, si elles échouent et que nous passons au bloc catch, procédons ainsi (quelle que soit la position du bloc catch) en réponse dans le cadre du processus de restauration et de restauration.

Dans ce cas, le simple fait d’imprimer un stacktrace puis d’être obligé de renvoyer null n’est pas exactement un état d’esprit de transaction/récupération. Le code transfère la responsabilité du traitement des erreurs à tout le code appelant getClone pour vérifier manuellement les échecs. Vous préférerez peut-être intercepter la variable CloneNotSupportedException et la traduire en une autre forme d'exception plus significative, mais vous ne voulez pas simplement avaler l'exception et renvoyer une valeur null dans ce cas, car il ne s'agit pas d'un site de récupération de transaction .

Vous finirez par confier aux appelants la responsabilité de vérifier et de traiter manuellement les échecs de cette façon, lorsqu'une exception sera évitée.

C'est comme si vous chargiez un fichier, c'est la transaction de haut niveau. Vous pourriez avoir un try/catch ici. Pendant le processus de trying pour charger un fichier, vous pouvez cloner des objets. En cas d’échec de cette opération de haut niveau (chargement du fichier), vous souhaitez généralement renvoyer les exceptions à ce bloc de transaction de niveau supérieur try/catch afin de pouvoir récupérer en douceur après une erreur lors du chargement d’un fichier cela est dû à une erreur de clonage ou autre chose). Par conséquent, nous ne voulons généralement pas simplement engloutir une exception dans un endroit aussi granulaire que celui-ci, puis renvoyer une valeur nulle, par exemple, car cela annulerait une grande partie de la valeur et de la raison d'être des exceptions. Au lieu de cela, nous souhaitons propager les exceptions jusqu’à un site où nous pouvons le gérer de manière significative.

2
Dragon Energy

Personne n'en a encore parlé, alors:

public static final Object ERROR_OBJECT = ...

//...

public Object getClone(Cloneable a) throws TotallyFooException {
Object ret;

if (a == null) 
    throw new TotallyFooException();

//no need for else here
try {
    ret = a.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
    //something went wrong! ERROR_OBJECT could also be null
    ret = ERROR_OBJECT; 
}

return ret;

}

Je n'aime pas return à l'intérieur de try blocs pour cette raison même.

2
rath

Votre exemple n’est pas idéal pour illustrer votre question comme indiqué dans le dernier paragraphe:

Est-ce une mauvaise pratique de mettre la déclaration de retour supplémentaire à la fin juste pour satisfaire la syntaxe et éviter les erreurs de compilation (avec un commentaire expliquant qu'elle ne sera pas atteinte), ou existe-t-il un meilleur moyen de coder quelque chose comme ceci pour que la déclaration de retour supplémentaire ne soit pas nécessaire?

Un meilleur exemple serait la mise en oeuvre du clone lui-même:

 public class A implements Cloneable {
      public Object clone() {
           try {
               return super.clone() ;
           } catch (CloneNotSupportedException e) {
               throw new InternalError(e) ; // vm bug.
           }
      }
 }

Ici, la clause de capture devrait jamais être entrée. Néanmoins, la syntaxe nécessite de jeter quelque chose ou de renvoyer une valeur. Étant donné que renvoyer quelque chose n'a pas de sens, une InternalError est utilisée pour indiquer une condition grave VM.

0
wero