Comment savoir à quel point mes méthodes doivent être réutilisables? [fermé]


133

Je garde mes propres affaires à la maison et ma femme vient à moi et dit

Chérie .. Pouvez-vous imprimer toutes les économies réalisées dans le monde entier pour la lumière du jour pour 2018 sur la console? J'ai besoin de vérifier quelque chose.

Et je suis super heureux parce que c’était ce que j’attendais de toute ma vie avec mon expérience de Java et de proposer:

import java.time.*;
import java.util.Set;

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);
                    }
                }
            }
        );
    }
}

Mais ensuite, elle dit qu'elle testait juste si j'étais un ingénieur en logiciel ayant une formation éthique et me dit qu'il me semble que je ne le suis plus depuis (pris d' ici ).

Il convient de noter qu'aucun ingénieur en logiciel formé sur le plan éthique n'accepterait jamais d'écrire une procédure DestroyBaghdad. Une éthique professionnelle de base l'obligerait plutôt à écrire une procédure DestroyCity, à laquelle Bagdad pourrait être paramétré.

Et je suis comme, d'accord, d'accord, tu m'as obtenu .. Passe l' année que tu veux, voilà:

import java.time.*;
import java.util.Set;

class App {
    void dayLightSavings(int year) {
        final 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, pourrait-elle dire…

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

void dayLightSavings(int year, DateTimeFormatter dtf)

  • elle ne s'intéresse qu'à certains mois

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

  • elle s'intéresse à certaines heures

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)c'est mieux que destroyBaghdad(), c'est takeActionOnCity(Action action, City city)encore mieux? Pourquoi pourquoi pas?

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

Mais agir sur les villes ne me suffit pas, que diriez-vous takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)? Après tout, je ne veux pas appeler:

takeActionOnCity(Action.DESTORY, City.BAGHDAD);

ensuite

takeActionOnCity(Action.DESTORY, City.ERBIL);

et ainsi de suite quand je peux faire:

takeActionOnGeographicArea(Action.DESTORY, Country.IRAQ);

PS J'ai construit ma question uniquement 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 valoir un point.



71
Ce que vous soulevez est un argument que j’ai essayé d’exprimer à maintes reprises: la généralité coûte cher et doit donc être justifiée par des avantages clairs et spécifiques . Mais ça va plus loin que ça; Les langages de programmation sont créés par leurs concepteurs pour rendre certains types de généralité plus faciles que d’autres, ce qui influence nos choix en tant que développeurs. Il est facile de paramétrer une méthode par une valeur, et lorsque c’est l’outil le plus simple de votre boîte à outils, la tentation est de l’utiliser, que cela ait du sens pour l’utilisateur ou non.
Eric Lippert

30
La réutilisation n'est pas quelque chose que vous voulez en soi. Nous donnons la priorité à la réutilisation car nous sommes convaincus que la construction d'artefacts de code est coûteuse et qu'elle devrait donc pouvoir être utilisée dans le plus grand nombre de scénarios possible afin d'amortir ce coût. Cette idée n’est souvent pas justifiée par des observations et les conseils de conception en vue de leur réutilisation sont donc fréquemment mal appliqués . Concevez votre code pour réduire le coût total de l'application .
Eric Lippert

7
Votre femme est la moins scrupuleuse de perdre votre temps en vous mentant. Elle demanda une réponse et donna un moyen suggéré. En vertu de ce contrat, la manière dont vous obtenez cette sortie n’est entre vous et vous-même. En outre, destroyCity(target)est beaucoup plus contraire à l'éthique que destroyBagdad()! Quel genre de monstre écrit un programme pour éliminer une ville, sans parler de n'importe quelle ville du monde? Et si le système était compromis?! Aussi, qu'est-ce que la gestion du temps / des ressources (efforts investis) a à voir avec l'éthique? Tant que le contrat verbal / écrit a été rempli comme convenu.
Tezra

25
Je pense que vous lisez peut-être trop dans cette blague. C'est une blague sur la façon dont les programmeurs informatiques prennent de mauvaises décisions éthiques, car ils accordent la priorité aux considérations techniques aux effets de leur travail sur les humains. Ce n'est pas destiné à être de bons conseils sur la conception du programme.
Eric Lippert

Réponses:


114

C'est des tortues tout en bas.

Ou des abstractions dans ce cas.

Le codage de bonne pratique est une chose qui peut être appliquée à l'infini, et à un moment donné, vous abstenez dans un souci d'abstraction, ce qui signifie que vous l'avez poussé trop loin. Trouver cette ligne n’est pas chose facile à mettre en pratique, cela dépend beaucoup de votre environnement.

Par exemple, des clients connus pour demander d’abord des applications simples, puis des extensions. Nous avons également eu des clients qui demandent ce qu'ils veulent et qui ne nous reviennent généralement jamais pour une expansion.
Votre approche variera par client. Pour le premier client, il sera avantageux de prélever le code de manière préventive, car vous êtes raisonnablement certain que vous devrez le consulter ultérieurement. Pour le deuxième client, vous pouvez ne pas vouloir investir cet effort supplémentaire si vous vous attendez à ce qu’ils ne veuillent pas développer l’application à tout moment (remarque: cela ne signifie pas que vous ne respectez aucune bonne pratique, mais simplement que vous évitez de faire plus que ce qui est actuellement nécessaire.

Comment savoir quelles fonctionnalités implémenter?

La raison pour laquelle je mentionne ce qui précède est 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 à un besoin commercial futur. En règle générale, ne vous basez pas sur des suppositions, développez seulement ce qui est actuellement requis.

Cependant, le contexte s'applique ici. Je ne connais pas ta femme. Peut-être que vous avez bien mesuré qu’elle le voudrait. Mais vous devez toujours confirmer avec le client que c'est bien ce qu'il veut, car sinon vous passerez du temps à développer une fonctionnalité que vous n'utiliserez jamais.

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 en grande partie hors de propos.

Cependant, vous pouvez toujours en confirmer la nécessité en posant les bonnes questions au client. Au lieu de poser des questions sur l'architecture, demandez-leur quelles sont leurs attentes en matière de développement futur ou de développement de la base de code. Vous pouvez également demander si l'objectif actuel est assorti d'une date limite, car vous ne pourrez peut-être pas mettre en œuvre votre architecture sophistiquée dans les délais nécessaires.

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 le remercierai), mais en règle générale, les développeurs devraient compter comme un homme des cavernes: un, deux, beaucoup .

entrez la description de l'image ici XKCD # 764

En d’autres termes, lorsqu’un certain algorithme / modèle est utilisé pour la troisième fois, il doit être résumé afin qu’il soit réutilisable (= utilisable plusieurs fois).

Juste pour être clair, je ne veux pas dire 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 aussi résumer, mais la règle devrait être que, dans trois cas, vous devez résumer.

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 naturellement résumer immédiatement. Mais si vous devinez seulement que vous souhaiterez 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 conjecture.
Si vous avez bien deviné, vous avez économisé du temps. Si vous avez mal compris, vous avez perdu une partie de votre temps et de vos efforts et vous avez peut-être compromis votre architecture pour mettre en œuvre quelque chose dont vous n'avez finalement pas besoin.

Si destroyCity(City city)c'est mieux que destroyBaghdad(), c'est takeActionOnCity(Action action, City city)encore mieux? Pourquoi pourquoi pas?

