Si les déclarations doivent être en méthode interne ou externe?

17

Lequel de ces modèles est le meilleur? Quels sont les avantages et les inconvénients de chacun? Lequel utiliseriez-vous? Toute autre suggestion sur la façon de traiter des méthodes comme celle-ci est appréciée.

Il est raisonnable de supposer que Draw () est le seul endroit à partir duquel les autres méthodes de dessin sont appelées. Cela doit s'étendre à de nombreuses autres méthodes Draw * et propriétés Show *, et pas seulement aux trois indiquées ici.

public void Draw()
{
    if (ShowAxis)
    {
        DrawAxis();
    }

    if (ShowLegend)
    {
        DrawLegend();
    }

    if (ShowPoints && Points.Count > 0)
    {
        DrawPoints();
    }
}

private void DrawAxis()
{
    // Draw things.
}

private void DrawLegend()
{
    // Draw things.
}

private void DrawPoints()
{
    // Draw things.
}

Ou

public void Draw()
{
    DrawAxis();
    DrawLegend();
    DrawPoints();
}

private void DrawAxis()
{
    if (!ShowAxis)
    {
        return;
    }

    // Draw things.
}

private void DrawLegend()
{
    if (!ShowLegend)
    {
        return;
    }

    // Draw things.
}

private void DrawPoints()
{
    if (!ShowPoints ||  Points.Count <= 0))
    {
        return;
    }

    // Draw things.
}
mjcopple
la source
Pour plus de référence, j'ai posé cette question sur SO - stackoverflow.com/questions/2966216/…
Tesserex
1
Chaque fois que je vois une phrase comme "Cela doit s'étendre à bien d'autres ...", je pense immédiatement "Cela devrait être une boucle".
Paul Butcher

Réponses:

28

Je ne pense pas que vous puissiez avoir une règle générale pour ce genre de chose, cela dépend de la situation.

Dans ce cas, je suggérerais d'avoir les clauses if en dehors des méthodes car les noms des méthodes Draw impliquent qu'elles dessinent simplement les choses sans conditions spéciales.

Si vous constatez que vous devez effectuer les vérifications avant d'appeler les méthodes à de nombreux endroits, vous souhaiterez peut-être placer la vérification à l'intérieur des méthodes et les renommer pour clarifier que cela se produit.

Brian Reichle
la source
7

Je dis le deuxième.

Les méthodes doivent avoir le même niveau d'abstraction.

Dans le deuxième exemple, la responsabilité de la Draw()méthode consiste à agir comme un contrôleur, en appelant chacune des méthodes de dessin individuelles. Tout le code de cette Draw()méthode est au même niveau d'abstraction. Cette directive entre en jeu dans les scénarios de réutilisation. Par exemple, si vous étiez tenté de réutiliser la DrawPoints()méthode dans une autre méthode publique (appelons-la Sketch()), vous n'auriez pas besoin de répéter la clause guard (l'instruction if décidant de dessiner ou non les points).

Dans le premier exemple, cependant, la Draw()méthode est chargée de déterminer s'il faut appeler chacune des méthodes individuelles, puis d'appeler ces méthodes. Draw()a quelques méthodes de bas niveau qui fonctionnent, mais il délègue d'autres travaux de bas niveau à d'autres méthodes, et a donc du Draw()code à différents niveaux d'abstraction. Pour réutiliser DrawPoints()dans Sketch(), vous devez également répliquer la clause de garde Sketch().

Cette idée est discutée dans le livre de Robert C. Martin "Clean Code", que je recommande fortement.

néontapir
la source
1
Bon produit. Pour répondre à un point soulevé dans une autre réponse, je suggérerais de changer les noms de DrawAxis()et. Al. pour indiquer que leur exécution est conditionnelle, peut-être TryDrawAxis(). Sinon, vous devez naviguer de la Draw()méthode à chaque sous-méthode individuelle pour confirmer son comportement.
Josh Earl
6

Mon opinion sur le sujet est assez controversée, mais restez avec moi, car je crois que tout le monde est d'accord sur le résultat final. J'ai juste une approche différente pour y arriver.

Dans mon article Function Hell , j'explique pourquoi je n'aime pas diviser les méthodes juste pour créer des méthodes plus petites. Je ne les sépare que lorsque je sais , ils seront réutilisés, ou bien sûr, quand je pourrai les réutiliser.

Le PO a déclaré:

Il est raisonnable de supposer que Draw () est le seul endroit à partir duquel les autres méthodes de dessin sont appelées.

Cela m'amène à une troisième option (intermédiaire) non mentionnée. Je crée des «blocs de code» ou des «paragraphes de code» pour lesquels d'autres créeraient des fonctions.

public void Draw()
{
    // Draw axis.
    if (ShowAxis)
    {
        // Drawing code ...
    }

    // Draw legend.
    if (ShowLegend)
    {
        // Drawing code ...
    }

    // Draw points.
    if (ShowPoints && Points.Count > 0)
    {
        // Drawing code ...
    }
}

Le PO a également déclaré:

Cela doit s'étendre à de nombreuses autres méthodes Draw * et propriétés Show *, et pas seulement aux trois illustrées ici.

... donc cette méthode grandira très rapidement. Presque tout le monde convient que cela diminue la lisibilité. À mon avis, la bonne solution n'est pas seulement de diviser en plusieurs méthodes, mais de diviser le code en classes réutilisables. Ma solution ressemblerait probablement à quelque chose comme ça.

private void Initialize()
{               
    // Create axis.
    Axe xAxe = new Axe();
    Axe yAxe = new Axe();

    _drawObjects = new List<Drawable>
    {
        xAxe,
        yAxe,
        new Legend(),
        ...
    }
}

public void Draw()
{
    foreach ( Drawable d in _drawObjects )
    {
        d.Draw();
    }
}

Bien entendu, il faudrait encore passer des arguments, etc.

Steven Jeuris
la source
Bien que je ne sois pas d'accord avec vous sur l'enfer de la fonction, votre solution est (à mon humble avis) la bonne, et celle qui résulterait d'une bonne application de Clean Code & SRP.
Paul Butcher
4

Pour les cas où vous vérifiez un champ qui contrôle s'il faut ou non dessiner, il est logique de l'avoir à l'intérieur Draw(). Lorsque cette décision devient plus compliquée, j'ai tendance à préférer cette dernière (ou à la diviser). Si vous avez besoin de plus de flexibilité, vous pouvez toujours l'étendre.

private void DrawPoints()
{
    if (ShouldDrawPoints())
    {
        DoDrawPoints();
    }
}

protected void ShouldDrawPoints()
{
    return ShowPoints && Points.Count > 0;
}

protected void DoDrawPoints()
{
    // Draw things.
}

Notez que les méthodes supplémentaires sont protégées, ce qui permet aux sous-classes d'étendre ce dont elles ont besoin. Cela vous permet également de forcer le dessin pour les tests ou pour toute autre raison que vous pourriez avoir.

David Harkness
la source
C'est bien: tout ce qui se répète, c'est dans sa propre méthode. Vous pouvez maintenant même ajouter une DrawPointsIfItShould()méthode qui raccourcit le if(should){do}:)
Konerak
4

