DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Java Zone»Programmation BS : Exceptions Cochées
    Java Zone

    Programmation BS : Exceptions Cochées

    octobre 15, 2021
    Programmation BS : Exceptions Cochées
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Le problème

    J’ai toujours détesté être obligé d’attraper une exception, en grande partie parce que :

    • Rappelez-vous que le code que vous avez écrit apporte la sauvegarde de la base de données, ajoute de l’espace disque ou de la mémoire, accorde les privilèges de fichier corrects en tant que root ? Moi non plus. Si un problème réel survient qui échappe au contrôle de votre code, alors par définition, vous ne pouvez pas changer le résultat. Alors à quoi bon vous forcer à le gérer ?
    • Ce n’est pas parce que quelque chose est un problème pour toi que c’est un problème pour moi. Par exemple, dans Java JNI (essentiellement un magasin clé/valeur), si vous recherchez une valeur pour une clé donnée, mais que la clé n’existe pas, une exception est levée. Il semble que les concepteurs de JNI ont supposé qu’il s’agissait de la seule source d’information pour une valeur donnée. Et si ce n’est pas le cas ?
    • Puisqu’il n’y a rien que vous puissiez vraiment faire en réponse à une exception, ils finissent par être renvoyés au sommet, où un service Web renvoie un code HTTP 500, une interface graphique affiche un « il y a un problème que nous ne pouvons pas résoudre » dialogue à l’utilisateur.
    • Si une méthode d’interface ne déclare aucun type d’exception vérifié, l’implémentation ne peut pas lever d’exception vérifiée. Mais que se passe-t-il si l’implémentation doit effectuer des appels qui lèvent des exceptions vérifiées ?
    • Parfois, vous êtes obligé d’utiliser la gestion des exceptions comme une instruction if/then
    • Parfois, vous devez avoir des blocs try/catch imbriqués, ce qui peut être difficile à raisonner et ne sont invariablement pas testés unitairement pour chaque instruction catch.

    Stratégies

    Si vous regardez le code Java comme exemple, les gens utilisent diverses stratégies pour essayer de gérer les exceptions vérifiées, telles que :

    • Écrivez une classe MyLibraryException, que chaque méthode qui lève une exception vérifiée utilise. Si le code de la bibliothèque doit gérer tout autre type d’exception, il l’enveloppe dans MyLibraryException.
    • Relancez les exceptions vérifiées en tant que RuntimeException.
    • Dans des cas tels que Integer.valueOf(String), vous devez intercepter l’exception NumberFormatException pour gérer les chaînes qui ne sont pas des entiers, à moins que vous ne puissiez garantir que les chaînes sont toujours des entiers.

    Résultats

    Cela conduit à divers résultats problématiques :

    • Si vous utilisez plusieurs bibliothèques (ou des bibliothèques qui dépendent de bibliothèques), même si vous pouviez réellement faire quelque chose pour gérer le problème racine, il peut être enfoui dans un nombre inconnaissable de classes d’enveloppe d’exception de différentes bibliothèques dans la trace de pile.
    • Dans un programme d’une complexité réelle et d’une chaîne de dépendances non triviale, il est tout simplement impossible d’utiliser un modèle de codage unique pour la gestion des exceptions. Vous devez utiliser plusieurs stratégies.
    • Différents développeurs – comme chaque problème de codage – ont leur propre façon de gérer cela, qu’ils peuvent soutenir comme « la meilleure ». Bonne chance pour obtenir une gestion cohérente des exceptions sur une base de code de taille décente et un roulement d’équipe.

    L’une des principales raisons pour lesquelles j’aime aller

    En fin de compte, c’est pourquoi j’aime l’utilisation de Go de panique et de report à la place des exceptions vérifiées. Il présente des avantages indéniables :

    • Vous n’êtes jamais obligé d’attraper quoi que ce soit – en fait, il n’y a même pas d’instruction catch.
    • Defer ne se limite pas à la gestion des exceptions, il peut également être utilisé pour simplement s’assurer qu’un certain nombre de ressources sont fermées avant que la portée ne se termine.
    • Vous pouvez introduire des portées avec des fonctions en ligne pour contrôler à quel code une instruction defer s’applique – je trouve cela particulièrement utile dans les tests unitaires, où je veux une seule fonction de test pour vérifier que chacune des paniques dans la fonction testée se produit uniquement lorsqu’elles sont Supposé.

    Qu’en est-il des autres langues

    Et si vous utilisiez un langage comme Java ? Je pense qu’une bonne solution consiste à écrire une classe Try qui a diverses méthodes statiques, chacune représentant une situation particulière. Les noms des méthodes doivent être suffisamment descriptifs pour ne pas avoir à lire constamment la documentation une fois que vous avez compris.

    Si vous avez besoin d’une situation try/catch imbriquée, elle devient des appels de méthode imbriqués. Fondamentalement, il offre une solution fonctionnelle au problème et constitue une utilisation particulièrement bonne des lambdas. Je trouve qu’un code fonctionnel bien écrit est presque toujours plus concis, plus facile à lire et plus facile à tester (car il a moins de contexte et est généralement exempt du problème de la « banana gorilla jungle »).

    Quelques exemples Essayez les méthodes de classe

    Tout d’abord, nous avons besoin de quelques interfaces fonctionnelles (interfaces compatibles avec les lambdas) :

    /**
     * A functional interface for executing logic with no arguments, a side effect.
     */
    @FunctionalInterface
    public interface TryTo {
        void execute() throws Throwable;
    }
    
    /**
     * A version of {@link Supplier} that allows throwing any kind of exception.
     * @param <T> the type to return
     */
    @FunctionalInterface
    public interface TrySupplier<T> {
        T get() throws Throwable;
    }

    La classe Try peut alors utiliser les interfaces fonctionnelles ci-dessus :

    public final class Try { // Execute a function, and if it throws, wrap it in RuntimeException public static void to( final TryTo fn ) { Objects.requireNonNull(fn, "fn"); try { fn.execute(); } catch (final RuntimeException fne) { throw fne; } catch (final Throwable fnt) { throw new RuntimeException(fnt); } } /** * Combine a number of TryTo instances into a single TryTo that executes each instance in turn. * When the combined TryTo is executed, if any instance throws an exception, the remaining instances are not executed. * * @param first first instance * @param more additional instances * @return joined instance */ public static TryTo all( final TryTo first, final TryTo... more ) { Objects.requireNonNull(first, "first"); Objects.requireNonNull(more, "more"); final List<TryTo> allFuncs = Helpers.toCollectionOf(new LinkedList<>(), first, more); allFuncs.forEach(Objects::requireNonNull); return () -> { for (final TryTo func : allFuncs) { func.execute(); } }; } /** * Return a value that may be null, or throw an unchecked exception * * @param <T> type to return * @param fn function that may throw an exception * @return possibly null source result * @throws RuntimeException if an exception occurs */ public static <T> T get( final TrySupplier<T> fn ) { Objects.requireNonNull(fn, "fn"); try { return fn.get(); } catch (final RuntimeException e) { throw e; } catch (final Throwable t) { throw new RuntimeException } } /** * Return a value that cannot be null, or throw an unchecked exception * * @param <T> type to return * @param fn function that may throw an exception * @param defaultValue default non-null value * @return result from fn or defaultValue if fn returns null * @throws NullPointerException if defaultValue is null * @throws RuntimeException if an exception occurs */ public static <T> T getDefault( final TrySupplier<T> fn, final T defaultValue ) { Objects.requireNonNull(fn, "fn"); Objects.requireNonNull(defaultValue, "defaultValue"); try { return Optional.ofNullable(fn.get()).orElse(defaultValue); } catch (final RuntimeException e) { throw e; } catch (final Throwable t) { throw new RuntimeException } } }

    Et voici quelques extraits de code réels qui appellent certaines des méthodes ci-dessus :

        // This method is part of a class that abstracts reading and writing fields of a Java class
    
        /**
         * set the value of the field on the given instance
         * 
         * @param instance the instance to set the value of
         * @param value the value to set
         */
        public void set(final Object instance, final Object value) {
            Try.to(() -> setter.invoke(instance, value));
        }
    
        // The next two methods are part of a class that adapts a JDBC ResultSet into an Iterator.
        // The class is also AutoCloseable since a ResultSet needs to be closed.
    
        public boolean hasNext() {
            // Allow user to call hasNext multiple times in a row
            if (mode == IterationMode.HAS_NEXT) {
                return true;
            }
    
            if (mode == IterationMode.DOES_NOT_HAVE_NEXT) {
                return false;
            }
    
            final boolean hasNext = Try.get(rs::next).booleanValue();
            mode = hasNext ? IterationMode.HAS_NEXT : IterationMode.DOES_NOT_HAVE_NEXT;
          
            return hasNext;
        }
    
        // Close the result set and statement
        @Override
        public void close() {
            Try.to(
                Try.all(
                    rs::close,
                    stmt::close
                )
            );
        }

    Conclusion

    Quand j’ai le choix de la langue, j’utilise Go. Pas seulement à cause de la panique/du report, mais aussi parce que la communauté Go épouse la simplicité, moins de code, de petites bibliothèques et évite généralement les grands frameworks.

    Si je dois utiliser Java mais que j’ai le choix, je choisirais d’utiliser une classe Try et de ne pas utiliser de grands frameworks comme Spring et JPA. le code et la mémoire sont utilisés.

    De plus, si un microservice n’a que quelques milliers de lignes de code avec une poignée de requêtes, pourquoi avez-vous besoin de ces grands frameworks de toute façon ?

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.