Cela dépend beaucoup de plusieurs choses:

  • Existe-t-il plusieurs actions pouvant être entreprises dans n'importe quelle ville?
  • Ces actions peuvent-elles être utilisées de manière interchangeable? Parce que si les actions "détruire" et "reconstruire" ont des exécutions complètement différentes, il ne sert à rien de fusionner les deux en une seule takeActionOnCityméthode.

Sachez également que si vous abstenez récursivement ceci, vous allez vous retrouver avec une méthode tellement abstraite qu'il ne s'agit que d'un conteneur dans lequel 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 takeActionOnCity(Action action, City city)corps de méthode finit par n'être rien de plus que action.TakeOn(city);, vous devriez vous demander si la takeActionOnCityméthode a vraiment un but ou n'est pas simplement une couche supplémentaire qui n'ajoute rien de valeur.

Mais agir sur les villes ne me suffit pas, que diriez-vous takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)?

La même question se pose 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?
  • Des actions peuvent-elles être entreprises dans n’importe quelle région / ville?

Si vous pouvez définitivement répondre «oui» aux trois questions, une abstraction est justifiée.


16
Je ne saurais trop insister sur la règle du "un, deux, beaucoup". Il y a une infinité de possibilités pour abstraire / paramétrer quelque chose, mais le sous-ensemble utile est petit, souvent nul. Connaître exactement quelle variante a de la valeur ne peut le plus souvent être déterminé que rétrospectivement. Alors tenez-vous en aux exigences immédiates * et ajoutez la complexité requise par les nouvelles exigences ou par la suite. * Parfois, vous connaissez bien le problème, alors vous pouvez ajouter quelque chose car vous savez que vous en aurez besoin demain. Mais utilisez ce pouvoir à bon escient, il peut aussi conduire à la ruine.
Christian Sauer

2
> "Je ne sais pas où je l'ai lu [..]". Vous avez peut-être lu Coding Horror: The Rule of Three .
Rune

10
La règle "un, deux, beaucoup" est vraiment là pour vous empêcher de construire la mauvaise abstraction en appliquant DRY à l'aveuglette. Le fait est que deux morceaux de code peuvent commencer à paraître presque identiques, il est donc tentant d’abstraire les différences; mais au début, vous ne savez pas quelles parties du code sont stables et lesquelles ne le sont pas; de plus, il pourrait s'avérer qu'ils doivent réellement évoluer de manière indépendante (différents modèles de changement, différents ensembles de responsabilités). Dans les deux cas, une mauvaise abstraction joue contre vous et vous gêne.
Filip Milovanović

4
Attendre plus de deux exemples de "la même logique" vous permet de mieux juger de ce qui doit être résumé et de la manière dont il faut (et vraiment, il s’agit de gérer les dépendances / le couplage entre du code avec des modèles de changement différents).
Filip Milovanović

1
@kukis: La ligne réaliste devrait être tracée à 2 (selon le commentaire de Baldrickk): zéro-un-plusieurs (comme c'est le cas pour les relations de base de données). Cependant, cela ouvre la porte à un comportement inutile de recherche de modèles. Deux choses peuvent se ressembler vaguement mais cela ne signifie pas qu’elles sont en réalité les mêmes. Cependant, quand une troisième instance entre dans la mêlée qui ressemble à la première et à la deuxième instance, vous pouvez juger plus précisément que leurs similitudes sont bien un motif réutilisable. La ligne du sens commun est donc tracée en 3, ce qui prend en compte l'erreur humaine lors de l'identification de "modèles" entre seulement deux instances.
Flater

44

Entraine toi

Il s’agit de Software Engineering SE, mais l’élaboration de logiciels est beaucoup plus artistique que technique. Il n'y a pas d'algorithme universel à suivre ni de mesure à prendre pour déterminer combien de capacité de réutilisation est suffisante. Comme avec n'importe quoi, plus vous maîtriserez les programmes de conception, plus vous y arriverez. Vous aurez une meilleure idée de ce qui est "suffisant" car vous verrez ce qui ne va pas et ce qui ne va pas quand vous paramétrez trop ou trop peu.

Ce n'est pas très utile maintenant , alors que diriez-vous de certaines lignes directrices?

Revenez à votre question. Il y a beaucoup de "elle pourrait dire" et "je pourrais". Beaucoup d'énoncés théorisant sur un besoin futur. Les humains sont fous de prédire l'avenir. Et vous (probablement) êtes un humain. Le problème fondamental de la conception de logiciels est de tenter 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é.

Directive 2: Coût / bénéfice

Cool, ce petit programme vous a pris quelques heures pour écrire peut-être? Alors , que si votre femme ne revient et demander ces choses? Dans le pire des cas, vous passez encore quelques heures à lancer un autre programme. Dans ce cas, ce n’est pas beaucoup de temps pour rendre ce programme plus flexible. Et cela ne va pas ajouter grand chose à 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 moment donné, les coûts ne valent manifestement pas le bénéfice, même avec des compétences imparfaites en énonciation future.

Directive 3: Concentration sur les constantes

Retournez à la question. Dans votre code d'origine, il y a beaucoup d'ints constants. 2018, 1. Ints constants, chaînes constantes ... Ce sont les choses les plus susceptibles de nécessiter une non-constante . Mieux encore, ils ne prennent que peu de temps à paramétrer (ou au moins à définir comme constantes réelles). Mais il faut également se méfier d'un comportement constant . leSystem.out.printlnpar exemple. Ce type d’hypothèse sur l’utilisation a tendance à changer et à être très coûteuse à corriger. Non seulement cela, mais IO comme cela rend la fonction impure (avec le fuseau horaire aller chercher un peu). Le paramétrage de ce comportement peut rendre la fonction plus pure et aboutir à une flexibilité et une testabilité accrues. Gros avantages pour un coût minimal (surtout si vous créez une surcharge qui utilise System.outpar défaut).


1
C'est juste une ligne directrice, les 1 vont bien, mais vous les regardez et vous dites «est-ce que cela changera jamais?» Nah. Et le println pourrait être paramétré avec une fonction d'ordre supérieur - bien que Java ne soit pas génial dans ces domaines.
Telastyn

5
@ KorayTugay: si le programme était vraiment destiné à votre femme qui viendrait à la maison, YAGNI vous dirait que votre version initiale est parfaite et que vous ne devriez plus investir de temps pour introduire des constantes ou des paramètres. YAGNI a besoin de contexte - votre programme est-il une solution jetable, ou un programme de migration ne dure-t-il que quelques mois, ou fait-il partie d'un énorme système ERP, destiné à être utilisé et maintenu sur plusieurs décennies?
Doc Brown

8
@KorayTugay: La séparation des E / S du calcul est une technique fondamentale de structuration de programme. Séparez la génération des données du filtrage des données de la transformation des données de la consommation des données de la présentation des données. Vous devriez étudier certains programmes fonctionnels, vous verrez alors cela plus clairement. En programmation fonctionnelle, il est assez courant de générer une quantité infinie de données, de filtrer uniquement les données qui vous intéressent, de transformer les données au format souhaité, de construire une chaîne à partir de celle-ci et d’imprimer cette chaîne dans 5 fonctions différentes, un pour chaque étape.
Jörg W Mittag

3
En suivant clairement YAGNI, il est nécessaire de refondre en permanence: "Utilisé sans refactoring en continu, cela pourrait conduire à un code désorganisé et à une refonte massive, connue sous le nom de dette technique." Ainsi, bien que YAGNI soit une bonne chose en général, il implique une grande responsabilité de révision et de réévaluation du code, ce que tous les développeurs / sociétés ne sont pas disposés à faire.
Flater

4
@Telastyn: Je suggère d'élargir la question à la question suivante: «Cela ne changera-t-il jamais et l'intention du code est-elle lisible de manière triviale sans nommer la constante ?» Même pour les valeurs qui ne changent jamais, il peut être pertinent de les nommer simplement pour que les choses restent lisibles.
Flater

27

Premièrement: aucun développeur de logiciel soucieux de la sécurité n’écrirait une méthode DestroyCity sans passer un 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 que cela soit applicable dans un autre contexte. Pourquoi est-il nécessaire d'autoriser une concaténation de chaînes?

Deuxièmement: Tous les codes exécutés doivent être entièrement spécifiés .

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

Cela pourrait se trouver dans le même fichier d’objet destroyCity(xyz), et dans un fichier de configuration:, destroy {"city": "XYZ"}"ou il pourrait s’agir d’une série de clics et de pressions de touches dans une interface utilisateur.

Troisièmement:

Chérie .. Pouvez-vous imprimer toutes les économies réalisées dans le monde entier pour la lumière du jour pour 2018 sur la console? J'ai besoin de vérifier quelque chose.

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

elle veut passer un formateur de chaînes personnalisé, ... intéressé par certaines périodes de mois, ... [et] intéressé par certaines périodes ...

Maintenant, le second ensemble d’exigences rend évidemment l’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. Il s'agit littéralement d'un programme si générique qu'il peut tout construire pour que l'ordinateur devienne ce dont vous avez besoin (dans les limites de son matériel).

En règle générale, les personnes qui ont besoin d'un logiciel ne veulent pas de produits génériques; ils veulent quelque chose de spécifique. En leur donnant plus d’options, vous leur rendez la vie plus compliquée. S'ils voulaient cette complexité, ils utiliseraient plutôt un compilateur, sans vous le demander.

Votre femme demandait des fonctionnalités et sous-spécifiait ses exigences. Dans ce cas, c'était apparemment délibéré, et en général c'est parce qu'ils ne savent pas mieux. Sinon, ils auraient juste utilisé le compilateur eux-mêmes. Le premier problème est que vous n'avez pas demandé plus de détails sur ce qu'elle voulait faire. Voulait-elle gérer cela pendant plusieurs années? Est-ce qu'elle le voulait dans un fichier CSV? Vous n'avez pas su quelles décisions elle voulait prendre, ni 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 déterminer comment communiquer ces décisions à l'aide de paramètres (et d'autres moyens configurables).

Cela étant dit, la plupart des clients manquent de communication, présument ou ignorent certains détails (décisions.) Qu’ils aimeraient vraiment prendre eux-mêmes ou qu’ils ne voulaient vraiment pas prendre (mais cela semble génial). C'est pourquoi les méthodes de travail telles que PDSA (plan-develop-study-study-act) 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, qui éclairent votre réflexion. Enfin, agissez en fonction de vos nouvelles connaissances - mettez à jour les exigences, affinez le processus, obtenez de nouveaux outils, etc. Ensuite, recommencez la planification. Cela aurait révélé toutes les exigences cachées au fil du temps et prouverait les progrès réalisé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 en cela que consiste le développement de logiciels. Retarder une décision sous forme d'argument peut simplifier la fonction actuelle, mais rend quelque part plus complexe. Cette décision est-elle pertinente dans cet autre endroit? Est-ce plus pertinent ici? À qui appartient cette décision? Vous décidez ceci; c'est du codage. Si vous répétez fréquemment des ensembles de décisions, il serait très avantageux de les codifier dans une abstraction. XKCD a une perspective utile ici. Et ceci est pertinent au niveau d’un système, qu’il s’agisse d’une fonction, d’un module, d’un programme, etc.

Les conseils donnés au départ impliquent que les décisions que votre fonction n'a pas le droit de prendre doivent être transmises en tant qu'argument. Le problème est qu’une DestroyBaghdadfonction peut en fait être celle qui a ce droit.


+1 aime la partie sur le compilateur!
Lee

4

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

Toute information codée en dur que vous avez dans votre fonction et 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);
                }
            }
        });
    }
}

