web-dev-qa-db-fra.com

Comment savoir à quel point mes méthodes devraient être réutilisables?

Je m'occupe de mes propres affaires à la maison et ma femme vient me voir et me dit

Chérie .. Pouvez-vous imprimer toutes les économies de lumière du jour dans le monde pour 2018 dans la console? Je dois vérifier quelque chose.

Et je suis super content parce que c'est ce que j'attendais depuis toute ma vie avec mon Java experience et trouver:

import Java.time.*;
import Java.util.Set;

class App {
    void dayLightSavings() {
        Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(2018, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (2018 == now.getYear()) {
                    int hour = now.getHour();
                    now = now.plusHours(1);
                    if (now.getHour() == hour) {
                        System.out.println(now);
                    }
                }
            }
        );
    }
}

Mais ensuite, elle dit qu'elle me testait simplement si j'étais un ingénieur en logiciels formé à l'éthique, et me dit que je ne le suis pas depuis (tiré de ici ) ..

Il convient de noter qu'aucun ingénieur logiciel formé à l'éthique ne consentirait jamais à écrire une procédure DestroyBaghdad. L'éthique professionnelle de base l'obligerait à la place à écrire une procédure DestroyCity, à laquelle Bagdad pourrait être donné comme paramètre.

Et je suis comme, d'accord, ok, tu m'as eu. Passez tout année vous aimez, c'est parti:

import Java.time.*;
import Java.util.Set;

class App {
    void dayLightSavings(int year) {
        Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(year, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (year == now.getYear()) {
                    // rest is same..

Mais comment savoir combien (et quoi) paramétrer? Après tout, elle pourrait dire ..

  • elle veut passer un formateur de chaînes personnalisé, peut-être qu'elle n'aime pas le format dans lequel j'imprime déjà: 2018-10-28T02:00+01:00[Arctic/Longyearbyen]

void dayLightSavings(int year, DateTimeFormatter dtf)

  • elle ne s'intéresse qu'à certaines périodes de mois

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd)

  • elle s'intéresse à certaines périodes horaires

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd, int hourStart, int hourend)

Si vous cherchez une question concrète:

Si destroyCity(City city) est meilleure que destroyBaghdad(), takeActionOnCity(Action action, City city) est-elle encore meilleure? Pourquoi pourquoi pas?

Après tout, je peux d'abord l'appeler avec Action.DESTROY Puis Action.REBUILD, N'est-ce pas?

Mais agir sur les villes ne me suffit pas, qu'en est-il de takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)? Après tout, je ne veux pas appeler:

takeActionOnCity(Action.DESTORY, City.BAGHDAD);

puis

takeActionOnCity(Action.DESTORY, City.ERBIL);

et ainsi de suite quand je peux faire:

takeActionOnGeographicArea(Action.DESTORY, Country.IRAQ);

p.s. J'ai seulement construit ma question autour de la citation que j'ai mentionnée, je n'ai rien contre aucun pays, religion, race ou quoi que ce soit dans le monde. J'essaie juste de faire un point.

136
Koray Tugay

Ce sont des tortues tout le long.

Ou des abstractions dans ce cas.

Le codage de bonnes pratiques est quelque chose qui peut être appliqué à l'infini, et à un moment donné, vous faites un résumé dans un souci d'abstrait, ce qui signifie que vous êtes allé trop loin. Trouver cette ligne n'est pas quelque chose qui est facile à mettre dans une règle empirique, car cela dépend beaucoup de votre environnement.

Par exemple, nous avons eu des clients qui étaient connus pour demander d'abord des applications simples, mais ensuite pour des extensions. Nous avons également eu des clients qui leur demandent ce qu'ils veulent et qui ne nous reviennent généralement pas pour une expansion.
Votre approche variera selon le client. Pour le premier client, il faudra payer pour à titre préventif résumer le code car vous êtes raisonnablement certain que vous devrez revoir ce code à l'avenir. Pour le deuxième client, vous ne voudrez peut-être pas investir cet effort supplémentaire si vous vous attendez à ce qu'il ne souhaite à aucun moment étendre l'application (remarque: cela ne signifie pas que vous ne suivez aucune bonne pratique, mais simplement que vous évitez de faire plus que nécessaire actuellement nécessaire.

Comment savoir quelles fonctionnalités mettre en œuvre?

La raison pour laquelle je mentionne ce qui précède est parce que vous êtes déjà tombé dans ce piège:

Mais comment savoir combien (et quoi) paramétrer? Après tout, pourrait-elle dire .

"Elle pourrait dire" n'est pas une exigence commerciale actuelle. C'est une supposition sur une future exigence commerciale. En règle générale, ne vous basez pas sur des suppositions, développez uniquement ce qui est actuellement requis.

Cependant, le contexte s'applique ici. Je ne connais pas ta femme. Peut-être avez-vous bien évalué qu'elle voudra en fait cela. Mais vous devez toujours confirmer avec le client que c'est bien ce qu'il veut, car sinon vous allez passer du temps à développer une fonctionnalité que vous êtes ne va jamais finir par utiliser.

Comment savoir quelle architecture mettre en œuvre?

C'est plus compliqué. Le client ne se soucie pas du code interne, vous ne pouvez donc pas lui demander s'il en a besoin. Leur opinion sur la question est pour la plupart hors de propos.

Cependant, vous pouvez toujours confirmer la nécessité de le faire en en posant les bonnes questions au client. Au lieu de poser des questions sur l'architecture, interrogez-les sur leurs attentes en matière de développement futur ou d'extensions de la base de code. Vous pouvez également demander si l'objectif actuel a une échéance, car vous ne pourrez peut-être pas mettre en œuvre votre architecture sophistiquée dans le délai nécessaire.

Comment savoir quand extraire davantage mon code?

Je ne sais pas où je l'ai lu (si quelqu'un le sait, faites-le-moi savoir et je vous en donnerai le crédit), mais une bonne règle de base est que les développeurs doivent compter comme un homme des cavernes: one , deux .

enter image description hereXKCD # 764

En d'autres termes, lorsqu'un certain algorithme/modèle est utilisé pendant une troisième fois, il doit être résumé de manière à être réutilisable (= utilisable beaucoup fois ).

Juste pour être clair, je n'implique pas que vous ne devriez pas écrire de code réutilisable quand il n'y a que deux instances de l'algorithme utilisé. Bien sûr, vous pouvez également résumer cela, mais la règle devrait être que pour trois cas, vous devez abstraire.

Encore une fois, cela tient compte de vos attentes. Si vous savez déjà que vous avez besoin de trois instances ou plus, vous pouvez bien sûr immédiatement résumer. Mais si vous seulement devinez que vous voudrez peut-être l'implémenter plusieurs fois, l'exactitude de l'implémentation de l'abstraction dépend entièrement de l'exactitude de votre supposition.
Si vous avez bien deviné, vous vous êtes fait gagner du temps. Si vous avez mal deviné, vous avez perdu une partie de votre temps et de vos efforts et possiblement compromis votre architecture pour mettre en œuvre quelque chose dont vous n'avez finalement pas besoin.

Si destroyCity(City city) est meilleure que destroyBaghdad(), takeActionOnCity(Action action, City city) est-elle encore meilleure? Pourquoi pourquoi pas?

Cela dépend beaucoup de plusieurs choses:

