[JAVA] Un petit effort pour éliminer les if-else compliqués

Contexte

Lors de la révision de code pour les débutants en programmation, nous constatons souvent une imbrication profonde de if-else. Même les outils métriques tels que SonarQube se fâchent contre "la complexité cyclomatique est trop élevée".

Si vous essayez de réduire complètement le branchement conditionnel, vous parlerez souvent de modèles de conception tels que le modèle de stratégie et le modèle d'état, mais même si vous n'allez pas aussi loin, c'est une histoire que le code sera plus facile à voir avec un peu de soin. .. Pour les débutants.

Exemple

Vous trouverez ci-dessous le code permettant de déterminer si l'article est disponible à l'achat.

public boolean canPurchase() {
    boolean result;

    //Êtes-vous connecté
    if (isLoggedIn()) {
        //Est-ce en stock
        if (existsStock()) {
            //En cas de paiement à la livraison
            if (isPaymentMethodCash()) {
                result = true;
            //Pour le paiement par crédit
            } else {
                //Votre carte de crédit est-elle enregistrée?
                if (isCreditCardRegistered()) {
                    result = true;
                } else {
                    result = false;
                }
            }
        } else {
            result = false;
        }
    } else {
        result = false;
    }

    return result;
}

Il s'agit d'un exemple typique d'imbrication profonde, et le code est très difficile à suivre. En regardant le code d'une personne inconnue, je sens que je suis conscient du flux de travail normal comme indiqué ci-dessous.

if (1_Système normal) {
    if (2_Système normal) {
        ...
        ...
        ... //Traitement long
        ...
        ...
    } else {
        // 2_Système anormal
        return null;
    }
} else {
    // 1_Système anormal
    throw new SomeException();
}

Cependant, dans des cas «particuliers» tels que non connecté ou en rupture de stock, d'autres conditions (mode de paiement et enregistrement de la carte) ne sont pas pertinentes et la valeur de retour est déterminée sans condition (ou exception). Arrivera). Séparons donc ces cas des autres logiques.

Tout d'abord, je vais séparer les conditions de connexion.

public boolean canPurchase() {

    //Inconditionnellement faux si non connecté
    if (!isLoggedIn()) {
        return false;
    }

    //Est-ce en stock
    if (existsStock()) {
        //En cas de paiement à la livraison
        if (isPaymentMethodCash()) {
            return true;
        //Pour le paiement par crédit
        } else {
            //Votre carte de crédit est-elle enregistrée?
            if (isCreditCardRegistered()) {
                return true;
            } else {
                return false;
            }
        }
    } else {
        return false;
    }
}

Dans le premier code, la variable locale «result» était complétée par une valeur booléenne et finalement «return result», mais cette fois la valeur booléenne est immédiatement «return». Il semble que la méthode de retour lorsque la valeur de retour est fixée sans utiliser de variables temporaires de cette manière est appelée retour anticipé. De plus, comme le code ci-dessus, la méthode de retour anticipé d'un cas spécial au début et d'exclusion de ce cas spécial par la suite est appelée clause de garde. De cette façon, vous n'avez pas à utiliser la clause else et l'imbrication est terminée.

Séparons également les conditions du stock.

public boolean canPurchase() {

    //Inconditionnellement faux si non connecté
    if (!isLoggedIn()) {
        return false;
    }

    //Inconditionnellement faux en cas de rupture de stock
    if (!existsStock()) {
        return false;
    }

    //En cas de paiement à la livraison
    if (isPaymentMethodCash()) {
        return true;
    //Pour le paiement par crédit
    } else {
        //Votre carte de crédit est-elle enregistrée?
        if (isCreditCardRegistered()) {
            return true;
        } else {
            return false;
        }
    }
}

Je pense que l'emboîtement a été éliminé et qu'il est plus facile de suivre le processus. C'est un bon moyen d'améliorer la visibilité du code avec un peu d'effort, alors gardez cela à l'esprit.

** * Portée de la clause de garde ** Si vous allez plus loin ici, ce sera toujours «vrai» dans le cas du paiement à la livraison, vous pouvez donc réduire davantage l'imbrication en appliquant une clause de garde ici également.

public boolean canPurchase() {

    //Inconditionnellement faux si non connecté
    if (!isLoggedIn()) {
        return false;
    }

    //Inconditionnellement faux en cas de rupture de stock
    if (!existsStock()) {
        return false;
    }

    //Inconditionnellement vrai pour le paiement à la livraison
    if (isPaymentMethodCash()) {
        return true;
    }

    //Votre carte de crédit est-elle enregistrée?
    if (isCreditCardRegistered()) {
        return true;
    } else {
        return false;
    }
}

Cependant, cela peut être une question de goût ou de politique, mais je pense personnellement qu'il vaut mieux ne pas le faire. La raison en est que la clause de garde n'est qu'un cas spécial à l'avance, et le paiement à la livraison ou le paiement par carte de crédit sont tous deux des conditions courantes dans des cas normaux. Si les conditions sont égales, je pense que vous devriez les organiser en parallèle avec if-else. De plus, si l'ordre des clauses de garde est modifié comme indiqué ci-dessous, des problèmes surviendront.

/**Mauvais exemple. True est retourné en cas de paiement à la livraison même si vous n'êtes pas connecté**/
public boolean canPurchase() {

    //Inconditionnellement vrai pour le paiement à la livraison
    if (isPaymentMethodCash()) {
        return true;
    }

    //Inconditionnellement faux si non connecté
    if (!isLoggedIn()) {
        return false;
    }

    //Inconditionnellement faux en cas de rupture de stock
    if (!existsStock()) {
        return false;
    }

    //Votre carte de crédit est-elle enregistrée?
    if (isCreditCardRegistered()) {
        return true;
    } else {
        return false;
    }
}

Je pense que la clause de garde doit être maintenue dans la plage qui peut être jugée indépendamment (indépendamment de l'ordre).

Recommended Posts

Un petit effort pour éliminer les if-else compliqués
[Introduction à JSP + Servlet] Une petite animation ♬
Je veux utiliser une petite icône dans Rails