Tu as:

The zoneIds
2018, 1, 1
System.out

Je déplacerais donc tous ces paramètres vers des paramètres sous une forme ou une autre. Vous pouvez faire valoir que les zonesId sont implicites dans le nom de la fonction. Peut-être voudriez-vous le rendre encore plus en le changeant en "DaylightSavingsAroundTheWorld" ou quelque chose du genre

Vous n'avez pas de chaîne de format; par conséquent, en ajouter une est une demande de fonctionnalité et vous devriez référer votre femme à l'instance de votre famille Jira. Il peut être mis sur l'arriéré et hiérarchisé lors de la réunion du comité de gestion de projet appropriée.


1
Vous (en vous adressant à OP) ne devez certainement pas ajouter une chaîne de format, car vous ne devriez rien imprimer. La seule chose à propos de ce code qui empêche absolument sa réutilisation est qu’il imprime. Il convient de renvoyer les zones, ou une carte des zones, au moment où elles désactivent l'heure d'été. (Bien que la raison pour laquelle il identifie uniquement le moment où le système passe à l' heure d'été, et non pas à l' heure d' été, je ne comprends pas. Cela ne semble pas correspondre à la déclaration du problème.)
David Conrad

l'exigence est d'imprimer sur console. vous pouvez supprimer le couplage étroit en passant dans le flux de sortie en tant que paramètre comme je le suggère
Ewan

1
Néanmoins, si vous souhaitez que le code soit réutilisable, vous ne devez pas imprimer sur la console. Ecrivez une méthode qui renvoie les résultats, puis écrivez un appelant qui les récupère et les imprime. Cela le rend également testable. Si vous voulez qu'il produise une sortie, je ne passerais pas dans un flux de sortie, je passerais dans un consommateur.
David Conrad

un flux ourput est un consommateur
Ewan

Non, un OutputStream n'est pas un consommateur .
David Conrad

4

En bref, ne concevez pas votre logiciel pour qu'il soit réutilisable, car aucun utilisateur final ne se soucie de savoir si vos fonctions peuvent être réutilisées. À la place, ingénieur pour la compréhensibilité de la conception - mon code est-il facile à comprendre pour quelqu'un d'autre ou mon futur moi oublieux à comprendre? - et flexibilité de conception- lorsque je dois inévitablement corriger des bogues, ajouter des fonctionnalités ou modifier d'autres fonctionnalités, dans quelle mesure mon code résistera-t-il aux modifications? La seule chose qui compte pour votre client est la rapidité avec laquelle vous pouvez répondre quand elle signale un bogue ou demande un changement. Le fait de poser ces questions sur votre conception a tendance à générer un code réutilisable, mais cette approche vous permet d'éviter les véritables problèmes que vous rencontrerez tout au long de la vie de ce code afin de pouvoir mieux servir l'utilisateur final plutôt que de rechercher des solutions fastidieuses et peu pratiques. "ingénierie" idéaux pour plaire à la barbe.

Pour quelque chose d'aussi simple que l'exemple que vous avez fourni, votre implémentation initiale est bonne à cause de sa petite taille, mais cette conception simple deviendra difficile à comprendre et fragile si vous essayez d'incorporer trop de flexibilité fonctionnelle (par opposition à une flexibilité de conception) dans une procédure. Vous trouverez ci-dessous une explication de l’approche que je privilégie pour la conception de systèmes complexes aux fins de la compréhensibilité et de la flexibilité, ce qui, j’espère, démontrera ce que j’entends par eux. Je n’utiliserais pas cette stratégie pour quelque chose qui pourrait être écrit en moins de 20 lignes en une seule procédure car quelque chose de si petit répond déjà à mes critères de compréhensibilité et de flexibilité.