  • Y a-t-il plusieurs actions qui peuvent être prises sur n'importe quelle ville?
  • Ces actions peuvent-elles être utilisées de manière interchangeable? Parce que si les actions "destroy" et "rebuild" ont des exécutions complètement différentes, il est inutile de fusionner les deux dans une seule méthode takeActionOnCity.

Sachez également que si vous résumez récursivement cela, vous allez vous retrouver avec une méthode si abstraite que ce n'est rien de plus qu'un conteneur pour exécuter une autre méthode, ce qui signifie que vous avez rendu votre méthode non pertinente et dénuée de sens.
Si tout votre corps de méthode takeActionOnCity(Action action, City city) finit par n'être rien de plus que action.TakeOn(city);, vous devriez vous demander si la méthode takeActionOnCity a vraiment un but ou n'est pas ' t juste une couche supplémentaire qui n'ajoute rien de valeur.

Mais agir sur les villes ne me suffit pas, qu'en est-il de takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)?

La même question apparaît ici:

  • Avez-vous un cas d'utilisation pour les régions géographiques?
  • L'exécution d'une action sur une ville et une région est-elle la même?
  • Peut-on prendre des mesures sur n'importe quelle région/ville?

Si vous pouvez définitivement répondre "oui" aux trois, alors une abstraction est justifiée.

116
Flater

Entraine toi

Il s'agit de Software Engineering SE, mais la création de logiciels est beaucoup plus de l'art que de l'ingénierie. Il n'y a pas d'algorithme universel à suivre ou de mesure à prendre pour déterminer la quantité de réutilisabilité suffisante. Comme pour tout, plus vous pratiquez la conception de programmes, mieux vous y arriverez. Vous aurez une meilleure idée de ce qui est "suffisant", car vous verrez ce qui ne va pas et comment cela se passe mal lorsque vous paramétrez trop ou trop peu.

Ce n'est pas très utile maintenant , alors qu'en est-il des directives?

Revenez à votre question. Il y a beaucoup de "elle pourrait dire" et "je pourrais". Beaucoup de déclarations théorisant sur certains besoins futurs. Les humains hésitent à prédire l'avenir. Et vous (très probablement) êtes un humain. Le problème écrasant de la conception de logiciels essaie de rendre compte d'un avenir que vous ne connaissez pas.

Ligne directrice 1: Vous n'en aurez pas besoin

Sérieusement. Arrête. Plus souvent qu'autrement, ce problème futur imaginé n'apparaît pas - et il n'apparaîtra certainement pas comme vous l'avez imaginé.

Ligne directrice 2: Coût/Avantage

Cool, ce petit programme vous a pris quelques heures pour écrire peut-être? Et si votre femme le fait revient et demande ces choses? Dans le pire des cas, vous passez encore quelques heures à lancer un autre programme pour le faire. Dans ce cas, ce n'est pas trop de temps pour rendre ce programme plus flexible. Et cela n'augmentera pas beaucoup la vitesse d'exécution ou l'utilisation de la mémoire. Mais les programmes non triviaux ont des réponses différentes. Différents scénarios ont des réponses différentes. À un certain point, les coûts ne valent clairement pas l'avantage même avec des compétences de révélation imparfaites.

Ligne directrice 3: Focus sur les constantes

Revenez à la question. Dans votre code d'origine, il y a beaucoup d'entiers constants. 2018, 1. Entiers constants, chaînes constantes ... Ce sont les choses les plus susceptibles d'avoir besoin d'être pas constantes. Mieux encore, ils ne prennent que peu de temps à paramétrer (ou du moins à définir comme constantes réelles). Mais une autre chose à se méfier est constante comportement. Le System.out.println par exemple. Ce genre d'hypothèse sur l'utilisation tend à être quelque chose qui change à l'avenir et a tendance à être très coûteux à corriger. Non seulement cela, mais IO comme cela rend la fonction impure (avec le fuseau horaire récupérant quelque peu). Le paramétrage de ce comportement peut rendre la fonction plus pure conduisant à une flexibilité et une testabilité accrues. Gros avantages avec un minimum coût (surtout si vous effectuez une surcharge qui utilise System.out par défaut).

44
Telastyn

Premièrement: Aucun développeur de logiciels soucieux de la sécurité n'écrirait une méthode DestroyCity sans passer de jeton d'autorisation pour quelque raison que ce soit.

Moi aussi, je peux écrire n'importe quoi comme un impératif qui a une sagesse évidente sans qu'il soit applicable dans un autre contexte. Pourquoi est-il nécessaire d'autoriser une concaténation de chaînes?

Deuxièmement: Tout le code une fois exécuté doit être entièrement spécifié.

Peu importe que la décision ait été codée en dur ou reportée à une autre couche. À un moment donné, il y a un morceau de code dans un langage qui sait à la fois ce qui doit être détruit et comment l'instruire.

Cela pourrait être dans le même fichier objet destroyCity(xyz), et cela pourrait être dans un fichier de configuration: destroy {"city": "XYZ"}", ou il peut s'agir d'une série de clics et d'appuis sur une interface utilisateur.

Troisièmement:

Chérie .. Pouvez-vous imprimer toutes les économies de lumière du jour dans le monde pour 2018 dans la console? Je dois vérifier quelque chose.

est un ensemble d'exigences très différent pour:

elle souhaite passer un formateur de chaînes personnalisé, ... intéressé uniquement par certaines périodes de mois, ... [et] intéressé par certaines périodes d'heure ...

Maintenant, le deuxième ensemble d'exigences rend évidemment un outil plus flexible. Il a un public cible plus large et un domaine d'application plus large. Le danger ici est que l'application la plus flexible au monde est en fait un compilateur de code machine. C'est littéralement un programme si générique qu'il peut construire n'importe quoi pour faire de l'ordinateur ce dont vous avez besoin (dans les limites de son matériel).

D'une manière générale, les personnes qui ont besoin d'un logiciel ne veulent pas quelque chose de générique; ils veulent quelque chose de spécifique. En donnant plus d'options, vous compliquez en fait leur vie. S'ils voulaient cette complexité, ils utiliseraient plutôt un compilateur, ne vous le demanderaient pas.

Votre femme demandait des fonctionnalités et vous a sous-spécifié ses exigences. Dans ce cas, c'était apparemment exprès, et en général c'est parce qu'ils ne savent pas mieux. Sinon, ils auraient juste utilisé le compilateur eux-mêmes. Donc, le premier problème est que vous n'avez pas demandé plus de détails sur ce qu'elle voulait faire. Voulait-elle diriger cela pendant plusieurs années différentes? La voulait-elle dans un fichier CSV? Vous n'avez pas découvert quelles décisions elle voulait prendre elle-même et ce qu'elle vous demandait de comprendre et de décider pour elle. Une fois que vous avez déterminé quelles décisions doivent être différées, vous pouvez comprendre comment communiquer ces décisions via des paramètres (et d'autres moyens configurables).

