web-dev-qa-db-fra.com

Fonctions d'une ligne appelées une seule fois

Considérons une fonction sans paramètre (edit: pas nécessairement) qui exécute une seule ligne de code et qui n'est appelée qu'une seule fois dans le programme (bien qu'il ne soit pas impossible qu'elle soit à nouveau nécessaire à l'avenir) .

Il pourrait effectuer une requête, vérifier certaines valeurs, faire quelque chose impliquant des expressions rationnelles ... quelque chose d'obscur ou de "hacky".

La raison derrière cela serait d'éviter des évaluations difficilement lisibles:

if (getCondition()) {
    // do stuff
}

getCondition() est la fonction d'une ligne.

Ma question est simplement: est-ce une bonne pratique? Cela me semble bien mais je ne connais pas le long terme ...

122
vemv

Cela dépend de cette seule ligne. Si la ligne est lisible et concise par elle-même, la fonction peut ne pas être nécessaire. Exemple simpliste:

void printNewLine() {
  System.out.println();
}

OTOH, si la fonction donne un bon nom à une ligne de code contenant par exemple une expression complexe et difficile à lire, elle est parfaitement justifiée (pour moi). Exemple artificiel (divisé en plusieurs lignes pour plus de lisibilité ici):

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}
242
Péter Török

Oui, cela peut être utilisé pour satisfaire les meilleures pratiques. Par exemple, il vaut mieux qu'une fonction clairement nommée fasse un peu de travail, même si elle ne fait qu'une ligne, que d'avoir cette ligne de code dans une fonction plus grande et d'avoir besoin d'un commentaire d'une ligne expliquant ce qu'elle fait. En outre, les lignes de code voisines doivent effectuer des tâches au même niveau d'abstraction. Un contre-exemple serait quelque chose comme

startIgnition();
petrolFlag |= 0x006A;
engageChoke();

Dans ce cas, il est certainement préférable de déplacer la ligne médiane dans une fonction judicieusement nommée.

67
Kilian Foth

Je pense que dans de nombreux cas, une telle fonction est un bon style, mais vous pouvez considérer une variable booléenne locale comme alternative dans les cas où vous n'avez pas besoin d'utiliser cette condition quelque part ailleurs, par exemple:

bool someConditionSatisfied = [complex expression];

Cela donnera un indice au lecteur de code et évitera d'introduire une nouvelle fonction.

37
cybevnm

En plus de réponse de Peter , si cette condition doit être mise à jour à un moment donné dans l'avenir, en l'encapsulant de la manière que vous suggérez, vous n'auriez qu'un seul point d'édition.

Suivant l'exemple de Peter, si cela

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}

devient ceci

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isMutant() 
        && (taxPayer.getNumberOfThumbs() > 2 
        || (taxPayer.getAge() > 123 && taxPayer.getEmployer().isXMan()));
}

Vous faites un seul montage et il est mis à jour universellement. En termes de maintenabilité, c'est un plus.

En ce qui concerne les performances, la plupart des compilateurs d'optimisation suppriment l'appel de fonction et insèrent de toute façon le petit bloc de code. L'optimisation de quelque chose comme cela peut en fait réduire la taille du bloc (en supprimant les instructions nécessaires pour l'appel de fonction, le retour, etc.), il est donc normalement sûr de le faire même dans des conditions qui pourraient autrement empêcher l'inline.

23
Stephen

En plus de la lisibilité (ou en complément), cela permet d'écrire des fonctions au niveau d'abstraction approprié.

5
tylermac

Ça dépend. Parfois, il est préférable d'encapsuler une expression dans une fonction/méthode, même s'il ne s'agit que d'une seule ligne. Si c'est compliqué à lire ou si vous en avez besoin à plusieurs endroits, je considère que c'est une bonne pratique. À long terme, il est plus facile à maintenir, car vous avez introduit un point de changement unique et meilleure lisibilité.

Cependant, parfois, c'est juste quelque chose dont vous n'avez pas besoin. Lorsque l'expression est facile à lire de toute façon et/ou apparaît simplement au même endroit, ne l'enveloppez pas.

4
Falcon

Je pense que si vous n'en avez que quelques-uns, alors ça va, mais le problème se pose quand il y en a beaucoup dans votre code. Et lorsque le compilateur s'exécute ou l'interpecteur (selon la langue que vous utilisez), il va à cette fonction en mémoire. Disons donc que vous en avez 3, je ne pense pas que l'ordinateur le remarquera, mais si vous commencez à avoir des centaines de ces petites choses, le système doit enregistrer des fonctions en mémoire qui ne sont appelées qu'une seule fois et qui ne sont pas détruites.

3
WojonsTech

J'ai fait cette chose exactement récemment dans une application que j'ai refactorisée, pour rendre explicite la signification réelle du code sans commentaires:

protected void PaymentButton_Click(object sender, EventArgs e)
    Func<bool> HaveError = () => lblCreditCardError.Text == string.Empty && lblDisclaimer.Text == string.Empty;

    CheckInputs();

    if(HaveError())
        return;

    ...
}
3
joshperry

Il y a déjà beaucoup de bonnes réponses, mais il y a un cas spécial qui mérite d'être mentionné .

Si votre déclaration sur une ligne a besoin d'un commentaire et que vous êtes en mesure d'identifier clairement (ce qui signifie: nommer) son objectif, envisagez d'extraire une fonction tout en améliorant le commentaire dans le document API. De cette façon, vous rendez l'appel de fonction plus facile et plus rapide.

Fait intéressant, la même chose peut être faite s'il n'y a actuellement rien à faire, mais un commentaire rappelant les extensions nécessaires (dans un avenir très proche1)), donc ça,

def sophisticatedHello():
    # todo set up
    say("hello")
    # todo tear down

pourrait aussi bien changé en ce

def sophisticatedHello():
    setUp()
    say("hello")
    tearDown()

1) vous devriez en être sûr (voir YAGNI principe)

0
Wolf

Si le langage le prend en charge, j'utilise généralement des fonctions anonymes étiquetées pour ce faire.

someCondition = lambda p: True if [complicated expression involving p] else False
#I explicitly write the function with a ternary to make it clear this is a a predicate
if (someCondition(p)):
    #do stuff...

À mon humble avis, c'est un bon compromis car il vous offre l'avantage de lisibilité de ne pas avoir l'expression compliquée encombrant la condition if tout en évitant d'encombrer l'espace de noms global/package avec de petites étiquettes jetables. Il a l'avantage supplémentaire que la fonction "définition" est juste là où elle est utilisée, ce qui facilite la modification et la lecture de la définition.

Il ne s'agit pas uniquement de fonctions de prédicat. J'aime également encapsuler des plaques de chaudière répétées dans de petites fonctions comme celle-ci (cela fonctionne particulièrement bien pour la génération de listes Pythonic sans encombrer la syntaxe des crochets). Par exemple, l'exemple simplifié suivant lors de l'utilisation de PIL en python

#goal - I have a list of PIL Image objects and I want them all as grayscale (uint8) numpy arrays
im_2_arr = lambda im: array(im.convert('L')) 
arr_list = [im_2_arr(image) for image in image_list]
0
crasic

Le déplacement de cette ligne dans une méthode bien nommée facilite la lecture de votre code. Beaucoup d'autres l'ont déjà mentionné ("code auto-documenté"). L'autre avantage de le déplacer dans une méthode est qu'il facilite le test unitaire. Quand il est isolé dans sa propre méthode et testé en unité, vous pouvez être sûr que si/quand un bogue est trouvé, il ne sera pas dans cette méthode.

0
Andrew