Des objets, pas des procédures

Plutôt que d'utiliser des classes telles que des modules de la vieille école avec un ensemble de routines que vous appelez pour exécuter les tâches que votre logiciel doit faire, envisagez de modéliser le domaine en tant qu'objets qui interagissent et coopèrent pour accomplir la tâche à accomplir. Les méthodes d'un paradigme orienté objet ont été créées à l'origine pour être des signaux entre objets afin que Object1chacun Object2puisse faire son travail, quel qu'il soit, et éventuellement recevoir un signal de retour. En effet, le paradigme orienté objet concerne intrinsèquement la modélisation des objets de votre domaine et de leurs interactions plutôt qu'un moyen sophistiqué d’organiser les mêmes fonctions et procédures anciennes du paradigme Imperative. Dans le cas duvoid destroyBaghdadPar exemple, au lieu d’essayer d’écrire une méthode générique sans contexte pour gérer la destruction de Bagdad ou de toute autre chose (qui pourrait devenir rapidement complexe, difficile à comprendre et cassante), tout ce qui peut être détruit devrait permettre de comprendre comment se détruire. Par exemple, vous avez une interface qui décrit le comportement de choses pouvant ê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 Cityne sera jamais soin comment cela se passe, donc il n'y a aucune raison pour que le code d'exister partout en dehors de City::destroy, et en effet, la connaissance intime des rouages de l' Cityextérieur de lui - même serait un couplage étroit qui réduit félicité puisque vous devez tenir compte de ces éléments extérieurs au cas où vous auriez besoin de modifier le comportement de City. C'est le but véritable de l'encapsulation. Pensez-y comme si chaque objet avait sa propre API, ce qui devrait vous permettre de faire tout ce dont vous avez besoin pour pouvoir le laisser vous soucier de répondre à vos demandes.

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

Maintenant, que votre classe d'implémentation Citysoit Baghdaddépendante ou non du degré de générique du processus de destruction de la ville. Selon toute probabilité, un Citysera composé de plus petites pièces qui devront être détruites individuellement pour accomplir la destruction totale de la ville. Ainsi, dans ce cas, chacune de ces pièces serait également mise en œuvre Destroyableet chacune d'elles aurait pour instruction Cityde détruire de la même manière que quelqu'un de l'extérieur a demandé Cityà 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 mettre en œuvre l'idée d'un objet Bombdéposé sur un emplacement et détruisant tout dans un rayon donné, 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);
    }
}

ObjectsByRadiusreprésente un ensemble d'objets qui est calculé pour les Bombentrées depuis, car la Bombmanière dont ce calcul est effectué ne se soucie pas tant qu'il peut fonctionner avec les objets. Ceci est d'ailleurs réutilisable, mais l'objectif principal est d'isoler le calcul des processus de suppression Bombet de destruction des objets afin que vous puissiez comprendre chaque élément et son agencement et modifier le comportement d'un élément individuel sans avoir à remodeler l'ensemble de l'algorithme. .

Des interactions, pas des algorithmes

Plutôt que d'essayer de deviner le bon nombre de paramètres pour un algorithme complexe, il est plus judicieux de modéliser le processus en tant qu'ensemble d'objets en interaction, chacun avec des rôles extrêmement étroits, car il vous permettra 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 la modification des objets instanciés dans votre main()méthode.

Je donnerais quelque chose à votre exemple initial, mais honnêtement, je ne peux 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, le résultat de celui-ci peut être formaté de différentes manières, la méthode que je préfère pour décomposer cela 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 ZonedDateTimedirectement. L'idée ici est que chaque calcul est encapsulé dans son propre objet. Il ne fait aucune hypothèse sur le nombre de fois qu'il devrait être exécuté ou comment il devrait formater le résultat. Il s’agit exclusivement de réaliser la forme la plus simple du calcul. Cela rend le changement facile à comprendre et flexible. De même, le Resultest exclusivement concerné par l’encapsulation du résultat du calcul, et le FormattedResultexclusivement sur l’interaction avec le Resultpour 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, car elles ont chacune une tâche bien définie . Il est également beaucoup plus simple de modifier l’avenir tant que les interfaces ne changent pas (ce qui est moins le cas si vous avez correctement minimisé les responsabilités de vos objets). Notremain()méthode 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é spécialement inventée pour résoudre le problème de complexité / flexibilité du paradigme de l’impératif, car il n’ya pas de bonne réponse (que tout le monde peut s’entendre ou arriver à un accord de façon indépendante) sur la manière optimale de fonctionner. spécifier les fonctions et les procédures impératives dans l'idiome.


C’est une réponse très détaillée et réfléchie, mais malheureusement, je pense qu’elle manque la cible de ce que le PO demandait vraiment. Il ne demandait pas une leçon sur les bonnes pratiques de POO pour résoudre son exemple spécieux, il posait des questions sur les critères selon lesquels nous décidons d'investir du temps dans une solution par rapport à une généralisation.
maple_shaft

@maple_shaft Peut-être que j'ai raté la cible, mais je pense que vous l'avez aussi fait. Le PO n'interroge pas sur l'investissement du temps par rapport à la généralisation. Il demande "Comment puis-je savoir à quel point mes méthodes doivent être réutilisables?" Il poursuit en posant dans le corps de sa question, "Si destroyCity (Ville ville) vaut mieux que destroyBaghdad (), est-ce que takeActionOnCity (Action action, Ville ville) est encore meilleur? Pourquoi / pourquoi pas?" J'ai plaidé en faveur d'une approche alternative des solutions d'ingénierie qui, à mon avis, résout le problème de la définition générique de la création de méthodes et a fourni des exemples à l'appui de ma demande. Je suis désolé que tu n'aies pas aimé.
Stuporman

@maple_shaft Franchement, seul le PO peut prendre la décision si ma réponse est pertinente pour sa question puisque le reste d'entre nous pourrait mener des guerres pour défendre nos interprétations de ses intentions, qui pourraient toutes être également fausses.
Stuporman

@maple_shaft J'ai ajouté une introduction pour tenter de clarifier le lien entre cette question et la question en fournissant une délimitation claire entre la réponse et l'exemple d'implémentation. Est-ce mieux?
Stuporman

1
Honnêtement, si vous appliquez tous ces principes, la réponse viendra naturellement, sera lisse et très lisible. De plus, modifiable sans beaucoup d'histoires. Je ne sais pas qui vous êtes, mais j'aimerais qu'il y ait plus de vous! Je continue à déchirer du code réactif pour un OO décent, et c'est TOUJOURS deux fois plus petit, plus lisible, plus contrôlable, et il a toujours des threads / fractionnements / mappages. Je pense que React est destiné aux personnes qui ne comprennent pas les concepts "de base" que vous avez énumérés.
Stephen J

3

Expérience , connaissance du domaine et revues de code.

Et, quels que soient votre niveau d' expérience , votre connaissance du domaine ou votre équipe , vous ne pouvez éviter le besoin de refactoriser au besoin.


Avec Experience , vous allez commencer à reconnaître les modèles dans les méthodes (et les classes) non spécifiques à un domaine que vous écrivez. Et, si vous êtes intéressé par le code DRY, vous éprouverez de la tristesse lorsque vous écrivez une méthode dont vous savez instinctivement que vous allez écrire des variations dans le futur. Ainsi, vous écrirez intuitivement à la place un plus petit dénominateur commun paramétré.

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