Cela étant dit, la plupart des clients ne communiquent pas, présument ou ignorent certains détails (aka. Décisions) qu'ils aimeraient vraiment faire eux-mêmes ou qu'ils ne voulaient vraiment pas faire (mais cela semble génial). C'est pourquoi les méthodes de travail comme PDSA (planifier-développer-étudier-agir) sont importantes. Vous avez planifié le travail conformément aux exigences, puis vous avez développé un ensemble de décisions (code). Il est maintenant temps de l'étudier, seul ou avec votre client et d'apprendre de nouvelles choses, et celles-ci éclairent votre réflexion à l'avenir. Enfin, agissez en fonction de vos nouvelles perspectives - mettez à jour les exigences, affinez le processus, obtenez de nouveaux outils, etc. ... Puis recommencez la planification. Cela aurait révélé des exigences cachées au fil du temps et prouve les progrès de nombreux clients.

Finalement. Votre temps est important ; c'est très réel et très fini. Chaque décision que vous prenez implique de nombreuses autres décisions cachées, et c'est à cela que sert le développement de logiciels. Retarder une décision en tant qu'argument peut rendre la fonction actuelle plus simple, mais cela rend quelque part plus complexe. Cette décision est-elle pertinente à cet autre endroit? Est-ce plus pertinent ici? Quelle décision doit-elle vraiment prendre? Vous décidez ceci; c'est du codage. Si vous répétez fréquemment des ensembles de décisions, il y a un avantage très réel à les codifier dans une abstraction. XKCD a une perspective utile ici. Et cela est pertinent au niveau d'un système, que ce soit une fonction, un module, un programme, etc.

Le conseil au début implique que les décisions que votre fonction n'a pas le droit de prendre doivent être transmises comme argument. Le problème est qu'une fonction DestroyBaghdad peut en fait être la fonction qui a ce droit.

27
Kain0_0

Il y a beaucoup de réponses longues ici, mais honnêtement, je pense que c'est super simple

Toute information codée en dur que vous avez dans votre fonction qui ne fait pas partie du nom de la fonction doit être un paramètre.

donc dans votre fonction

class App {
    void dayLightSavings() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(zoneId -> {
            LocalDateTime dateTime = LocalDateTime.of(LocalDate.of(2018, 1, 1), LocalTime.of(0, 0, 0));
            ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
            while (2018 == now.getYear()) {
                int hour = now.getHour();
                now = now.plusHours(1);
                if (now.getHour() == hour) {
                    System.out.println(now);
                }
            }
        });
    }
}

Vous avez:

The zoneIds
2018, 1, 1
System.out

Je déplacerais donc tous ces paramètres sous une forme ou une autre. Vous pourriez faire valoir que les zoneIds sont implicites dans le nom de la fonction, peut-être voudriez-vous le faire encore plus en le changeant en "DaylightSavingsAroundTheWorld" ou quelque chose

Vous n'avez pas de chaîne de format, donc en ajouter une est une demande de fonctionnalité et vous devez référer votre femme à l'instance Jira de votre famille. Il peut être mis en attente et priorisé lors de la réunion appropriée du comité de gestion de projet.

4
Ewan

En bref, ne concevez pas la réutilisation de votre logiciel car aucun utilisateur final ne se soucie si vos fonctions peuvent être réutilisées. Au lieu de cela, ingénieur pour compréhensibilité de la conception - mon code est-il facile à comprendre pour quelqu'un d'autre ou pour mon futur moi oublieux? - et flexibilité de conception - quand je dois inévitablement corriger des bogues, ajouter des fonctionnalités ou autrement modifier des fonctionnalités, dans quelle mesure mon code résistera-t-il aux changements? La seule chose qui importe à votre client, c'est la rapidité avec laquelle vous pouvez répondre lorsqu'elle signale un bug ou demande un changement. Le fait de poser ces questions sur votre conception a tendance à donner lieu à un code réutilisable, mais cette approche vous permet de rester concentré sur l'évitement des vrais problèmes auxquels vous serez confronté pendant la durée de vie de ce code afin que vous puissiez mieux servir l'utilisateur final plutôt que de poursuivre des tâches élevées et peu pratiques. des idéaux "d'ingénierie" pour plaire aux barbes.

Pour quelque chose d'aussi simple que l'exemple que vous avez fourni, votre implémentation initiale est correcte en raison de sa petite taille, mais cette conception simple deviendra difficile à comprendre et fragile si vous essayez de brouiller trop de flexibilité fonctionnelle (par opposition à la flexibilité de conception) dans une procédure. Ci-dessous est mon explication de mon approche préférée pour concevoir des systèmes complexes pour la compréhensibilité et la flexibilité qui j'espère démontrera ce que je veux dire par eux. Je n'emploierais pas cette stratégie pour quelque chose qui pourrait être écrit en moins de 20 lignes en une seule procédure, car quelque chose d'aussi petit répond déjà à mes critères de compréhensibilité et de flexibilité.


Objets, pas procédures

Plutôt que d'utiliser des classes comme des modules de la vieille école avec un tas de routines que vous appelez pour exécuter les choses que votre logiciel devrait faire, envisagez de modéliser le domaine comme des objets qui interagissent et coopèrent pour accomplir la tâche à accomplir. Les méthodes dans un paradigme orienté objet ont été initialement créées pour être des signaux entre les objets afin que Object1 Puisse dire à Object2 De faire ce qu'il veut, quoi qu'il en soit, et éventuellement de recevoir un signal de retour. En effet, le paradigme orienté objet consiste intrinsèquement à modéliser vos objets de domaine et leurs interactions plutôt qu'une manière sophistiquée d'organiser les mêmes anciennes fonctions et procédures du paradigme impératif. Dans le cas de l'exemple void destroyBaghdad, Au lieu d'essayer d'écrire une méthode générique sans contexte pour gérer la destruction de Bagdad ou toute autre chose (qui pourrait rapidement devenir complexe, difficile à comprendre et fragile), chaque chose qui peut être détruite doit être responsable de la compréhension de la façon de se détruire. Par exemple, vous avez une interface qui décrit le comportement des choses qui peuvent être détruites:

interface Destroyable {
    void destroy();
}

Ensuite, vous avez une ville qui implémente cette interface:

class City implements Destroyable {
    @Override
    public void destroy() {
        ...code that destroys the city
    }
}

Rien qui appelle à la destruction d'une instance de City ne se souciera jamais de la façon dont cela se produit, donc il n'y a aucune raison pour que ce code existe n'importe où en dehors de City::destroy, Et en fait, une connaissance intime de la le fonctionnement interne de City en dehors de lui-même serait un couplage étroit qui réduit la flexibilité car vous devez tenir compte de ces éléments externes si vous avez besoin de modifier le comportement de City. C'est le véritable objectif de l'encapsulation. Considérez-le comme chaque objet possède sa propre API qui devrait vous permettre de faire tout ce dont vous avez besoin pour que vous puissiez le laisser s'inquiéter de répondre à vos demandes.