De ces deux alternatives, je préfère la première version. Ma raison est que je veux une méthode pour faire ce que son nom implique, sans dépendances cachées. DrawLegend devrait dessiner une légende, pas peut-être dessiner une légende.

La réponse de Steven Jeuris est cependant meilleure que les deux versions de la question.

user281377
la source
3

Au lieu d'avoir des Show___propriétés individuelles pour chaque partie, je définirais probablement un champ de bits. Cela simplifierait un peu les choses pour:

[Flags]
public enum DrawParts
{
    Axis = 1,
    Legend = 2,
    Points = 4,
    // More...
}

public class MyClass
{
    public void Draw() {
        if (VisibleParts.HasFlag(DrawPart.Axis))   DrawAxis();
        if (VisibleParts.HasFlag(DrawPart.Legend)) DrawLegend();
        if (VisibleParts.HasFlag(DrawPart.Points)) DrawPoints();
        // More...
    }

    public DrawParts VisibleParts { get; set; }
}

En dehors de cela, je pencherais pour vérifier la visibilité dans la méthode extérieure, pas intérieure. C'est, après tout, DrawLegendet non DrawLegendIfShown. Mais cela dépend en quelque sorte du reste de la classe. S'il existe d'autres endroits qui appellent cette DrawLegendméthode et qu'ils doivent ShowLegendégalement vérifier , je déplacerais probablement le chèque dans DrawLegend.

munificent
la source
2

J'irais avec la première option - avec le "si" en dehors de la méthode. Il décrit mieux la logique suivie, plus vous donne la possibilité de dessiner réellement un axe, par exemple, dans les cas où vous voulez en dessiner un quel que soit un paramètre. De plus, il supprime la surcharge d'un appel de fonction supplémentaire (en supposant qu'il n'est pas intégré), qui peut s'accumuler si vous optez pour la vitesse (votre exemple semble qu'il peut simplement dessiner un graphique où il peut ne pas être un facteur, mais dans une animation ou jeu, il pourrait être).

GrandmasterB
la source
1

En général, je préfère que les niveaux de code inférieurs supposent que les conditions préalables sont remplies et effectuent le travail qu'ils sont censés faire, les vérifications étant effectuées plus haut dans la pile des appels. Cela a l'avantage secondaire d'économiser des cycles en ne faisant pas de vérifications redondantes, mais cela signifie également que vous pouvez écrire du bon code comme celui-ci:

int do_something()
{
    sometype* x;
    if(!(x = sometype_new())) return -1;
    foo(x);
    bar(x);
    baz(x);
    return 0;
}

au lieu de:

int do_something()
{
    sometype x* = sometype_new();
    if(ERROR == foo(x)) return -1;
    if(ERROR == bar(x)) return -1;
    if(ERROR == baz(x)) return -1;
    return 0;
}

Et bien sûr, cela devient encore plus désagréable si vous appliquez une sortie unique, avez de la mémoire que vous devez libérer, etc.

Cercerilla
la source
0

Tout dépend du contexte:

void someThing(var1, var2)
{
    // If input fails validation then return quickly.
    if (!isValid(var1) || !isValid(var2))
    {   return;
    }


    // Otherwise do what is logical and makes the code easy to read.
    if (doTaskConditionOK())
    {   doTask();
    }

    // Return early if it is logical
    // This is OK in C++ but not C like languages.
    // You have to be careful of cleanup in C like languages while RIAA will do
    // that auto-magically in C++. 
    if (allFinished)
    {   return;
    }

    doAlternativeIfNotFinished();

    // -- Alternative to the above for C
    if (!allFinished)
    {   
        doAlternativeIfNotFinished();
    }

} 
Martin York
la source