Avec la connaissance du domaine , vous aurez une idée des concepts d’entreprise étroitement liés, des concepts qui ont des variables, qui sont relativement statiques, etc.

Avec les revues de code, la sous-paramétrisation et la sur-paramétrisation seront plus facilement détectées avant de devenir un code de production, car vos pairs auront (espérons-le) des expériences et des perspectives uniques, à la fois sur le domaine et sur le codage.


Cela dit, les nouveaux développeurs n'auront généralement pas ces Spidey Senses ou un groupe de pairs expérimentés sur lesquels s'appuyer tout de suite. Et même les développeurs expérimentés bénéficient d'une discipline de base leur permettant de se familiariser avec les nouvelles exigences - ou avec les journées brumeuses. Alors, voici ce que je suggérerais pour commencer :

  • Commencez par la mise en œuvre naïve, avec une paramétrisation minimale.
    (Incluez évidemment tous les paramètres dont vous savez déjà qu'ils auront besoin ...)
  • Supprimez les nombres et les chaînes magiques, en les déplaçant vers des configurations et / ou des paramètres
  • Factorisez les "grandes" méthodes en méthodes plus petites et bien nommées
  • Refactoriser les méthodes hautement redondantes (si cela 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 assoyez pour écrire une méthode que vous savez déjà hautement redondante avec une méthode existante , passez directement au refactoring si cela vous convient. (Si cela ne prend pas beaucoup plus de temps à refactoriser qu'à écrire, tester et maintenir deux méthodes.)

Mais, en plus d’avoir beaucoup d’expérience et ainsi de suite, je conseille un code assez minimaliste DRY-ing. Il n’est pas difficile de repenser les 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 son équivalent "WET".


2
Donc, il n'y a pas de bonne réponse à If destoryCity(City city) is better than destoryBaghdad(), is takeActionOnCity(Action action, City city) even better?? C'est une question oui / non, mais il n'y a pas de réponse, non? Ou la supposition initiale est fausse, destroyCity(City)pourrait ne pas être nécessairement meilleure et cela dépend en fait ? Cela ne signifie donc pas que je ne suis pas un ingénieur logiciel non formé sur le plan éthique, car j'ai directement mis en œuvre sans aucun paramètre? Je veux dire quelle est la réponse à la question concrète que je pose?
Koray Tugay

Votre question pose en quelque sorte quelques questions. La réponse à la question du titre est "Expérience, connaissance du domaine, revues de code ... et ... n'ayez pas peur de refactoriser." La réponse à une question concrète "sont-ils les bons paramètres pour la méthode ABC" est-elle ... "Je ne sais pas. Pourquoi me demandez-vous? Quelque chose ne va pas avec le nombre de paramètres qu'il a actuellement? Si oui, articulez le réparer. " ... Je pourrais vous renvoyer au "PPAO" pour plus de précisions : Vous devez comprendre pourquoi vous faites ce que vous faites!
svidgen

Je veux dire ... nous allons faire un pas en arrière de la destroyBaghdad()méthode. Quel est le contexte? Est-ce un jeu vidéo où la fin du jeu entraîne la destruction de Bagdad ??? Si oui ... destroyBaghdad()pourrait être une méthode parfaitement raisonnable nom / signature ...
svidgen

1
Vous n'êtes donc pas d'accord avec la citation citée dans ma question, n'est-ce pas? It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure.Si vous étiez dans la pièce avec Nathaniel Borenstein, vous lui diriez que cela dépend en fait et que sa déclaration est fausse? Je veux dire que c’est beau que beaucoup de gens répondent, dépensent leur temps et leur énergie, mais je ne vois aucune réponse concrète nulle part. Spidey-sense, code reviews .. Mais à quoi répond-on is takeActionOnCity(Action action, City city) better? null?
Koray Tugay

1
@svidgen Bien sûr, une autre façon de résumer ceci sans aucun effort supplémentaire consiste à inverser la dépendance - demandez à la fonction de renvoyer une liste de villes au lieu de les exécuter (comme "println" dans le code d'origine). Ceci peut être résumé si nécessaire, mais ce seul changement prend en charge environ la moitié des exigences supplémentaires de la question initiale - et au lieu d'une fonction impure pouvant faire toutes sortes de mauvaises choses, vous avez simplement une fonction. qui renvoie une liste et l’ appelant fait le mauvais choix.
Luaan

2

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

Comme réutilisable vous, l'utilisateur, 1 ont besoin d'être

Il s’agit essentiellement de faire preuve de discernement - si le coût de la conception et de la maintenance de l’abstraction sera remboursé (= temps et efforts), cela vous fera économiser en bout de ligne.

  • Notez la phrase "en bas de la ligne": il existe un mécanisme de paiement ici, donc cela dépendra de la quantité de travail que vous ferez avec ce code. Par exemple:
    • S'agit-il d'un projet ponctuel ou va-t-il être amélioré progressivement sur une longue période?
    • Êtes-vous confiant dans votre conception ou devrez-vous probablement la supprimer ou la modifier radicalement pour le prochain projet / jalon (par exemple, essayer 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) de votre application. Plus de fois, vous pensez que vous pouvez, mais vous ne pouvez pas. Les règles de base sont le principe de YAGNI et la règle de trois - les deux insistent sur le fait de travailler à partir de ce que vous savez, maintenant.

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


1

Vous pouvez suivre un processus clair:

  • Ecrivez un test d'échec pour une seule fonctionnalité qui est en soi une "chose" (c'est-à-dire, pas une division arbitraire d'une fonctionnalité où aucune des deux n'a de sens).
  • Écrivez le code minimum absolu pour le rendre vert, pas une ligne de plus.
  • Rincer et répéter.
  • (Refacturez implacablement si nécessaire, ce qui devrait être facile en raison de la grande couverture de test.)

Au moins, de l'avis de certaines personnes, cela donne un code plutôt optimal, puisqu'il est aussi petit que possible, chaque fonction finie prend le moins de temps possible (ce qui peut être vrai ou non si vous regardez le produit fini. produit après refactoring), et sa couverture de test est très bonne. Cela évite aussi sensiblement les méthodes ou les classes trop génériques trop sophistiquées.

Cela vous donne également des instructions très claires quand il faut faire des choses génériques et quand se spécialiser.

Je trouve votre exemple de ville bizarre. Je serais très probablement jamais jamais hardcode 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 signalisation sont une couleur si omniprésente que vous pouvez simplement vous en sortir (et vous pouvez toujours les refactoriser). 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 jamais et il n'y a pas vraiment d'alternative non plus.

Votre méthode de première heure avancée est tout simplement cassée. Bien que conforme au cahier des charges, le code 2018 codé en dur 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, 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, eh bien, le temps passe. Mais à part cela, tout le reste est en discussion. Si vous donnez une année simple et calculez toujours l'année complète, continuez. La plupart des choses que vous avez énumérées (formatage, choix d'une plage plus petite, etc.) indiquent que votre méthode en fait trop et qu'elle devrait plutôt renvoyer une liste / un tableau de valeurs afin que l'appelant puisse se formater / filtrer lui-même.

Mais au bout du compte, il s’agit essentiellement d’opinion, de goût, d’expérience et de préjugés personnels. Ne vous inquiétez donc pas.


En ce qui concerne l’avant-dernier paragraphe, examinez les «exigences» données initialement, c’est-à-dire celles sur lesquelles reposait la première méthode. Il spécifie 2018, donc le code est techniquement correct (et correspond probablement à votre approche axée sur les fonctionnalités).
dimanche

@dwizum, les exigences sont correctes, mais le nom de la méthode est trompeur. En 2019, tout programmeur se contentant de regarder le nom de la méthode supposerait qu'il fait quoi que ce soit (peut-être renverrait les valeurs de l'année en cours), pas 2018 ... J'ajouterai une phrase à la réponse pour préciser ce que je voulais dire.
AnoE

1

Je suis venu à l'opinion qu'il existe deux sortes 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. Cela s'applique à des choses comme des listes, des hashmaps, des magasins de clés / valeurs, des correspondeurs de chaînes (regex, glob, ...), des tuples, unification, des arbres de recherche (profondeur d'abord, largeur d'abord, approfondissement itératif, ...) , combinateurs d’analyseurs, caches / memoisers, lecteurs / graveurs de format de données (expressions-s, XML, JSON, protobuf, ...), files de tâches, etc.

Ces choses sont tellement générales, d’une manière très abstraite, qu’elles sont réutilisées partout dans la programmation quotidienne. Si vous vous trouvez en train d'écrire un code à usage spécifique, ce qui serait plus simple s'il était plus abstrait / général (par exemple, si nous avons "une liste de commandes de clients", nous pourrions jeter la "commande de client" pour obtenir "une liste" ) alors ce pourrait être une bonne idée de le retirer. Même s'il n'est pas réutilisé, il nous permet de découpler les fonctionnalités non liées.

Le deuxième type est celui où nous avons un code concret, qui résout un problème réel, mais le fait en prenant toute une série de décisions. Nous pouvons le rendre plus général / réutilisable en "codant de manière souple" ces décisions, par exemple en les transformant en paramètres, en compliquant la mise en œuvre et en cuisant des détails encore plus concrets (c.-à-d. Savoir quels crochets nous pourrions souhaiter pour des dérogations). Votre exemple semble être de cette sorte. Le problème avec ce type de réutilisabilité est que nous pouvons finir par essayer de deviner les cas d'utilisation d'autres personnes, ou notre avenir. Nous pourrions finir par avoir tellement de paramètres que notre code n'est pas utilisable, encore moins réutilisable! En d’autres termes, lorsqu’il appelle, cela demande plus d’efforts que d’écrire sa 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 ne peuvent en tenir les paramètres, ou ces utilisateurs potentiels préfèrent lancer leurs propres (heck, regardez tous les normes et bibliothèques dont les auteurs préfixés par le mot "Simple", pour les distinguer des prédécesseurs!).

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


Pouvons-nous dire que vous êtes d'accord pour dire que ma première prise s'est bien déroulée et que même l'année a été codée en dur? Ou, si vous appliquiez initialement cette exigence, feriez-vous de l'année un paramètre lors de votre première prise?
Koray Tugay

Votre première prise était bonne, car il fallait un script unique pour «vérifier quelque chose». Il échoue à son test «éthique», mais elle échoue au test «pas de dogme». "Elle pourrait dire ..." est en train d'inventer des exigences dont vous n'aurez pas besoin.
Warbo

Nous ne pouvons pas dire quelle "ville détruite" est "meilleure" sans plus d'infos: destroyBaghdadc'est un script unique (ou du moins, c'est idempotent). Peut - être en train de détruire toute la ville serait une amélioration, mais si les destroyBaghdadtravaux en inondant le Tigre? Cela peut être réutilisable pour Mossoul et Bassorah, mais pas pour La Mecque ou Atlanta.
Warbo

Je vois que vous n'êtes pas d'accord avec Nathaniel Borenstein, le propriétaire de la citation. J'essaie de comprendre lentement, je pense en lisant toutes ces réponses et les discussions.
Koray Tugay

1
J'aime cette différenciation. Ce n'est pas toujours clair, et il y a toujours des "cas frontières". Mais en général, je suis également un fan des "blocs de construction" (souvent sous forme de staticméthodes) qui sont purement fonctionnels et de bas niveau, et contrairement à cela, décider de la "configuration des paramètres et des crochets" est généralement celui où vous devez construire des structures qui demandent à être justifiées.
Marco13

1

Il existe déjà de nombreuses réponses excellentes et élaborées. Certaines d’entre elles vont profondément dans les détails, exposent certains points de vue sur les méthodologies de développement de logiciels en général, et certaines d’entre elles ont certainement des éléments controversés ou des "opinions" parsemées.

La réponse de Warbo a déjà souligné différents types de réutilisabilité. À savoir, si quelque chose est réutilisable parce que c'est un élément fondamental, ou si quelque chose est réutilisable parce que c'est "générique" d'une certaine manière. En référence à ce dernier point, il y a quelque chose que je considérerais comme une sorte de mesure de réutilisation:

Si une méthode peut en imiter une autre.

En ce qui concerne l’exemple tiré de la question: imaginons 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 d' autres programmeurs sont supposé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 juste

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

Les prochaines "demandes de fonctionnalités" et les généralisations suivent le même schéma. Donc, si et seulement s'il existe 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 de celles plus spécifiques:

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

Si vous aviez prévu des extensions et des demandes de fonctionnalités futures, et si vous aviez du temps à votre disposition et que vous vouliez passer un week-end ennuyeux avec des généralisations (potentiellement inutiles), vous auriez pu commencer par la plus générique dès le début. Mais seulement comme une méthode privée . Tant que vous exposez uniquement la méthode simple demandée par le client en tant que méthode publique , vous êtes en sécurité.

tl; dr :

En réalité, la question n'est pas tant de savoir "à quel point une méthode doit être réutilisable". La question est de savoir combien de cette réutilisabilité est exposée et à quoi ressemble l'API. Créer une API fiable et capable de résister à l'épreuve du temps (même si d'autres exigences se présentent ultérieurement) est un art et un métier, et le sujet est beaucoup trop complexe pour être couvert ici. Jetez un œil à cette présentation de Joshua Bloch ou au wiki du livre de conception d'API pour commencer.


dayLightSavings()appeler dayLightSavings(2018)ne me semble pas une bonne idée.
Koray Tugay

@KorayTugay Lorsque la demande initiale est d'imprimer "l'heure d'été 2018", tout va bien. En fait, c'est exactement la méthode que vous avez implémentée à l'origine. Si cela devait imprimer "l'heure avancée pour l' année en cours , alors vous appelleriez bien sûr dayLightSavings(computeCurrentYear());. ...
Marco13

0

En règle générale, votre méthode doit être aussi réutilisable que… réutilisable.

Si vous prévoyez d'appeler votre méthode à un seul endroit, il ne doit contenir que des paramètres connus du site d'appels et non disponibles pour cette méthode.

Si vous avez plus d’appelants, vous pouvez introduire de nouveaux paramètres à condition que d’autres appelants puissent les transmettre; sinon vous avez besoin d'une nouvelle méthode.

Comme le nombre d’appelants peut augmenter avec le temps, vous devez être prêt à refactoriser ou à surcharger. Dans de nombreux cas, cela signifie que vous devez vous sentir en sécurité pour sélectionner une expression et exécuter l'action “extraire le paramètre” de votre IDE.


0

Réponse ultra-courte: Moins le couplage ou la dépendance à un autre code de votre module générique est important, plus il peut être réutilisé.

Votre exemple ne dépend que de

import java.time.*;
import java.util.Set;

donc, en théorie, il peut être très réutilisable.

En pratique, je ne pense pas que vous ayez jamais un deuxième dossier utilisateur qui a besoin de ce code, donc, suivant le principe de yagni , je ne le rendrais pas réutilisable s’il n’ya pas plus de 3 projets différents qui ont besoin de ce code.

Les autres aspects de la réutilisation sont la facilité d’utilisation et la documentation qui sont liées au développement piloté par les tests : il est utile de disposer d’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 bibliothèque.


0

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

Être un bon programmeur, c'est être capable de prédire l'avenir.

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

Les autres facteurs à prendre en compte sont le temps et les efforts supplémentaires nécessaires et la complexité de votre code. Parfois, vous avez de la chance, et résoudre le problème plus général est en réalité plus simple! (Du moins sur le plan conceptuel, sinon en termes de quantité de code.) Mais le plus souvent, il existe un coût de complexité, de temps et d’efforts.

Donc, si vous pensez qu'une généralisation sera très probablement nécessaire, cela en vaut souvent la peine (sauf si cela ajoute 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 J'avais de la chance: c'était un changement facile, et je devinais qu'il était fort probable qu'il fût désirable. C'est souvent plus difficile à juger. Mais cela vaut toujours la peine d'essayer de prédire, et l'expérience est souvent un bon guide .)