Délégation, pas "Contrôle"

Maintenant, que votre classe d'implémentation soit City ou Baghdad dépend de la façon dont le processus de destruction de la ville se révèle générique. Selon toute probabilité, un City sera composé de plus petites pièces qui devront être détruites individuellement pour accomplir la destruction totale de la ville, donc dans ce cas, chacune de ces pièces implémenterait également Destroyable, et ils seraient chacun chargés par le City de se détruire de la même manière que quelqu'un de l'extérieur demandait au City de se détruire.

interface Part extends Destroyable {
    ...part-specific methods
}

class Building implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
       ...code to destroy a building
    }
}

class Street implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
        ...code to destroy a building
    }
}

class City implements Destroyable {
    public List<Part> parts() {...}

    @Override
    public void destroy() {
        parts().forEach(Destroyable::destroy);            
    }
}

Si vous voulez devenir vraiment fou et implémenter l'idée d'un Bomb qui est déposé sur un emplacement et détruit tout dans un certain rayon, cela pourrait ressembler à ceci:

class Bomb {
    private final Integer radius;

    public Bomb(final Integer radius) {
        this.radius = radius;
    }

    public void drop(final Grid grid, final Coordinate target) {
        new ObjectsByRadius(
            grid,
            target,
            this.radius
        ).forEach(Destroyable::destroy);
    }
}

ObjectsByRadius représente un ensemble d'objets qui est calculé pour le Bomb à partir des entrées car le Bomb ne se soucie pas de la façon dont ce calcul est effectué tant qu'il peut travailler avec les objets . Ceci est réutilisable accessoirement, mais l'objectif principal est d'isoler le calcul des processus de suppression du Bomb et de destruction des objets afin que vous puissiez comprendre chaque pièce et comment ils s'emboîtent et changer le comportement d'une pièce individuelle sans avoir à remodeler l'algorithme entier.

Interactions, pas algorithmes

Au lieu d'essayer de deviner le bon nombre de paramètres pour un algorithme complexe, il est plus logique de modéliser le processus comme un ensemble d'objets en interaction, chacun avec des rôles extrêmement étroits, car cela vous donnera la possibilité de modéliser la complexité de votre processus à travers les interactions entre ces objets bien définis, faciles à comprendre et presque immuables. Lorsque cela est fait correctement, cela rend même certaines des modifications les plus complexes aussi triviales que l'implémentation d'une interface ou deux et le remaniement des objets qui sont instanciés dans votre méthode main().

Je voudrais vous donner quelque chose à votre exemple original, mais je ne peux honnêtement pas comprendre ce que signifie "imprimer ... Day Light Savings". Ce que je peux dire à propos de cette catégorie de problème, c'est que chaque fois que vous effectuez un calcul, dont le résultat pourrait être formaté de plusieurs façons, ma façon préférée de le décomposer est la suivante:

interface Result {
    String print();
}

class Caclulation {
    private final Parameter paramater1;

    private final Parameter parameter2;

    public Calculation(final Parameter parameter1, final Parameter parameter2) {
        this.parameter1 = parameter1;
        this.parameter2 = parameter2;
    }

    public Result calculate() {
        ...calculate the result
    }
}

class FormattedResult {
    private final Result result;

    public FormattedResult(final Result result) {
        this.result = result;
    }

    @Override
    public String print() {
        ...interact with this.result to format it and return the formatted String
    }
}

Étant donné que votre exemple utilise des classes de la bibliothèque Java qui ne prennent pas en charge cette conception, vous pouvez simplement utiliser l'API de ZonedDateTime directement. L'idée ici est que chaque calcul est encapsulé au sein de son propre objet. Il ne fait aucune hypothèse sur le nombre d'exécutions ou la mise en forme du résultat. Il ne concerne que l'exécution de la forme de calcul la plus simple. Cela le rend à la fois facile à comprendre et flexible à modifier. De même, le Result est exclusivement concerné par l'encapsulation du résultat du calcul, et le FormattedResult est exclusivement destiné à interagir avec le Result pour le formater selon les règles que nous définissons. De cette façon, nous pouvons trouver le nombre parfait d'arguments pour chacune de nos méthodes puisqu'elles ont chacune une tâche bien définie . Il est également beaucoup plus simple de modifier la marche à suivre tant que les interfaces ne changent pas (ce qu'elles ne sont pas aussi susceptible de faire si vous avez correctement mi nimisé les responsabilités de vos objets). Notre méthode main() pourrait ressembler à ceci:

class App {
    public static void main(String[] args) {
        final List<Set<Paramater>> parameters = ...instantiated from args
        parameters.forEach(set -> {
            System.out.println(
                new FormattedResult(
                    new Calculation(
                        set.get(0),
                        set.get(1)
                    ).calculate()
                ).print()
            );
        });
    }
}

En fait, la programmation orientée objet a été inventée spécifiquement comme une solution au problème de complexité/flexibilité du paradigme impératif car il n'y a tout simplement pas de bonne réponse (sur laquelle tout le monde peut s'entendre ou arriver indépendamment, de toute façon) sur la façon d'optimiser spécifier les fonctions et procédures impératives dans l'idiome.

4
Stuporman

Expérience, Connaissance du domaine, et Revues de code.

Et, peu importe combien ou peu d'expérience , connaissance du domaine , ou l'équipe que vous avez, vous ne pouvez pas éviter d'avoir à refactoriser au besoin.


Avec l'expérience, vous commencerez à reconnaître des modèles dans les méthodes (et classes) non spécifiques au domaine que vous écrivez. Et, si vous êtes du tout intéressé par le code DRY, vous ressentirez de mauvais sentiments lorsque vous serez à propos d'écrire une méthode que vous savez instinctivement dont vous écrirez des variantes à l'avenir. Donc, vous écrivez intuitivement un dénominateur le moins commun paramétré.

(Cette expérience peut également être transférée instinctivement dans certains objets et méthodes de votre domaine.)

Avec la connaissance du domaine, vous aurez une idée pour quels concepts commerciaux sont étroitement liés, quels concepts ont des variables, qui sont assez statiques, etc.

Avec les revues de code, la sous-paramétrisation et la sur-paramétrisation seront plus susceptibles d'être détectées avant qu'elles ne deviennent du code de production, car vos pairs auront (espérons-le) des expériences et des perspectives uniques, à la fois sur domaine et codage en général.


Cela dit, les nouveaux développeurs n'auront généralement pas ces sens Spidey ou un groupe de pairs expérimentés sur lesquels s'appuyer immédiatement. Et, même les développeurs expérimentés bénéficient d'une discipline de base pour les guider à travers de nouvelles exigences - ou à travers des jours de brouillard cérébral. Alors, voici ce que je suggérerais pour commencer :

  • Commencez par l'implémentation naïve, avec un paramétrage minimal.
    (Incluez tous les paramètres que vous connaissez déjà dont vous aurez besoin, évidemment ...)
  • Supprimez les nombres magiques et les chaînes, en les déplaçant vers les configurations et/ou les paramètres
  • Factorisez les "grandes" méthodes en méthodes plus petites et bien nommées
  • Refactorisez les méthodes hautement redondantes (si cela vous convient) en un dénominateur commun, en paramétrant les différences.