0

Tout d’abord, la meilleure réponse à la question «Comment savoir si mes méthodes doivent être réutilisables?» Est «Expérience». Faites-le plusieurs milliers de fois, et vous obtiendrez généralement la bonne réponse. Mais, en guise de teaser, je peux vous donner le dernier 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 ont des conseils spécifiques. Je voulais donner quelque chose de plus générique ... parce que l'ironie est trop amusante pour la laisser passer!

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

Je me concentre sur l’établissement d’une échelle allant de "irréversible" à "réversible". C'est une échelle lisse. La seule chose véritablement irréversible est le "temps passé sur le projet". Vous ne récupérerez jamais ces ressources. Les situations de «menottes dorées» telles que l’API Windows sont un peu moins réversibles. Les fonctionnalités obsolètes sont conservées dans cette API pendant des décennies, car le modèle économique de Microsoft en fait la demande. Si vous avez des clients dont la relation serait définitivement endommagée en annulant certaines fonctionnalités de l'API, cela devrait alors être considéré comme irréversible. À l'autre bout de l'échelle, vous avez des choses comme le code prototype. Si vous n'aimez pas où cela va, vous pouvez simplement le jeter. Les API à usage interne pourraient être légèrement moins réversibles. Ils peuvent être refactorisés sans déranger un client,le temps (la ressource la plus irréversible de tous!)

Alors mettez-les sur une échelle. Vous pouvez maintenant appliquer une heuristique: plus une chose est réversible, plus vous pouvez l’utiliser pour des activités futures. 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 ce que vous regrettez.

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

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

Si vous avez 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 dans lequel j'ai eu la tâche brutale d'expliquer à un client pourquoi il ne pouvait pas obtenir ce qu'il souhaitait en raison de sa flexibilité construite en 10 couches, ce qu'il n'était jamais supposé voir. Nous nous sommes inscrits dans un coin. Nous nous sommes noués avec toute la flexibilité dont nous pensions avoir besoin.

Ainsi, lorsque vous effectuez cette opération de généralisation / DRY, veillez toujours à ce que votre client soit au courant . 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 dira efficacement ses besoins futurs. Si vous n'avez pas le talent, la plupart d'entre nous ne nous fions qu'à des conjectures! (surtout avec les conjoints!) Certains clients voudront une grande flexibilité et seront disposés à accepter le coût supplémentaire que représente le développement de toutes ces couches, car ils bénéficient directement de la flexibilité de ces couches. D'autres clients ont plutôt des exigences fixes fixes et préfèrent un développement plus direct. Votre client vous indiquera le degré de flexibilité et le nombre de couches de généralisation à rechercher.


Bien, il devrait y avoir d'autres personnes qui ont fait cela 10000 fois, alors pourquoi devrais-je le faire 10000 fois et acquérir de l'expérience quand je peux apprendre des autres? Parce que la réponse sera différente pour chaque individu, les réponses des personnes expérimentées ne me concernent pas? Aussi dans Your customer will tell you how much flexibility and how many layers of generalization you should seek.quel monde est-ce?
Koray Tugay

@ KorayTugay C'est un monde des affaires. Si vos clients ne vous disent pas quoi faire, vous n'écoutez pas assez fort. Bien sûr, ils ne vous le diront pas toujours avec des mots, mais ils vous le diront d'une autre manière. Experience vous aide à écouter leurs messages les plus subtils. Si vous ne possédez pas encore les compétences, recherchez un membre de votre entreprise capable d'écouter ces astuces client subtiles et demandez-leur conseil. Quelqu'un aura cette compétence, même s'il est le PDG ou le marketing.
Cort Ammon

Dans votre cas spécifique, si vous n'aviez pas réussi à éliminer la corbeille parce que vous étiez trop occupé à coder une version généralisée de ce problème de fuseau horaire au lieu de pirater la solution spécifique, que ressentirait votre client?
Cort Ammon