Ces étapes ne se produisent pas nécessairement dans l'ordre indiqué. Si vous vous asseyez pour écrire une méthode que vous savez déjà être hautement redondante avec une méthode existante , passez directement au refactoring si c'est pratique. (Si cela ne prendra pas beaucoup plus de temps pour refactoriser qu'il ne le serait pour simplement écrire, tester et maintenir deux méthodes.)

Mais, en plus d'avoir beaucoup d'expérience et ainsi de suite, je conseille un code DRY-ing assez minimaliste. Il n'est pas difficile de refactoriser des violations évidentes. Et, si vous êtes trop zélé , vous pouvez vous retrouver avec un code "trop ​​SEC" qui est encore plus difficile à lire, à comprendre et à maintenir que le Équivalent "WET".

3
svidgen

La même réponse que pour la qualité, la convivialité, la dette technique, etc.:

Aussi réutilisable que vous, l'utilisateur,1 besoin qu'ils soient

Il s'agit essentiellement d'un jugement - si le coût de la conception et de la maintenance de l'abstraction sera remboursé par le coût (= temps et efforts), cela vous fera économiser sur toute la ligne.

  • Notez la phrase "en bas de la ligne": il y a un mécanisme de paiement ici, donc cela dépendra de combien vous travaillerez avec ce code plus loin. Par exemple.:
    • S'agit-il d'un projet ponctuel ou va-t-il s'améliorer progressivement sur une longue période?
    • Êtes-vous confiant dans votre conception, ou devrez-vous probablement l'abandonner ou le changer radicalement pour le prochain projet/jalon (par exemple, essayez un autre cadre)?
  • L'avantage prévu dépend également de votre capacité à prédire l'avenir (modifications apportées à l'application). Parfois, vous pouvez raisonnablement voir le (s) lieu (x) que votre application va prendre. Plus souvent, vous pensez que vous pouvez mais vous ne pouvez pas réellement. Les règles de base ici sont le principe YAGNI et le règle de trois - les deux mettent l'accent sur le travail à partir de ce que vous savez, maintenant.

1Ceci est une construction de code, vous êtes donc "l'utilisateur" dans ce cas - l'utilisateur du code source

2
ivan_pozdeev

Je suis arrivé à l'opinion qu'il existe deux types de code réutilisable:

  • Code qui est réutilisable parce que c'est une chose fondamentale et fondamentale.
  • Code qui est réutilisable car il a des paramètres, des remplacements et des crochets pour partout.

Le premier type de réutilisabilité est souvent une bonne idée. Il s'applique à des choses comme les listes, les hashmaps, les magasins de clés/valeurs, les égaliseurs de chaînes (par exemple, regex, glob, ...), les tuples, l'unification, les arbres de recherche (profondeur d'abord, largeur d'abord, approfondissement itératif, ...) , combinateurs d'analyseurs, caches/memoisers, lecteurs/écrivains de format de données (expressions s, XML, JSON, protobuf, ...), files d'attente de tâches, etc.

Ces choses sont donc générales, d'une manière très abstraite, qu'elles sont réutilisées partout dans la programmation quotidienne. Si vous vous retrouvez en train d'écrire du code spécial qui serait plus simple s'il était rendu plus abstrait/général (par exemple si nous avons "une liste de commandes clients", nous pourrions jeter la "commande client" pour obtenir "une liste"), alors ce serait une bonne idée de le retirer. Même s'il n'est pas réutilisé, il nous permet de découpler des fonctionnalités indépendantes.

Le deuxième type est celui où nous avons un code concret, qui résout un vrai problème, mais le fait en prenant tout un tas de décisions. Nous pouvons le rendre plus général/réutilisable en "codant en douceur" ces décisions, par exemple les transformer en paramètres, compliquer l'implémentation et la cuisson dans des détails encore plus concrets (c'est-à-dire la connaissance des crochets que nous pourrions vouloir pour les remplacements). Votre exemple semble être de ce genre. Le problème avec ce type de réutilisabilité est que nous pourrions finir par essayer de deviner les cas d'utilisation d'autres personnes, ou notre moi futur. Finalement, nous pourrions finir par avoir tellement de paramètres que notre code n'est pas tilisable, encore moins réutilisable! En d'autres termes, lors de l'appel, il faut plus d'efforts que d'écrire notre propre version. C'est là que YAGNI (vous n'en aurez pas besoin) est important. Souvent, de telles tentatives de code "réutilisable" finissent par ne pas être réutilisées, car elles peuvent être incompatibles avec ces cas d'utilisation plus fondamentalement que les paramètres ne peuvent le justifier, ou ces utilisateurs potentiels préfèrent rouler les leurs (diable, regardez tous les standards et bibliothèques dont les auteurs ont préfixé le mot "Simple", pour les distinguer des prédécesseurs!).

Cette deuxième forme de "réutilisabilité" devrait essentiellement se faire selon les besoins. Bien sûr, vous pouvez y coller des paramètres "évidents", mais n'essayez pas de prédire l'avenir. YAGNI.

1
Warbo

Il existe déjà de nombreuses réponses excellentes et élaborées. Certains d'entre eux approfondissent des détails spécifiques, exposent certains points de vue sur les méthodologies de développement de logiciels en général, et certains d'entre eux contiennent certainement des éléments controversés ou des "opinions".

Le réponse de Warbo indiquait déjà différents types de réutilisabilité. À savoir, si quelque chose est réutilisable parce que c'est un bloc de construction fondamental, ou si quelque chose est réutilisable parce qu'il est "générique" d'une manière ou d'une autre. En référence à ce dernier, il y a quelque chose que je considérerais comme une sorte de mesure pour la réutilisabilité:

Si une méthode peut émuler une autre.

Concernant l'exemple de la question: Imaginez que la méthode

void dayLightSavings()

était la mise en œuvre d'une fonctionnalité demandée par un client. Ce sera donc quelque chose que les autres programmeurs sont censés utiliser, et donc, être une méthode publique , comme dans

publicvoid dayLightSavings()

Cela pourrait être mis en œuvre comme vous l'avez montré dans votre réponse. Maintenant, quelqu'un veut le paramétrer avec l'année. Vous pouvez donc ajouter une méthode

publicvoid dayLightSavings(int year)

et changer l'implémentation d'origine pour être juste

public void dayLightSavings() {
    dayLightSavings(2018);
}

Les "demandes de fonctionnalités" et les généralisations suivantes suivent le même modèle. Donc si et seulement si il y a une demande pour la forme la plus générique, vous pouvez l'implémenter, sachant que cette forme la plus générique permet des implémentations triviales des plus spécifiques:

public void dayLightSavings() {
    dayLightSavings(2018, 0, 12, 0, 12, new DateTimeFormatter(...));
}

Si vous aviez anticipé de futures extensions et demandes de fonctionnalités, et que vous aviez du temps à votre disposition et que vous vouliez passer un week-end ennuyeux avec des généralisations (potentiellement inutiles), vous pourriez avoir commencé avec la plus générique Depuis le début. Mais uniquement comme méthode privée . Tant que vous n'avez exposé que la méthode simple qui a été demandée par le client comme méthode publique , vous êtes sûr.

tl; dr :

La question n'est pas vraiment de savoir "comment une méthode doit être réutilisable". La question est de savoir dans quelle mesure cette réutilisabilité est exposée et à quoi ressemble l'API. La création d'une API fiable qui peut résister à l'épreuve du temps (même lorsque d'autres exigences viendront plus tard) est un art et un métier, et le sujet est beaucoup trop complexe pour le couvrir ici. Jetez un œil à cette présentation de Joshua Bloch ou le wiki du livre de conception d'API pour commencer.

1
Marco13

Vous pouvez suivre un processus clair:

  • Écrivez un test qui échoue pour une seule caractéristique qui est en soi une "chose" (c'est-à-dire pas une division arbitraire d'une caractéristique où aucune moitié n'a vraiment de sens).
  • Écrivez le code minimum absolu pour le faire passer au vert, pas une ligne de plus.
  • Rincez et répétez.
  • (Refactoriser sans relâche si nécessaire, ce qui devrait être facile en raison de la grande couverture du test.)

Cela se révèle avec - au moins de l'avis de certaines personnes - un code à peu près optimal, car il est aussi petit que possible, chaque fonctionnalité terminée prend le moins de temps possible (ce qui pourrait ou non être vrai si vous regardez le fini) produit après refactorisation), et il a une très bonne couverture de test. Il évite également de manière notable des méthodes ou des classes trop génériques trop génériques.

Cela vous donne également des instructions très claires sur le moment de rendre les choses génériques et de vous spécialiser.

Je trouve votre exemple de ville bizarre; Je n'aurais probablement jamais codé en dur un nom de ville. Il est tellement évident que d'autres villes seront incluses plus tard, quoi que vous fassiez. Mais un autre exemple serait les couleurs. Dans certaines circonstances, le codage en dur "rouge" ou "vert" serait une possibilité. Par exemple, les feux de circulation sont d'une couleur tellement omniprésente que vous pouvez simplement vous en tirer (et vous pouvez toujours refaçonner). La différence est que "rouge" et "vert" ont une signification universelle "codée en dur" dans notre monde, il est incroyablement improbable que cela change un jour, et il n'y a pas vraiment d'alternative non plus.

Votre première méthode d'heure d'été est tout simplement cassée. Bien qu'il soit conforme au cahier des charges, le codé en dur 2018 est particulièrement mauvais car a) il n'est pas mentionné dans le "contrat" ​​technique (dans le nom de la méthode, dans ce cas), et b) il sera bientôt obsolète, donc casse est inclus dès le départ. Pour les choses qui sont liées à l'heure/la date, il serait très rarement logique de coder en dur une valeur spécifique car, bien, le temps passe. Mais à part cela, tout le reste est à discuter. Si vous lui donnez une année simple et que vous calculez toujours l'année complète, allez-y. La plupart des choses que vous avez répertoriées (formatage, choix d'une plage plus petite, etc.) indiquent que votre méthode en fait trop, et elle devrait plutôt renvoyer une liste/un tableau de valeurs afin que l'appelant puisse effectuer le formatage/filtrage lui-même.

Mais à la fin de la journée, la plupart de cela est l'opinion, le goût, l'expérience et les préjugés personnels, alors ne vous inquiétez pas trop.

1
AnoE

C'est une bonne occasion d'énoncer une règle que j'ai inventée récemment:

Être un bon programmeur signifie être capable de prédire l'avenir.

Bien sûr, c'est strictement impossible! Après tout, vous ne savez jamais avec certitude quelles généralisations vous trouverez utiles plus tard, quelles tâches connexes vous voudrez effectuer, quelles nouvelles fonctionnalités votre les utilisateurs voudront, & c. Mais l'expérience vous donne parfois une idée approximative de ce qui pourrait vous être utile.

Les autres facteurs dont vous devez tenir compte sont le temps et les efforts supplémentaires que cela impliquerait et la complexité de votre code. Parfois, vous avez de la chance, et résoudre le problème plus général est en fait plus simple! (Du moins sur le plan conceptuel, sinon dans la quantité de code.) Mais le plus souvent, il y a un coût de complexité ainsi qu'un temps et d'efforts.

Donc, si vous pensez qu'une généralisation est très probablement nécessaire, cela vaut souvent la peine de le faire (à moins qu'elle ajoute un beaucoup de travail ou de complexité); mais si cela semble beaucoup moins probable, alors ce n'est probablement pas le cas (à moins que ce soit très facile et/ou simplifie le code).

(Pour un exemple récent: la semaine dernière, on m'a donné une spécification pour les actions qu'un système devrait prendre exactement 2 jours après l'expiration de quelque chose. Alors bien sûr, j'ai fait de la période de 2 jours un paramètre. Cette semaine, les gens d'affaires étaient ravis, car ils étaient sur le point de demander cette amélioration! J'ai eu de la chance: c'était un changement facile, et j'ai deviné que c'était très probable. Souvent, c'est plus difficile à juger. Mais ça vaut quand même la peine d'essayer de prédire, et l'expérience est souvent un bon guide .)

0
gidds

Premièrement, la meilleure réponse à "Comment puis-je savoir à quel point mes méthodes devraient être réutilisables?" Est "expérience". Faites-le plusieurs milliers de fois, et vous obtenez généralement la bonne réponse. Mais en tant qu'accroche, je peux donnez-vous la dernière ligne de cette réponse: Votre client vous dira combien de flexibilité et combien de couches de généralisation vous devriez rechercher.

Beaucoup de ces réponses contiennent des conseils spécifiques. Je voulais donner quelque chose de plus générique ... parce que l'ironie est trop amusante à laisser passer!

Comme l'ont noté certaines réponses, la généralité coûte cher. Mais ce n'est vraiment pas le cas. Pas toujours. Comprendre les dépenses est essentiel pour jouer au jeu de la réutilisabilité.

Je me concentre sur la mise des choses sur une échelle de "irréversible" à "réversible". C'est une échelle lisse. La seule chose vraiment irréversible est "le temps consacré au projet". Vous ne récupérerez jamais ces ressources. Un peu moins réversibles pourraient être des situations de "menottes dorées" telles que l'API Windows. Les fonctionnalités obsolètes restent dans cette API pendant des décennies, car le modèle commercial de Microsoft l'exige. Si vous avez des clients dont la relation serait définitivement endommagée par l'annulation d'une fonctionnalité d'API, cela devrait être traité comme irréversible. En regardant à l'autre extrémité de l'échelle, vous avez des choses comme le code prototype. Si vous n'aimez pas où il va, vous pouvez simplement le jeter. Les API à usage interne peuvent être légèrement moins réversibles. Ils peuvent être refactorisés sans déranger un client, mais ils peuvent coûter plus cher temps (la ressource la plus irréversible de toutes!)

Alors mettez-les sur une échelle. Vous pouvez maintenant appliquer une heuristique: plus quelque chose est réversible, plus vous pouvez l'utiliser pour de futures activités. Si quelque chose est irréversible, utilisez-le uniquement pour des tâches concrètes orientées client. C'est pourquoi vous voyez des principes comme ceux de la programmation extrême qui suggèrent de ne faire que ce que le client demande et rien de plus. Ces principes permettent de s'assurer que vous ne faites pas quelque chose que vous regrettez.

Des choses comme le DRY principe suggèrent un moyen de déplacer cet équilibre. Si vous vous retrouvez à vous répéter, c'est l'occasion de créer ce qui est fondamentalement une API interne. Aucun client ne le voit, vous pouvez donc toujours changer Une fois que vous avez cette API interne, vous pouvez maintenant commencer à jouer avec les choses tournées vers l'avenir. Combien de tâches différentes basées sur le fuseau horaire pensez-vous que votre femme va vous donner? Avez-vous d'autres clients qui voudraient des tâches basées sur le fuseau horaire? - Votre flexibilité ici est achetée par les demandes concrètes de vos clients actuels, et elle prend en charge les demandes futures potentielles de futurs clients.

Cette approche de la pensée en couches, qui vient naturellement de DRY fournit naturellement la généralisation que vous voulez sans gaspillage. Mais y a-t-il une limite? Bien sûr qu'il y en a. Mais pour le voir, vous devez voir la forêt pour les arbres.

Si vous disposez de plusieurs couches de flexibilité, elles conduisent souvent à un manque de contrôle direct des couches auxquelles vos clients sont confrontés. J'ai eu un logiciel où j'ai eu la tâche brutale d'expliquer à un client pourquoi il ne peut pas avoir ce qu'il veut en raison de la flexibilité intégrée dans 10 couches vers le bas qu'il n'était jamais censé voir. Nous nous sommes écrits dans un coin. Nous nous sommes attachés en un nœud avec toute la flexibilité dont nous pensions avoir besoin.

Donc, quand vous faites cette astuce de généralisation/SEC, gardez toujours une impulsion sur votre client. Que pensez-vous que votre femme va demander ensuite? Vous mettez-vous en mesure de répondre à ces besoins? Si vous avez le talent, le client vous indiquera efficacement ses besoins futurs. Si vous n'avez pas le talent, eh bien, la plupart d'entre nous ne font que deviner! (en particulier avec les conjoints!) Certains clients voudront une grande flexibilité et accepteront le surcoût de votre développement avec toutes ces couches car ils bénéficient directement de la flexibilité de ces couches. D'autres clients ont des exigences inébranlables plutôt fixes et préfèrent que le développement soit plus direct. Votre client vous dira combien de flexibilité et combien de couches de généralisation vous devriez rechercher.

0
Cort Ammon

Il s'agit d'une ligne facile à tracer, car la réutilisation telle que définie par les astronautes de l'architecture est des bollocks.

Presque tout le code créé par les développeurs d'applications est extrêmement spécifique au domaine. Ce n'est pas 1980. Presque tout ce qui vaut la peine est déjà dans un cadre.

Les abstractions et les conventions nécessitent de la documentation et des efforts d'apprentissage. Veuillez cesser d'en créer de nouvelles juste pour le plaisir. (Je regarde vous, les gens JavaScript!)

Laissez-nous aller au fantasme invraisemblable que vous avez trouvé quelque chose qui devrait vraiment être dans votre cadre de choix. Vous ne pouvez pas simplement frapper le code comme vous le faites habituellement. Oh non, vous avez besoin d'une couverture de test non seulement pour l'utilisation prévue, mais aussi pour les écarts par rapport à l'utilisation prévue, pour tous les cas Edge connus, pour tous les modes de défaillance imaginables, les cas de test pour les diagnostics, les données de test, la documentation technique, documentation utilisateur, gestion des versions, scripts de support, tests de régression, gestion des changements ...

Votre employeur est-il heureux de payer tout cela? Je vais dire non.

L'abstraction est le prix que nous payons pour la flexibilité. Cela rend notre code plus complexe et plus difficile à comprendre. À moins que la flexibilité ne réponde à un besoin réel et présent, ne le faites pas, car YAGNI.

Jetons un coup d'œil à un exemple du monde réel que je venais de voir: HTMLRenderer. Il n'était pas mis à l'échelle correctement lorsque j'ai essayé de faire le rendu dans le contexte de périphérique d'une imprimante. Il m'a fallu toute la journée pour découvrir que par défaut, il utilisait GDI (qui ne se redimensionne pas) plutôt que GDI + (qui le fait, mais ne fait pas d'antialias) parce que je devais parcourir six niveaux d'indirection dans deux assemblys avant de trouver du code qui a fait n'importe quoi.

Dans ce cas, je pardonnerai à l'auteur. L'abstraction est en fait nécessaire car ce est code cadre qui cible cinq cibles de rendu très différentes: WinForms, WPF, dotnet Core, Mono et PdfSharp.

Mais cela ne fait que souligner mon point: vous êtes presque certainement pas faisant quelque chose d'extrêmement complexe (rendu HTML avec des feuilles de style) ciblant plusieurs plates-formes avec un objectif déclaré de hautes performances sur toutes les plates-formes.

Votre code est presque certainement une autre grille de base de données avec des règles commerciales qui ne s'appliquent qu'à votre employeur et des règles fiscales qui ne s'appliquent qu'à votre état, dans une application qui n'est pas à vendre.

Toute cette indirection résout un problème que vous n'avez pas et rend votre code beaucoup plus difficile à lire, ce qui augmente massivement le coût de la maintenance et est un énorme mauvais service pour votre employeur. Heureusement, les personnes qui devraient s'en plaindre sont incapables de comprendre ce que vous leur faites.

Un contre-argument est que ce type d'abstraction prend en charge le développement piloté par les tests, mais je pense que TDD est également un problème, car cela suppose que l'entreprise ait une compréhension claire, complète et correcte de ses besoins. TDD est idéal pour la NASA et le logiciel de contrôle pour l'équipement médical et les voitures autonomes, mais beaucoup trop cher pour tout le monde.


Soit dit en passant, il n'est pas possible de prévoir tous l'heure d'été dans le monde. Israël en particulier a environ 40 transitions chaque année qui sautent partout parce que nous ne pouvons pas avoir des gens qui prient au mauvais moment et Dieu ne fait pas l'heure d'été.

0
Peter Wone

aucun ingénieur logiciel formé à l'éthique ne consentirait jamais à écrire une procédure DestroyBaghdad. L'éthique professionnelle de base l'obligerait à la place à écrire une procédure DestroyCity, à laquelle Bagdad pourrait être donné comme paramètre.

C'est quelque chose connu dans les cercles de génie logiciel avancé comme une "blague". Les blagues ne doivent pas nécessairement être ce que nous appelons "vraies", mais pour être drôles, elles doivent généralement faire allusion à quelque chose de vrai.

Dans ce cas particulier, la "blague" n'est pas "vraie". Le travail impliqué dans la rédaction d'une procédure générale pour détruire n'importe laquelle ville est, nous pouvons supposer en toute sécurité, des ordres de grandeur au-delà de celui requis pour détruire une spécifique ville. Sinon, quiconque a détruit une ou une poignée de villes (le Josué biblique, dirons-nous, ou le président Truman) pourrait généraliser trivialement ce qu'il a fait et être capable de détruire absolument n'importe quelle ville à volonté. Ce n'est en fait pas le cas. Les méthodes utilisées par ces deux personnes pour détruire un petit nombre de villes spécifiques ne fonctionneraient pas nécessairement sur n'importe quelle ville à tout moment. Une autre ville dont les murs avaient une fréquence de résonance différente ou dont les défenses aériennes à haute altitude étaient plutôt meilleures, aurait besoin de changements d'approche mineurs ou fondamentaux (une trompette à hauteur différente ou une fusée).

Cela conduit également à la maintenance du code contre les changements au fil du temps: il y a maintenant beaucoup de villes qui ne tomberaient dans aucune de ces approches, grâce aux méthodes de construction modernes et au radar omniprésent.

Développer et tester un moyen complètement général qui détruira tout ville, avant d'accepter de détruire juste n ville, est une approche désespérément inefficace. Aucun ingénieur logiciel formé à l'éthique n'essaierait de généraliser un problème dans une mesure nécessitant des ordres de grandeur de travail de plus que ce que son employeur/client doit réellement payer, sans exigence démontrée.

Alors qu'est-ce qui est vrai? Parfois, l'ajout de généralités est insignifiant. Faut-il alors toujours ajouter de la généralité quand il est trivial de le faire? Je dirais toujours "non, pas toujours", en raison du problème de maintenance à long terme. Supposons qu'au moment de la rédaction de ce document, toutes les villes soient fondamentalement les mêmes, alors je continue avec DestroyCity. Une fois que j'ai écrit cela, ainsi que des tests d'intégration qui (en raison de l'espace finement énumérable des entrées) itèrent sur chaque ville connue et s'assurent que la fonction fonctionne sur chacune (je ne sais pas comment cela fonctionne. Appelle probablement City.clone () et détruit le clone? Quoi qu'il en soit).

En pratique, la fonction est utilisée uniquement pour détruire Bagdad, supposons que quelqu'un construise une nouvelle ville qui résiste à mes techniques (c'est profond sous terre ou quelque chose). Maintenant, j'ai un échec de test d'intégration pour un cas d'utilisation qui n'existe même pas vraiment, et avant de pouvoir continuer ma campagne de terreur contre les civils innocents de l'Irak, je dois trouver comment détruire Subterrania. Peu importe que ce soit éthique ou non, c'est idiot et ne perte de mon temps flippant.

Alors, voulez-vous vraiment une fonction qui puisse générer des économies d'heure d'été pour n'importe quelle année, juste pour produire des données pour 2018? Peut-être, mais cela va certainement nécessiter un peu d'effort supplémentaire juste pour assembler des cas de test. Cela peut nécessiter beaucoup d'efforts pour obtenir une meilleure base de données de fuseau horaire que celle que vous avez réellement. Ainsi, par exemple, en 1908, la ville de Port Arthur, en Ontario, a connu une période d'heure d'été commençant le 1er juillet. Est-ce dans la base de données de fuseau horaire de votre système d'exploitation? Je ne pensais pas, donc votre fonction généralisée est mauvaise. Il n'y a rien de particulièrement éthique dans l'écriture de code qui fait des promesses qu'il ne peut pas tenir.

Très bien, donc, avec des mises en garde appropriées, il est facile d'écrire une fonction qui fait des fuseaux horaires pour une gamme d'années, par exemple de 1970 à nos jours. Mais il est tout aussi facile de prendre la fonction que vous avez réellement écrite et de la généraliser pour paramétrer l'année. Il n'est donc plus vraiment éthique/sensé de généraliser maintenant, c'est de faire ce que vous avez fait et de généraliser ensuite si et quand vous en avez besoin.

Cependant, si vous saviez pourquoi votre femme voulait vérifier cette liste d'heure d'été, alors vous auriez une opinion éclairée sur le fait qu'elle est susceptible de poser à nouveau la même question en 2019 et, si oui, si vous pouvez prendre vous sortir de cette boucle en lui donnant une fonction qu'elle peut appeler, sans avoir besoin de la recompiler. Une fois que vous avez fait cette analyse, la réponse à la question "si cela se généralise aux dernières années" pourrait être "oui". Mais vous créez encore n autre problème pour vous-même, c'est-à-dire que les données de fuseau horaire à l'avenir ne sont que provisoires, et donc si elle les exécute pour 2019 aujourd'hui, elle peut ou non se rendre compte que cela la nourrit d'une meilleure estimation. Donc, vous devez encore écrire un tas de documentation qui ne serait pas nécessaire pour la fonction moins générale ("les données proviennent de la base de données de fuseau horaire blah blah, voici le lien pour voir leurs politiques sur les mises à jour blah blah"). Si vous refusez le cas spécial, alors tout le temps que vous le faites, elle ne peut pas poursuivre la tâche pour laquelle elle avait besoin des données de 2018, en raison de bêtises sur 2019 dont elle ne se soucie même pas encore.

Ne faites pas de choses difficiles sans y penser correctement, simplement parce qu'une blague vous l'a dit. Est-ce utile? Est-ce assez bon marché pour ce degré d'utilité?

0
Steve Jessop

Une bonne règle d'or est la suivante: votre méthode doit être aussi réutilisable que… réutilisable.

Si vous vous attendez à ce que vous n'appeliez votre méthode qu'à un seul endroit, elle ne devrait avoir que des paramètres connus du site d'appel et qui ne sont pas disponibles pour cette méthode.

Si vous avez plus d'appels, vous pouvez introduire de nouveaux paramètres tant que d'autres appelants peuvent transmettre ces paramètres; sinon vous avez besoin d'une nouvelle méthode.

Comme le nombre d'appels peut augmenter avec le temps, vous devez être prêt à effectuer une refactorisation ou une surcharge. Dans de nombreux cas, cela signifie que vous devriez vous sentir en sécurité pour sélectionner l'expression et exécuter l'action "extraire les paramètres" de votre IDE.

0
Karol

Réponse ultra courte: Moins couplage ou dépendance à un autre code votre module générique est le plus réutilisable possible.

Votre exemple ne dépend que de

import Java.time.*;
import Java.util.Set;

donc en théorie, il peut être hautement réutilisable.

Dans la pratique, je ne pense pas que vous aurez un deuxième cas d'utilisation qui aura besoin de ce code, donc suivant le principe yagni je ne le rendrais pas réutilisable s'il n'y a pas plus de 3 projets différents qui ont besoin de ce code.

D'autres aspects de la réutilisabilité sont la facilité d'utilisation et la documentation qui correspondent à Test Driven Development : C'est utile si vous avez un test unitaire simple qui démontre/documente une utilisation facile de votre module générique comme exemple de codage pour les utilisateurs de votre lib.

0
k3b