Vous êtes donc d'accord pour dire que ma première approche était la bonne: coder en dur en 2018 au lieu de paramétrer l'année? (au fait, ce n'est pas vraiment écouter mon client, je pense, l'exemple des ordures. Connaître votre client. Même si vous obtenez le support de The Oracle, il n'y a pas de message subtil à écouter quand elle dit que j'ai besoin d'une liste de jour. changements pour 2018.) Merci pour votre temps et répondez au fait.
Koray Tugay

@ KorayTugay Sans connaître d'autres détails, je dirais que le codage en dur était la bonne approche. Vous n'aviez aucun moyen de savoir si vous alliez avoir besoin d'un futur code DLS, ni aucune idée du type de demande qu'elle pourrait faire par la suite. Et si votre client essaie de vous tester, il obtient ce qu'il obtient = D
Cort Ammon

0

aucun ingénieur logiciel formé sur le plan éthique ne consentirait jamais à écrire une procédure DestroyBaghdad. Une éthique professionnelle de base l'obligerait plutôt à écrire une procédure DestroyCity, à laquelle Bagdad pourrait être paramétré.

C'est ce que les cercles du génie logiciel avancés appellent une "blague". Les blagues ne doivent pas forcément être ce que nous appelons «vraies», bien que 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". La tâche qui consiste à rédiger une procédure générale pour détruire une ville est, nous pouvons le supposer, des ordres de grandeur allant au-delà de ce qui est nécessaire pour détruire un site spécifique.ville. Autrement, quiconque a détruit une ou plusieurs villes (le Josué biblique, dirons-nous, ou le président Truman) pourrait trivialement généraliser ce qu’il a fait et pouvoir détruire absolument n’importe quelle ville à sa guise. Ce n'est en fait pas le cas. Les méthodes que ces deux personnes ont l'habitude d'utiliser pour détruire un petit nombre de villes spécifiques ne fonctionneraient pas nécessairement sur n'importe quelle ville à aucun moment. Une autre ville dont les murs ont une fréquence de résonance différente ou dont les défenses aériennes à haute altitude sont plutôt meilleures nécessiterait des modifications d'approche mineures ou fondamentales (une trompette ou une roquette à la hauteur différente).

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

Développer et tester un moyen tout à fait général qui détruira n'importe quelle ville, avant d'accepter de détruire une seule ville, est une approche désespérément inefficace. Aucun ingénieur logiciel formé sur le plan éthique n'essaierait de généraliser un problème nécessitant beaucoup plus de travail que ce que leur employeur / client doit réellement payer, sans obligation manifeste.

Alors qu'est-ce qui est vrai? Parfois, ajouter de la généralité est trivial. Devrions-nous alors toujours ajouter de la généralité quand il est trivial de le faire? Je dirais toujours "non, pas toujours", à cause du problème de la maintenance à long terme. En supposant qu'au moment de la rédaction, toutes les villes sont fondamentalement les mêmes, je vais donc de l'avant avec DestroyCity. Une fois que j’ai écrit cela, ainsi que des tests d’intégration qui (en raison de l’espace infiniment énumérable d’entrées) parcourent toutes les villes connues et s’assurent que la fonction fonctionne sur chaque détruit le clone?

En pratique, cette fonction sert uniquement à détruire Bagdad. Supposons que quelqu'un construise une nouvelle ville résistante à mes techniques (elle est souterraine ou quelque chose du genre). 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 poursuivre ma campagne de terreur contre les civils innocents d'Irak, je dois trouver un moyen de détruire Subterrania. Peu importe que ce soit éthique ou non, c'est idiot et une perte de temps .

Alors, est-ce que vous voulez vraiment une fonction capable de produire une heure avancée pour n’importe quelle année, uniquement pour produire des données pour 2018? Peut-être, mais il va certainement falloir déployer des efforts supplémentaires pour mettre en place des scénarios de test. Il faudra peut-être beaucoup d'efforts pour obtenir une meilleure base de données de fuseaux horaires que celle que vous avez réellement. Ainsi, par exemple, en 1908, la ville de Port Arthur, en Ontario, commençait la période d’été à compter du 1er juillet. Est-ce dans la base de données du fuseau horaire de votre système d'exploitation? Je ne pensais pas, alors votre fonction généralisée est fausse . Il n’ya rien d’éthique dans l’écriture de code qui fait des promesses qu’il ne peut pas tenir.

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

Toutefois, si vous saviez pourquoi votre femme souhaitait consulter cette liste d'heures d'été, vous auriez alors une opinion éclairée sur le point de savoir si elle est susceptible de poser la même question à nouveau en 2019 et, le cas échéant, si vous pouvez vous sortir de cette boucle en donnant elle est 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 "devrait-elle généraliser aux dernières années" pourrait être "oui". Mais vous vous créez encore un autre problème: les données de fuseau horaire dans le futur ne sont que provisoires. Par conséquent, si elle les gère pour 2019 aujourd'hui, elle peut ou non se rendre compte que cela la nourrit de son mieux. Il vous reste donc encore à rédiger une 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 fuseaux horaires blah blah, voici le lien pour voir leurs politiques en matière de mise à jour blah blah"). Si vous refusez le cas particulier, elle ne peut pas continuer à travailler pour le compte pour lequel elle avait besoin des données de 2018, à cause d'un non-sens à propos de 2019 dont elle ne se soucie même pas encore.

Ne faites pas de tâches difficiles sans y avoir bien réfléchi, simplement parce qu'une blague vous a dit de le faire. Est-ce utile? Est-ce assez bon marché pour ce degré d'utilité?


0

C'est une ligne facile à tracer, car la réutilisation telle que définie par l'architecture des astronautes est complexe.

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

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

Adonnons le fantasme invraisemblable selon lequel vous avez trouvé quelque chose qui devrait véritablement figurer dans votre cadre de choix. Vous ne pouvez pas simplement taper 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 critiques connus, pour tous les modes de défaillance imaginables, cas de test pour diagnostics, données de test, documentation technique, documentation utilisateur, gestion des versions, support scripts, tests de régression, gestion du changement ...

Votre employeur est-il content de payer pour 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 serve un besoin réel et actuel, ne le faites pas, parce que YAGNI.

Regardons un exemple concret auquel je viens de faire face: HTMLRenderer. La mise à l'échelle n'était pas correcte lorsque j'ai essayé de rendre 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 n'échelonnait pas) plutôt que GDI + (qui le faisait, mais pas antialias) car je devais parcourir six niveaux d'indirection dans deux assemblys avant de trouver code qui fait n'importe quoi .

Dans ce cas, je pardonnerai à l'auteur. L'abstraction est en fait nécessaire car il s'agit d'un code d'infrastructure 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 ne faites probablement pas quelque chose d'extrêmement complexe (rendu HTML avec des feuilles de style) ciblant plusieurs plates-formes avec l'objectif affiché de hautes performances sur toutes les plates-formes.

Votre code est presque certainement encore 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 que dans votre État, dans une application qui n'est pas à vendre.

Tout ce qui indirection résout un problème que vous n'avez pas et rend votre code beaucoup plus difficile à lire, ce qui augmente considérablement les coûts d'entretien et est un très mauvais service à 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 est favorable au développement piloté par les tests, mais je pense que TDD est également bouleversé, car cela présuppose que l’entreprise comprenne de manière claire, complète et correcte ses besoins. TDD est idéal pour la NASA et pour les logiciels de contrôle des équipements médicaux et des voitures autonomes, mais beaucoup trop cher pour tout le monde.


Incidemment, il n’est pas possible de prévoir toutes les économies d’été du 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 que Dieu ne fait pas l'heure avancée.


Bien que je sois d’accord avec ce que vous avez dit sur les abstractions et l’effort d’apprentissage, et en particulier quand il s’agit de l’abomination appelée "JavaScript", je suis tout à fait en désaccord avec la première phrase. La réutilisation peut se produire à plusieurs niveaux, cela peut aller trop loin , et le code à jeter peut être écrit par des programmeurs jetables . Mais il y a des gens assez idéalistes pour au moins viser un code réutilisable. Dommage pour vous si vous ne voyez pas les avantages que cela peut avoir.
Marco13

@ Marco13 - Puisque votre objection est raisonnable, je vais développer ce point.
Peter Wone

-3

Si vous utilisez au moins java 8, vous devez écrire la classe WorldTimeZones pour fournir ce qui semble être en substance une collection de fuseaux horaires.

Ajoutez ensuite une méthode de filtrage (filtre de prédicat) à la classe WorldTimeZones. Cela donne à l'appelant la possibilité de filtrer ce qu'il veut en transmettant une expression lambda en tant que paramètre.

En substance, la méthode de filtrage unique prend en charge le filtrage sur tout ce qui est contenu dans la valeur transmise au prédicat.

Vous pouvez également ajouter une méthode stream () à votre classe WorldTimeZones qui génère un flux de fuseaux horaires lorsqu'elle est appelée. L’appelant peut alors filtrer, mapper et réduire à sa guise sans que vous ayez à écrire de spécialisations.


3
Ce sont de bonnes idées de généralisation, mais cette réponse passe complètement à côté de ce qui est demandé dans la question. La question n'est pas de savoir comment généraliser au mieux la solution, mais plutôt de tracer une ligne de démarcation entre généralisations et de pondérer ces considérations de manière éthique.
maple_shaft

Je dis donc que vous devriez les peser par le nombre de cas d’utilisation qu’ils supportent, modifiés par la complexité de la création. Une méthode prenant en charge un cas d'utilisation n'a pas la même valeur qu'une méthode prenant en charge 20 cas d'utilisation. D'autre part, si vous ne connaissez qu'un cas d'utilisation et qu'il faut 5 minutes pour le coder, allez-y. Les méthodes de codage prenant en charge des utilisations spécifiques vous informent de la manière de généraliser.
Rodney P. Barbati
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.