Woher weiß ich, wie wiederverwendbar meine Methoden sein sollten? [geschlossen]

133

Ich kümmere mich zu Hause um meine eigenen Angelegenheiten und meine Frau kommt zu mir und sagt

Schatz. Kannst du alle Tageslichteinsparungen der Welt für 2018 in der Konsole drucken? Ich muss etwas überprüfen.

Und ich bin super glücklich, denn darauf hatte ich mein ganzes Leben lang mit meiner Java-Erfahrung gewartet und mir Folgendes ausgedacht:

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

Aber dann sagt sie, dass sie mich nur getestet hat, ob ich ein ethisch ausgebildeter Software-Ingenieur bin, und sagt mir, dass es so aussieht, als wäre ich nicht da (von hier genommen ).

Es sollte beachtet werden, dass kein ethisch geschulter Softwareingenieur jemals zustimmen würde, eine DestroyBaghdad-Prozedur zu schreiben. Eine grundlegende Berufsethik würde stattdessen erfordern, dass er eine DestroyCity-Prozedur schreibt, für die Bagdad als Parameter angegeben werden könnte.

Und mir geht es gut, ok, du hast mich erwischt. Verpass jedes Jahr , das du magst.

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..

Aber woher weiß ich, wie viel (und was) parametriert werden muss? Immerhin könnte sie sagen ..

  • Sie möchte einen benutzerdefinierten String-Formatierer übergeben. Vielleicht gefällt ihr das Format, in dem ich bereits drucke, nicht: 2018-10-28T02:00+01:00[Arctic/Longyearbyen]

void dayLightSavings(int year, DateTimeFormatter dtf)

  • Sie interessiert sich nur für bestimmte Monatsperioden

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

  • Sie interessiert sich für bestimmte Stundenperioden

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

Wenn Sie eine konkrete Frage suchen:

Wenn destroyCity(City city)ist besser als destroyBaghdad(), ist takeActionOnCity(Action action, City city)noch besser? Warum Warum nicht?

Immerhin kann ich es Action.DESTROYdann erstmal mit anrufen Action.REBUILD, oder?

Aber Maßnahmen in Städten zu ergreifen, reicht mir nicht aus, wie wäre es takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)? Immerhin möchte ich nicht anrufen:

takeActionOnCity(Action.DESTORY, City.BAGHDAD);

dann

takeActionOnCity(Action.DESTORY, City.ERBIL);

und so weiter, wenn ich kann:

takeActionOnGeographicArea(Action.DESTORY, Country.IRAQ);

ps Ich habe meine Frage nur um das Zitat aufgebaut, das ich erwähnt habe. Ich habe nichts gegen ein Land, eine Religion, eine Rasse oder was auch immer auf der Welt. Ich versuche nur, einen Punkt zu machen.

Koray Tugay
quelle
71
Der Punkt, den Sie hier ansprechen, habe ich oft versucht auszudrücken: Allgemeingültigkeit ist teuer und muss daher durch spezifische, klare Vorteile gerechtfertigt sein . Aber es geht tiefer als das; Programmiersprachen werden von ihren Designern erstellt, um einige Arten der Allgemeinheit einfacher zu machen als andere, und dies beeinflusst unsere Entscheidungen als Entwickler. Es ist einfach , eine Methode anhand eines Werts zu parametrisieren, und wenn dies das einfachste Tool ist, das Sie in Ihrer Toolbox haben, besteht die Versuchung darin, es zu verwenden, unabhängig davon, ob es für den Benutzer sinnvoll ist.
Eric Lippert
30
Die Wiederverwendung ist nicht etwas, was Sie für sich selbst wollen. Wir priorisieren die Wiederverwendung, da wir der Ansicht sind, dass die Erstellung von Code-Artefakten teuer ist und daher in möglichst vielen Szenarien verwendet werden sollte, um diese Kosten in diesen Szenarien amortisieren zu können. Diese Überzeugung wird häufig nicht durch Beobachtungen gerechtfertigt, und der Ratschlag zur Wiederverwendbarkeit wird daher häufig falsch angewendet . Entwerfen Sie Ihren Code, um die Gesamtkosten der Anwendung zu senken .
Eric Lippert
7
Ihre Frau ist die unmoralische Person, die Ihre Zeit damit verschwendet, Sie anzulügen. Sie bat um eine Antwort und gab ein vorgeschlagenes Medium; Durch diesen Vertrag liegt es nur zwischen Ihnen und Ihnen selbst, wie Sie diese Ausgabe erhalten. Auch destroyCity(target)ist viel unethischer als destroyBagdad()! Was für ein Monster schreibt ein Programm, um eine Stadt auszulöschen, geschweige denn eine Stadt auf der Welt? Was ist, wenn das System kompromittiert wurde ?! Was hat Zeit- / Ressourcenmanagement (Aufwand) auch mit Ethik zu tun? Solange der mündliche / schriftliche Vertrag wie vereinbart abgeschlossen wurde.
Tezra
25
Ich glaube, Sie lesen zu viel in diesen Witz. Es ist ein Witz darüber, wie Computerprogrammierer zu schlechten ethischen Entscheidungen kommen, weil sie technischen Überlegungen Vorrang vor den Auswirkungen ihrer Arbeit auf den Menschen einräumen. Es ist nicht als guter Ratschlag zur Programmgestaltung gedacht.
Eric Lippert

Antworten:

114

Es ist Schildkröten den ganzen Weg nach unten.

Oder Abstraktionen in diesem Fall.

Das Codieren von bewährten Methoden kann unbegrenzt angewendet werden, und irgendwann abstrahieren Sie, um zu abstrahieren, was bedeutet, dass Sie es zu weit gebracht haben. Diese Linie zu finden, ist keine einfache Faustregel, da sie sehr stark von Ihrer Umgebung abhängt.

Zum Beispiel hatten wir Kunden, die bekanntermaßen zuerst nach einfachen Anwendungen, dann nach Erweiterungen fragten. Wir hatten auch Kunden, die fragten, was sie wollten und im Allgemeinen nie zu uns zurückkehrten, um eine Erweiterung zu beantragen.
Ihre Vorgehensweise variiert je nach Kunde. Für den ersten Kunden lohnt es sich, den Code vorab zu abstrahieren, da Sie sich ziemlich sicher sind, dass Sie diesen Code in Zukunft erneut aufrufen müssen. Für den zweiten Kunden möchten Sie diesen zusätzlichen Aufwand möglicherweise nicht investieren, wenn Sie erwarten, dass er die Anwendung zu keinem Zeitpunkt erweitern möchte (Hinweis: Dies bedeutet nicht, dass Sie keine bewährten Methoden befolgen, sondern lediglich dass Sie vermeiden, mehr zu tun, als derzeit erforderlich ist.

Woher weiß ich, welche Funktionen implementiert werden müssen?

Der Grund, warum ich das oben erwähne, ist, dass Sie bereits in diese Falle geraten sind:

Aber woher weiß ich, wie viel (und was) parametriert werden muss? Immerhin könnte sie sagen .

"Sie könnte sagen" ist keine aktuelle Geschäftsanforderung. Es ist eine Vermutung über eine zukünftige Geschäftsanforderung. In der Regel sollten Sie sich nicht auf Vermutungen stützen, sondern nur das entwickeln, was gerade benötigt wird.

Hier gilt jedoch der Kontext. Ich kenne deine Frau nicht. Vielleicht haben Sie genau eingeschätzt, dass sie das tatsächlich wollen wird. Sie sollten dem Kunden dennoch bestätigen, dass dies tatsächlich das ist, was er möchte, da Sie sonst Zeit damit verbringen, eine Funktion zu entwickeln, die Sie niemals nutzen werden.

Woher weiß ich, welche Architektur zu implementieren ist?

Das ist schwieriger. Der Kunde kümmert sich nicht um den internen Code, daher können Sie ihn nicht fragen, ob er ihn benötigt. Ihre Meinung zu diesem Thema ist größtenteils irrelevant.

Sie können die Notwendigkeit dennoch bestätigen, indem Sie dem Kunden die richtigen Fragen stellen . Anstatt nach der Architektur zu fragen, fragen Sie sie nach ihren Erwartungen an zukünftige Entwicklungen oder Erweiterungen der Codebasis. Sie können auch fragen, ob das aktuelle Ziel eine Frist hat, da Sie möglicherweise nicht in der Lage sind, Ihre ausgefallene Architektur innerhalb des erforderlichen Zeitrahmens umzusetzen.

Woher weiß ich, wann ich meinen Code weiter abstrahieren muss?

Ich weiß nicht, wo ich es lese (wenn jemand es weiß, lass es mich wissen und ich werde es anerkennen), aber eine gute Faustregel ist, dass Entwickler wie ein Höhlenmensch zählen sollten: eins, zwei, viele .

Bildbeschreibung hier eingeben XKCD # 764

Mit anderen Worten, wenn ein bestimmte Algorithmus / Muster für ein verwendet wird, um drittes Mal, soll es abstrahiert werden , so dass es wiederverwendbar ist (= verwendbar viele Male).

Um es klar zu sagen, ich will damit nicht sagen, dass Sie keinen wiederverwendbaren Code schreiben sollten, wenn nur zwei Instanzen des Algorithmus verwendet werden. Natürlich können Sie das auch abstrahieren, aber die Regel sollte sein, dass Sie für drei Instanzen abstrahieren müssen .

Auch dies berücksichtigt Ihre Erwartungen. Wenn Sie bereits wissen, dass Sie drei oder mehr Instanzen benötigen, können Sie natürlich sofort abstrahieren. Wenn Sie jedoch nur vermuten, dass Sie es mehrmals implementieren möchten, hängt die Richtigkeit der Implementierung der Abstraktion vollständig von der Richtigkeit Ihrer Vermutung ab.
Wenn Sie richtig geraten haben, haben Sie sich Zeit gespart. Wenn Sie falsch geraten haben, haben Sie einen Teil Ihrer Zeit und Mühe verschwendet und möglicherweise Ihre Architektur kompromittiert, um etwas zu implementieren, das Sie am Ende nicht benötigen.

Wenn destroyCity(City city)ist besser als destroyBaghdad(), ist takeActionOnCity(Action action, City city)noch besser? Warum Warum nicht?

Das hängt sehr von mehreren Dingen ab:

  • Gibt es mehrere Aktionen, die für jede Stadt ausgeführt werden können?
  • Können diese Aktionen synonym verwendet werden? Denn wenn die Aktionen "Zerstören" und "Wiederherstellen" völlig unterschiedliche Ausführungen haben, macht es keinen Sinn, die beiden in einer einzigen takeActionOnCityMethode zusammenzuführen.

Beachten Sie auch, dass Sie, wenn Sie dies rekursiv abstrahieren, am Ende eine Methode haben, die so abstrakt ist, dass es nicht mehr als ein Container ist, in dem eine andere Methode ausgeführt wird. Dies bedeutet, dass Sie Ihre Methode irrelevant und bedeutungslos gemacht haben.
Wenn Ihr gesamter takeActionOnCity(Action action, City city)Methodenkörper nichts anderes ist als action.TakeOn(city);, sollten Sie sich fragen, ob die takeActionOnCityMethode wirklich einen Zweck hat oder nicht nur eine zusätzliche Ebene ist, die nichts von Wert hinzufügt.

Aber Maßnahmen in Städten zu ergreifen, reicht mir nicht aus, wie wäre es takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)?

Die gleiche Frage taucht hier auf:

  • Haben Sie einen Anwendungsfall für geografische Regionen?
  • Ist die Ausführung einer Aktion für eine Stadt und eine Region gleich?
  • Kann für jede Region / Stadt etwas unternommen werden?

Wenn Sie alle drei definitiv mit "Ja" beantworten können, ist eine Abstraktion gerechtfertigt.

Flater
quelle
16
Ich kann die "Eins, Zwei, Viele" -Regel nicht genug betonen. Es gibt unendlich viele Möglichkeiten, etwas zu abstrahieren / zu parametrisieren, aber die nützliche Teilmenge ist klein, häufig Null. Welche Variante genau einen Wert hat, lässt sich meist erst im Nachhinein feststellen. Halten Sie sich also an die unmittelbaren Anforderungen * und erhöhen Sie die Komplexität, wenn dies aufgrund neuer Anforderungen oder im Nachhinein erforderlich ist. * Manchmal kennen Sie den Problemraum gut, dann kann es in Ordnung sein, etwas hinzuzufügen, weil Sie wissen, dass Sie es morgen brauchen. Aber nutzen Sie diese Kraft mit Bedacht, es kann auch zum Ruin führen.
Christian Sauer
2
> "Ich weiß nicht, wo ich es gelesen habe [..]". Sie haben vielleicht Coding Horror: Die Dreierregel gelesen .
Rune
10
Die "Eins, Zwei, Viele" -Regel soll wirklich verhindern, dass Sie die falsche Abstraktion aufbauen, indem Sie DRY blind anwenden. Die Sache ist, dass zwei Teile des Codes fast gleich aussehen können, also ist es verlockend, die Unterschiede wegzuziehen. Aber schon früh wissen Sie nicht, welche Teile des Codes stabil sind und welche nicht. Außerdem könnte sich herausstellen, dass sie sich tatsächlich unabhängig entwickeln müssen (unterschiedliche Veränderungsmuster, unterschiedliche Verantwortlichkeiten). In beiden Fällen wirkt sich eine falsche Abstraktion gegen Sie aus und steht Ihnen im Weg.
Filip Milovanović
4
Wenn Sie auf mehr als zwei Beispiele für "dieselbe Logik" warten, können Sie besser beurteilen, was und wie abstrahiert werden soll (und es geht wirklich darum, Abhängigkeiten / Kopplungen zwischen Code mit unterschiedlichen Änderungsmustern zu verwalten).
Filip Milovanović
1
@kukis: Die realistische Linie sollte bei 2 (gemäß Baldrickks Kommentar) gezogen werden: null-eins-viele (wie dies bei Datenbankbeziehungen der Fall ist). Dies öffnet jedoch die Tür zu unnötigem Mustersuchverhalten. Zwei Dinge mögen sich vage ähneln, aber das bedeutet nicht, dass sie tatsächlich gleich sind. Wenn jedoch eine dritte Instanz in den Kampf eintritt, die sowohl der ersten als auch der zweiten Instanz ähnelt, können Sie genauer beurteilen, ob ihre Ähnlichkeiten tatsächlich ein wiederverwendbares Muster sind. Die Common-Sense-Linie wird also bei 3 gezogen, was menschliches Versagen berücksichtigt, wenn "Muster" nur zwischen zwei Instanzen erkannt werden.
Flater
44

Trainieren

Das ist Software Engineering SE, aber das Erstellen von Software ist viel mehr Kunst als Technik. Es muss weder ein universeller Algorithmus befolgt noch eine Messung durchgeführt werden, um herauszufinden, wie viel Wiederverwendbarkeit ausreicht. Je mehr Übung Sie mit dem Entwerfen von Programmen haben, desto besser werden Sie damit umgehen können. Sie bekommen ein besseres Gefühl für das, was "genug" ist, weil Sie sehen, was schief geht und wie es schief geht, wenn Sie zu viel oder zu wenig parametrieren.

Das ist jetzt allerdings nicht sehr hilfreich. Wie wäre es mit einigen Richtlinien?

Schau zurück auf deine Frage. Es gibt eine Menge von "Sie könnte sagen" und "Ich könnte". Viele Aussagen, die sich auf zukünftige Bedürfnisse beziehen. Die Menschen sind scheiße, wenn es darum geht, die Zukunft vorherzusagen. Und Sie sind (höchstwahrscheinlich) ein Mensch. Das überwältigende Problem des Software-Designs besteht darin, eine Zukunft zu planen, die Sie nicht kennen.

Leitlinie 1: Du wirst es nicht brauchen

Ernsthaft. Hör einfach auf. In den meisten Fällen wird dieses imaginäre zukünftige Problem nicht angezeigt - und es wird mit Sicherheit nicht so angezeigt, wie Sie es sich vorgestellt haben.

Leitlinie 2: Kosten / Nutzen

Cool, das kleine Programm hat ein paar Stunden gedauert, um es vielleicht zu schreiben? Was also , wenn Ihre Frau nicht zurückkommen und für die Dinge fragen? Im schlimmsten Fall verbringen Sie noch ein paar Stunden damit, ein anderes Programm zusammenzuwerfen, um dies zu tun. In diesem Fall ist es nicht zu viel Zeit, um dieses Programm flexibler zu gestalten. Und es wird nicht viel zur Laufzeitgeschwindigkeit oder zur Speichernutzung beitragen. Aber nicht triviale Programme haben unterschiedliche Antworten. Unterschiedliche Szenarien haben unterschiedliche Antworten. Irgendwann sind die Kosten den Nutzen eindeutig nicht mehr wert, auch wenn die zukünftigen Fähigkeiten nicht perfekt sind.

Leitlinie 3: Konstanten im Fokus

Schauen Sie sich die Frage noch einmal an. In Ihrem ursprünglichen Code gibt es viele konstante Ints. 2018, 1. Konstante Ints, konstante Strings ... Es ist am wahrscheinlichsten, dass Dinge nicht konstant sein müssen . Besser noch, die Parametrisierung (oder zumindest die Definition als tatsächliche Konstante) nimmt nur wenig Zeit in Anspruch. Aber eine andere Sache, vor der man vorsichtig sein sollte, ist beständiges Verhalten . DasSystem.out.printlnzum Beispiel. Diese Art von Vermutung über die Verwendung ist in der Regel etwas, das sich in der Zukunft ändert, und ihre Behebung ist in der Regel sehr kostspielig. Nicht nur das, sondern IO wie dieses macht die Funktion unrein (zusammen mit der Zeitzone, die etwas abruft). Die Parametrisierung dieses Verhaltens kann die Funktion reiner machen und zu mehr Flexibilität und Testbarkeit führen. Große Vorteile bei minimalen Kosten (insbesondere, wenn Sie eine Überlastung vornehmen, die System.outstandardmäßig verwendet wird).

Telastyn
quelle
1
Es ist nur eine Richtlinie, die Einsen sind in Ordnung, aber du siehst sie dir an und sagst: "Wird sich das jemals ändern?" Und der Ausdruck könnte mit einer Funktion höherer Ordnung parametrisiert werden - obwohl Java bei diesen nicht großartig ist.
Telastyn
5
@KorayTugay: Wenn das Programm wirklich für Ihre Frau gedacht wäre, die nach Hause kommt, würde Ihnen YAGNI sagen, dass Ihre ursprüngliche Version perfekt ist und Sie keine Zeit mehr investieren sollten, um Konstanten oder Parameter einzuführen. YAGNI braucht Kontext - ist Ihr Programm eine Einweglösung oder ein Migrationsprogramm, das nur wenige Monate ausgeführt wird, oder ist es Teil eines riesigen ERP-Systems, das über mehrere Jahrzehnte hinweg verwendet und gewartet werden soll?
Doc Brown
8
@KorayTugay: Die Trennung von E / A und Berechnung ist eine grundlegende Programmstrukturierungstechnik. Separate Generierung von Daten vom Filtern von Daten von der Transformation von Daten vom Verbrauch von Daten von der Präsentation von Daten. Sie sollten einige Funktionsprogramme studieren, dann werden Sie dies deutlicher sehen. In der funktionalen Programmierung ist es üblich, unendlich viele Daten zu generieren, nur die Daten herauszufiltern, die Sie interessieren, die Daten in das gewünschte Format zu transformieren, daraus eine Zeichenfolge zu erstellen und diese Zeichenfolge in 5 verschiedenen Funktionen zu drucken. eine für jeden Schritt.
Jörg W Mittag
3
Als Randbemerkung führt die strikte Befolgung von YAGNI dazu, dass eine kontinuierliche Überarbeitung erforderlich ist: "Ohne kontinuierliche Überarbeitung kann es zu ungeordnetem Code und massiven Überarbeitungen kommen, die als technische Schulden bezeichnet werden." Obwohl YAGNI im Allgemeinen eine gute Sache ist, ist es mit einer großen Verantwortung verbunden, Code zu überarbeiten und neu zu bewerten, was nicht jeder Entwickler / jedes Unternehmen gewillt ist.
Flater
4
@Telastyn: Ich schlage vor, die Frage auf "Wird sich dies nie ändern und ist die Absicht des Codes trivial lesbar, ohne die Konstante zu benennen ?" Zu erweitern.
Flater
27

Erstens: Kein sicherheitsbewusster Softwareentwickler würde eine DestroyCity-Methode schreiben, ohne ein Autorisierungstoken aus irgendeinem Grund zu übergeben.

Auch ich kann alles als einen Imperativ schreiben, der offensichtliche Weisheit besitzt, ohne dass er in einem anderen Kontext anwendbar ist. Warum muss eine Zeichenfolgenverkettung autorisiert werden?

Zweitens: Der gesamte ausgeführte Code muss vollständig angegeben werden .

Es spielt keine Rolle, ob die Entscheidung fest programmiert oder auf eine andere Ebene verschoben wurde. Irgendwann gibt es einen Code in einer Sprache, der sowohl weiß, was zerstört werden soll, als auch wie man ihn anweist.

Dies kann sich in derselben Objektdatei destroyCity(xyz)und in einer Konfigurationsdatei befinden: destroy {"city": "XYZ"}"oder es kann sich um eine Reihe von Klicks und Tastendrücken in einer Benutzeroberfläche handeln.

Drittens:

Schatz. Kannst du alle Tageslichteinsparungen der Welt für 2018 in der Konsole drucken? Ich muss etwas überprüfen.

ist eine ganz andere Reihe von Anforderungen an:

sie möchte einen benutzerdefinierten String-Formatierer übergeben, ... der nur an bestimmten Monatsperioden interessiert ist, ... und an bestimmten Stundenperioden interessiert ist ...

Die zweite Reihe von Anforderungen erfordert offensichtlich ein flexibleres Werkzeug. Es hat eine breitere Zielgruppe und einen breiteren Anwendungsbereich. Hier besteht die Gefahr, dass die flexibelste Anwendung der Welt tatsächlich ein Compiler für Maschinencode ist. Es ist buchstäblich ein Programm, das so allgemein ist, dass es alles erstellen kann, um den Computer so zu machen, wie Sie es benötigen (innerhalb der Einschränkungen seiner Hardware).

Generell wollen Leute, die Software benötigen, nichts generisches. Sie wollen etwas Bestimmtes. Indem Sie mehr Optionen angeben, machen Sie ihr Leben tatsächlich komplizierter. Wenn sie diese Komplexität wollten, würden sie stattdessen einen Compiler verwenden und Sie nicht fragen.

Ihre Frau hat nach Funktionalität gefragt und Ihre Anforderungen an Sie unterschätzt. In diesem Fall war es scheinbar absichtlich, und im Allgemeinen, weil sie es nicht besser wissen. Andernfalls hätten sie den Compiler nur selbst verwendet. Das erste Problem ist also, dass Sie nicht genau nach Details gefragt haben, was sie tun wollte. Wollte sie das mehrere Jahre lang machen? Wollte sie es in einer CSV-Datei? Du hast nicht herausgefunden, welche Entscheidungen sie selbst treffen wollte und was sie dich bat, herauszufinden und für sie zu entscheiden. Sobald Sie herausgefunden haben, welche Entscheidungen zurückgestellt werden müssen, können Sie herausfinden, wie diese Entscheidungen über Parameter (und andere konfigurierbare Mittel) kommuniziert werden.

Davon abgesehen kommunizieren die meisten Kunden falsch, nehmen an oder kennen bestimmte Details (auch bekannt als Entscheidungen) nicht, die sie wirklich selbst treffen möchten oder die sie wirklich nicht treffen möchten (aber das klingt großartig). Deshalb sind Arbeitsmethoden wie PDSA (Plan-Develop-Study-Act) wichtig. Sie haben die Arbeit gemäß den Anforderungen geplant und dann eine Reihe von Entscheidungen (Code) entwickelt. Jetzt ist es an der Zeit, es entweder selbst oder mit Ihrem Kunden zu studieren und neue Dinge zu lernen, die Ihr zukünftiges Denken beeinflussen. Machen Sie sich endlich Ihre neuen Erkenntnisse zunutze - aktualisieren Sie die Anforderungen, verfeinern Sie den Prozess, holen Sie sich neue Tools usw. ... Beginnen Sie dann erneut mit der Planung. Dies hätte im Laufe der Zeit verborgene Anforderungen aufgedeckt und viele Kunden davon überzeugt, dass sie Fortschritte erzielt haben.

Endlich. Ihre Zeit ist wichtig ; es ist sehr real und sehr endlich. Jede Entscheidung, die Sie treffen, bringt viele andere versteckte Entscheidungen mit sich, und darum geht es bei der Entwicklung von Software. Wenn Sie eine Entscheidung als Argument verzögern, wird die aktuelle Funktion möglicherweise einfacher, an anderen Stellen jedoch komplexer. Ist diese Entscheidung an diesem anderen Ort relevant? Ist es hier relevanter? Wessen Entscheidung ist es wirklich zu treffen? Du entscheidest das; Das ist Codierung. Wenn Sie Entscheidungsmengen häufig wiederholen, ist es von großem Vorteil, sie innerhalb einer Abstraktion zu kodieren. XKCD hat hier eine nützliche Perspektive. Und dies ist auf der Ebene eines Systems relevant, sei es eine Funktion, ein Modul, ein Programm usw.

Der Ratschlag zu Beginn impliziert, dass Entscheidungen, zu denen Ihre Funktion kein Recht hat, als Argument herangezogen werden sollten. Das Problem ist, dass eine DestroyBaghdadFunktion tatsächlich die Funktion sein kann, die über dieses Recht verfügt.

Kain0_0
quelle
+1 Liebe den Teil über den Compiler!
Lee
4

Hier gibt es viele langwierige Antworten, aber ehrlich gesagt finde ich es super einfach

Alle fest codierten Informationen in Ihrer Funktion, die nicht Teil des Funktionsnamens sind, sollten ein Parameter sein.

also in deiner funktion

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

Du hast:

The zoneIds
2018, 1, 1
System.out

Also würde ich all diese Parameter in der einen oder anderen Form verschieben. Sie könnten argumentieren, dass die zoneIds im Funktionsnamen impliziert sind. Vielleicht möchten Sie dies noch verstärken, indem Sie ihn in "DaylightSavingsAroundTheWorld" oder so ändern

Sie haben keine Formatzeichenfolge, daher ist das Hinzufügen einer Zeichenfolge eine Funktionsanforderung, und Sie sollten Ihre Frau an die Jira-Instanz Ihrer Familie verweisen. Es kann auf dem Backlog eingestellt und bei der entsprechenden Sitzung des Projektmanagementausschusses priorisiert werden.

Ewan
quelle
1
Sie (wenden sich an OP) sollten auf keinen Fall einen Formatstring hinzufügen, da Sie nichts drucken sollten. Das einzige an diesem Code, das seine Wiederverwendung absolut verhindert, ist, dass er gedruckt wird. Es sollte die Zonen oder eine Karte der Zonen zurückgeben, wenn sie die Sommerzeit verlassen. (Obwohl , warum es identifiziert nur , wenn die gehen aus DST, und nicht auf DST, ich verstehe nicht , es scheint nicht das Problem Rechnung..)
David Conrad
Die Anforderung besteht darin, auf der Konsole zu drucken. Sie können die enge Kopplung unterbinden, indem Sie den Ausgabestream als Parameter übergeben, wie ich vorschlage
Ewan
1
Wenn Sie jedoch möchten, dass der Code wiederverwendbar ist, sollten Sie nicht über die Konsole drucken. Schreiben Sie eine Methode, die die Ergebnisse zurückgibt, und schreiben Sie dann einen Aufrufer, der sie abruft und druckt. Das macht es auch testbar. Wenn Sie möchten, dass es eine Ausgabe erzeugt, würde ich keinen Ausgabestream übergeben, sondern einen Consumer.
David Conrad
Ein Ourput Stream ist ein Verbraucher
Ewan
Nein, ein OutputStream ist kein Consumer .
David Conrad
4

Kurz gesagt: Entwickeln Sie Ihre Software nicht für Wiederverwendbarkeit, da es keinen Endbenutzer interessiert, ob Ihre Funktionen wiederverwendet werden können. Stattdessen Ingenieur für Designverständlichkeit - ist mein Code leicht für andere oder mein zukünftiges vergessliches Selbst zu verstehen? - und Designflexibilität- Wenn ich unvermeidlich Fehler beheben, Features hinzufügen oder auf andere Weise die Funktionalität ändern muss, wie sehr wird mein Code den Änderungen widerstehen? Ihr Kunde kümmert sich nur darum, wie schnell Sie reagieren können, wenn er einen Fehler meldet oder nach einer Änderung fragt. Wenn Sie diese Fragen zu Ihrem Design stellen, ist der Code in der Regel wiederverwendbar. Bei diesem Ansatz konzentrieren Sie sich jedoch darauf, die tatsächlichen Probleme zu vermeiden, denen Sie im Laufe der Lebensdauer des Codes gegenüberstehen, damit Sie dem Endbenutzer einen besseren Service bieten können, als einen hohen, unpraktischen Wert zu verfolgen "engineering" -Ideen, um den Nackenbärten zu gefallen.

Für etwas so Einfaches wie das von Ihnen bereitgestellte Beispiel ist Ihre anfängliche Implementierung in Ordnung, da es so klein ist, aber dieses einfache Design wird schwer zu verstehen und spröde, wenn Sie versuchen, zu viel funktionale Flexibilität (im Gegensatz zu Designflexibilität) einzubeziehen ein Verfahren. Nachstehend finden Sie eine Erläuterung meines bevorzugten Ansatzes für die Gestaltung komplexer Systeme im Hinblick auf Verständlichkeit und Flexibilität. Ich hoffe, dass dies zeigen wird, was ich damit meine. Ich würde diese Strategie nicht für etwas anwenden, das in weniger als 20 Zeilen in einem einzigen Verfahren geschrieben werden könnte, da etwas so Kleines bereits meine Kriterien für Verständlichkeit und Flexibilität erfüllt.


Objekte, keine Prozeduren

Anstatt Klassen wie Old-School-Module mit einer Reihe von Routinen zu verwenden, die Sie aufrufen, um die Aufgaben Ihrer Software auszuführen, sollten Sie die Domäne als Objekte modellieren, die interagieren und zusammenarbeiten, um die jeweilige Aufgabe zu erfüllen. Methoden in einem objektorientierten Paradigma wurden ursprünglich erstellt, um Signale zwischen Objekten zu sein, Object1damit sie sagen können Object2, was auch immer das ist, und möglicherweise ein Rücksignal erhalten. Dies liegt daran, dass es beim objektorientierten Paradigma inhärent darum geht, Ihre Domänenobjekte und ihre Interaktionen zu modellieren, anstatt die alten Funktionen und Prozeduren des imperativen Paradigmas auf originelle Weise zu organisieren. Im Falle dervoid destroyBaghdadAnstatt beispielsweise zu versuchen, eine kontextlose generische Methode zu schreiben, um mit der Zerstörung von Bagdad oder anderen Dingen umzugehen (die schnell komplex, schwer zu verstehen und spröde werden können), sollte alles, was zerstört werden kann, dafür verantwortlich sein, zu verstehen, wie sich selbst zerstören. Sie haben beispielsweise eine Schnittstelle, die das Verhalten von Dingen beschreibt, die zerstört werden können:

interface Destroyable {
    void destroy();
}

Dann haben Sie eine Stadt, die diese Schnittstelle implementiert:

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

Nichts, das die Zerstörung einer Instanz von Cityerfordert, wird sich jemals darum kümmern, wie dies geschieht. Es gibt also keinen Grund, warum dieser Code irgendwo außerhalb von sich existiert City::destroy, und in der Tat wäre eine genaue Kenntnis der inneren Funktionsweise Cityvon sich selbst eine enge Kopplung, die sich verringert Flexibilität, da Sie diese externen Elemente berücksichtigen müssen, falls Sie jemals das Verhalten von ändern müssen City. Dies ist der wahre Zweck der Kapselung. Stellen Sie sich das so vor, als hätte jedes Objekt eine eigene API, mit der Sie alles tun können, was Sie brauchen, damit es sich um die Erfüllung Ihrer Anforderungen kümmern kann.

Delegation, nicht "Kontrolle"

Nun, ob Ihre implementierende Klasse ist Cityoder Baghdaddavon abhängt, wie allgemein sich der Prozess der Zerstörung der Stadt herausstellt. Es ist sehr wahrscheinlich, dass ein CityWille aus kleineren Stücken besteht, die einzeln zerstört werden müssen, um die vollständige Zerstörung der Stadt zu erreichen. In diesem Fall würde also jeder dieser Stücke auch ausgeführt Destroyableund jeder von ihnen angewiesen, Cityzu zerstören sich in der gleichen Weise jemand von außen forderte die City, sich selbst zu zerstören.

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

Wenn Sie wirklich verrückt werden und die Idee eines Objekts implementieren möchten, das Bomban einem Ort abgelegt wird und alles in einem bestimmten Radius zerstört, sieht es möglicherweise so aus:

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

ObjectsByRadiusStellt einen Satz von Objekten dar, der für die Bombaus den Eingaben Bombberechnet wird, da es egal ist, wie diese Berechnung durchgeführt wird, solange sie mit den Objekten arbeiten kann. Dies ist übrigens wiederverwendbar, aber das Hauptziel besteht darin, die Berechnung von den Prozessen des Ablegens Bombund Zerstörens der Objekte zu isolieren, damit Sie jedes Stück nachvollziehen können und wie sie zusammenpassen und das Verhalten eines einzelnen Stücks ändern können, ohne den gesamten Algorithmus umformen zu müssen .

Interaktionen, keine Algorithmen

Anstatt zu versuchen, die richtige Anzahl von Parametern für einen komplexen Algorithmus zu erraten, ist es sinnvoller, den Prozess als einen Satz interagierender Objekte mit jeweils extrem engen Rollen zu modellieren, da Sie die Komplexität Ihres Prozesses modellieren können durchlaufen die Wechselwirkungen zwischen diesen gut definierten, leicht verständlichen und nahezu unveränderlichen Objekten. Bei richtiger Ausführung sind selbst einige der komplexesten Änderungen so einfach wie die Implementierung einer oder zweier Schnittstellen und die Überarbeitung der in Ihrer main()Methode instanziierten Objekte .

Ich würde Ihnen etwas zu Ihrem ursprünglichen Beispiel geben, aber ich kann ehrlich gesagt nicht herausfinden, was es bedeutet, "... Day Light Savings" zu drucken. Was ich zu dieser Kategorie von Problemen sagen kann, ist, dass jedes Mal, wenn Sie eine Berechnung durchführen, deren Ergebnis auf verschiedene Arten formatiert werden kann, meine bevorzugte Methode, dies aufzuschlüsseln, wie folgt lautet:

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

Da Ihr Beispiel Klassen aus der Java-Bibliothek verwendet, die dieses Design nicht unterstützen, können Sie einfach die API von ZonedDateTimedirekt verwenden. Die Idee dabei ist, dass jede Berechnung in einem eigenen Objekt gekapselt ist. Es werden keine Annahmen darüber getroffen, wie oft es ausgeführt werden soll oder wie das Ergebnis formatiert werden soll. Es geht ausschließlich um die einfachste Form der Berechnung. Dies macht es einfach zu verstehen und flexibel zu ändern. Ebenso befasst sich der Resultausschließlich mit der Kapselung des Berechnungsergebnisses und der FormattedResultausschließlich mit der Interaktion mit dem, Resultum es gemäß den von uns definierten Regeln zu formatieren. Auf diese Weise,Wir können die perfekte Anzahl von Argumenten für jede unserer Methoden finden, da sie jeweils eine genau definierte Aufgabe haben . Es ist auch viel einfacher, Änderungen vorzunehmen, solange sich die Benutzeroberflächen nicht ändern (was weniger wahrscheinlich ist, wenn Sie die Verantwortlichkeiten Ihrer Objekte richtig minimiert haben). Unseremain()Methode könnte so aussehen:

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

Tatsächlich wurde die objektorientierte Programmierung speziell als Lösung für das Komplexitäts- / Flexibilitätsproblem des Imperativ-Paradigmas erfunden, da es keine gute Antwort gibt (auf die sich ohnehin jeder einigen oder unabhängig davon kommen kann), wie man optimal vorgeht Imperative Funktionen und Prozeduren innerhalb der Redewendung angeben.

Stuporman
quelle
Dies ist eine sehr detaillierte und durchdachte Antwort, aber ich denke leider, dass sie die Marke verfehlt, nach der das OP wirklich gefragt hat. Er bat nicht um eine Lektion über gute OOP-Praktiken, um sein spezielles Beispiel zu lösen, sondern fragte nach den Kriterien, nach denen wir entscheiden, wie viel Zeit in eine Lösung oder eine Verallgemeinerung investiert werden soll.
maple_shaft
@maple_shaft Vielleicht habe ich die Marke verpasst, aber ich denke du hast es auch. Das OP fragt nicht nach der Investition von Zeit oder Verallgemeinerung. Er fragt: "Woher weiß ich, wie wiederverwendbar meine Methoden sein sollten?" Im weiteren Verlauf seiner Frage fragt er: "Wenn destroyCity (Stadtstadt) besser ist als destroyBaghdad (), ist takeActionOnCity (Aktion Aktion, Stadtstadt) noch besser? Warum / warum nicht?" Ich habe mich für eine alternative Herangehensweise an technische Lösungen ausgesprochen, die meiner Meinung nach das Problem löst, herauszufinden, wie generisch Methoden zu erstellen sind, und Beispiele zur Untermauerung meiner Behauptung geliefert. Es tut mir leid, dass es dir nicht gefallen hat.
Stuporman
@maple_shaft Ehrlich gesagt kann nur das OP entscheiden, ob meine Antwort für seine Frage relevant ist, da der Rest von uns Kriege führen könnte, in denen unsere Interpretationen seiner Absichten verteidigt werden, die alle gleichermaßen falsch sein könnten.
Stuporman
@maple_shaft Ich habe ein Intro hinzugefügt, um zu klären, wie es mit der Frage zusammenhängt, und eine klare Abgrenzung zwischen der Antwort und der Beispielimplementierung gegeben. Ist das besser?
Stuporman
1
Ehrlich gesagt, wenn Sie all diese Prinzipien anwenden, wird die Antwort natürlich, glatt und massenhaft lesbar sein. Plus, ohne viel Aufhebens veränderbar. Ich weiß nicht, wer du bist, aber ich wünschte, es gäbe mehr von dir! Ich rippe immer wieder reaktiven Code für anständige OO raus, und er ist IMMER halb so groß, lesbarer, kontrollierbarer und hat immer noch Threading / Splitting / Mapping. Ich denke, React ist für Leute gedacht, die die "grundlegenden" Konzepte, die Sie gerade aufgelistet haben, nicht verstehen.
Stephen J
3

Erfahrung , Domänenwissen und Codeüberprüfungen.

Unabhängig davon, wie viel oder wie wenig Erfahrung , Fachwissen oder Team Sie haben, können Sie es nicht vermeiden, bei Bedarf umzugestalten.


Mit Experience beginnen Sie, Muster in den von Ihnen geschriebenen domänenunspezifischen Methoden (und Klassen) zu erkennen. Und wenn Sie überhaupt an DRY-Code interessiert sind, werden Sie schlechte Gefühle verspüren , wenn Sie eine Methode schreiben, von der Sie instinktiv wissen, dass Sie Variationen von in Zukunft schreiben werden. So schreiben Sie stattdessen intuitiv einen parametrisierten kleinsten gemeinsamen Nenner.

(Diese Erfahrung kann auch instinktiv auf einige Ihrer Domänenobjekte und -methoden übertragen werden.)

Mit Domain Knowledge haben Sie ein Gespür dafür, welche Geschäftskonzepte eng miteinander verbunden sind, welche Konzepte Variablen aufweisen, welche ziemlich statisch sind usw.

Bei Code Reviews wird Unter- und Überparametrisierung mit größerer Wahrscheinlichkeit abgefangen, bevor sie zu Produktionscode wird, da Ihre Kollegen (hoffentlich) einzigartige Erfahrungen und Perspektiven haben, sowohl in Bezug auf die Domäne als auch in Bezug auf die Codierung im Allgemeinen.


Allerdings werden neue Entwickler diese Spidey Senses oder eine erfahrene Gruppe von Kollegen nicht auf Anhieb zur Verfügung haben. Und selbst erfahrene Entwickler profitieren von einer grundlegenden Disziplin, die sie durch neue Anforderungen führt - oder durch hirnneblige Tage. Also, hier ist, was ich als Anfang vorschlagen würde :

  • Beginnen Sie mit der naiven Implementierung mit minimaler Parametrisierung.
    (Fügen Sie alle Parameter , die Sie bereits wissen , die Sie benötigen, natürlich ...)
  • Entfernen Sie magische Zahlen und Zeichenfolgen und verschieben Sie sie in Konfigurationen und / oder Parameter
  • Zählen Sie "große" Methoden in kleinere, gut benannte Methoden
  • Fassen Sie hochredundante Methoden (falls zweckmäßig) in einen gemeinsamen Nenner um und parametrisieren Sie die Unterschiede.

Diese Schritte müssen nicht unbedingt in der angegebenen Reihenfolge ausgeführt werden. Wenn Sie sich hinsetzen, um eine Methode zu schreiben, von der Sie bereits wissen, dass sie mit einer vorhandenen Methode in hohem Maße redundant ist , können Sie direkt in das Refactoring einsteigen, wenn dies sinnvoll ist. (Wenn die Umgestaltung nicht wesentlich länger dauert als das Schreiben, Testen und Verwalten von zwei Methoden.)

Aber abgesehen davon, dass ich nur viel Erfahrung usw. habe, empfehle ich ein ziemlich minimalistisches Code-DRY-ing. Es ist nicht schwer, offensichtliche Verstöße umzugestalten. Und wenn Sie zu eifrig sind , können Sie am Ende übermäßig trockenen Code erhalten, der noch schwerer zu lesen, zu verstehen und zu warten ist als das Äquivalent "WET".

Svidgen
quelle
2
Also gibt es keine richtige Antwort auf If destoryCity(City city) is better than destoryBaghdad(), is takeActionOnCity(Action action, City city) even better?? Es ist eine Ja / Nein-Frage, aber es hat keine Antwort, oder? Oder ist die anfängliche Annahme falsch, destroyCity(City)könnte nicht unbedingt besser sein und es kommt tatsächlich darauf an ? Das heißt also nicht, dass ich kein nicht ethisch ausgebildeter Softwareingenieur bin, weil ich direkt ohne Parameter den ersten Platz einnehme? Ich meine, wie lautet die Antwort auf die konkrete Frage, die ich stelle?
Koray Tugay
Ihre Frage stellt ein paar Fragen. Die Antwort auf die Titelfrage lautet: "Erfahrung, Domänenkenntnisse, Codeüberprüfungen ... und ... haben Sie keine Angst vor einer Umgestaltung." Die Antwort auf eine konkrete Frage "Sind dies die richtigen Parameter für die Methode ABC?" Lautet ... "Ich weiß nicht. Warum fragen Sie? Stimmt etwas mit der Anzahl der Parameter nicht, die sie derzeit hat? Wenn ja, artikulieren Sie es. Reparieren Sie es. " ... Ich verweise Sie möglicherweise auf "the POAP", um weitere Hinweise zu erhalten: Sie müssen verstehen, warum Sie das tun, was Sie tun!
Svidgen
Ich meine ... lasst uns sogar einen Schritt von der destroyBaghdad()Methode zurücktreten . Was ist der Kontext? Handelt es sich um ein Videospiel, bei dem Bagdad am Ende des Spiels zerstört wird? Wenn ja ... destroyBaghdad()könnte eine völlig vernünftige Methode Name / Unterschrift sein ...
Svidgen
1
Sie stimmen also dem in meiner Frage zitierten Zitat nicht zu, oder? It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure.Wenn Sie mit Nathaniel Borenstein im Raum wären, würden Sie ihm argumentieren, dass es tatsächlich darauf ankommt und seine Aussage nicht korrekt ist? Ich meine, es ist schön, dass viele Menschen antworten und ihre Zeit und Energie aufwenden, aber ich sehe nirgendwo eine einzige konkrete Antwort. Spidey-Senses, Code-Reviews. Aber worauf ist die Antwort is takeActionOnCity(Action action, City city) better? null?
Koray Tugay
1
@svidgen Eine andere Möglichkeit, dies ohne zusätzlichen Aufwand zu abstrahieren, besteht darin, die Abhängigkeit umzukehren. Die Funktion gibt eine Liste der Städte zurück, anstatt eine Aktion für diese auszuführen (z. B. "println" im Originalcode). Dies kann bei Bedarf weiter abstrahiert werden, aber nur diese eine Änderung alleine erledigt ungefähr die Hälfte der zusätzlichen Anforderungen in der ursprünglichen Frage - und statt einer unreinen Funktion, die alle Arten von schlechten Dingen ausführen kann, haben Sie nur eine Funktion Das gibt eine Liste zurück und der Anrufer erledigt die schlechten Sachen.
Luaan
2

Die gleiche Antwort wie bei Qualität, Benutzerfreundlichkeit, technischer Verschuldung usw.:

Als wiederverwendbar , wie Sie, der Benutzer, 1 müssen sie sein

Es ist im Grunde genommen ein Urteilsspruch - ob die Kosten für das Entwerfen und Verwalten der Abstraktion durch die Kosten (= Zeit und Mühe), die Sie auf der ganzen Linie sparen, erstattet werden.

  • Beachten Sie die "Down the Line" -Phrase: Es gibt hier einen Auszahlungsmechanismus, der davon abhängt, wie viel Sie mit diesem Code weiter arbeiten werden. Z.B:
    • Handelt es sich um ein einmaliges Projekt oder wird es über einen langen Zeitraum hinweg schrittweise verbessert?
    • Sind Sie von Ihrem Design überzeugt oder müssen Sie es wahrscheinlich für das nächste Projekt / den nächsten Meilenstein ausrangieren oder auf andere Weise drastisch ändern (z. B. versuchen Sie es mit einem anderen Framework)?
  • Der projizierte Nutzen hängt auch von Ihrer Fähigkeit ab, die Zukunft vorherzusagen (Änderungen an der App). Manchmal können Sie vernünftigerweise sehen, welchen Veranstaltungsort (welche Veranstaltungsorte) Ihre App einnehmen wird. Mehrmals denkst du, du kannst, aber du kannst es tatsächlich nicht. Die Faustregeln sind hier das YAGNI Prinzip und die Herrschaft von drei - beide betonen Abarbeiten von dem, was Sie wissen, jetzt.

1 Dies ist ein Codekonstrukt. In diesem Fall sind Sie der "Benutzer" - der Benutzer des Quellcodes

ivan_pozdeev
quelle
1

Es gibt einen klaren Prozess, dem Sie folgen können:

  • Schreiben Sie einen fehlgeschlagenen Test für ein einzelnes Feature, das an sich ein "Ding" ist (dh keine willkürliche Aufteilung eines Features, bei der keine der beiden Hälften wirklich Sinn ergibt).
  • Schreiben Sie den absoluten Mindestcode, damit er grün durchgeht, keine Zeile mehr.
  • Spülen und wiederholen.
  • (Bei Bedarf unermüdlich refaktorieren, was aufgrund der großen Testabdeckung einfach sein sollte.)

Dies ergibt - zumindest nach Meinung einiger Leute - einen ziemlich optimalen Code, da jedes fertige Feature so klein wie möglich ist und so wenig Zeit wie möglich in Anspruch nimmt (was möglicherweise zutrifft oder nicht, wenn Sie sich das fertige ansehen) Produkt nach dem Refactoring), und es hat eine sehr gute Testabdeckung. Außerdem werden überentwickelte, zu generische Methoden oder Klassen deutlich vermieden.

Dies gibt Ihnen auch sehr klare Anweisungen, wann Sie Dinge generisch machen und wann Sie sich spezialisieren müssen.

Ich finde dein Stadtbeispiel komisch; Ich würde sehr wahrscheinlich niemals einen Städtenamen fest codieren. Es ist so offensichtlich, dass später weitere Städte hinzukommen, was auch immer Sie tun. Ein anderes Beispiel wären Farben. Unter bestimmten Umständen wäre eine Hardcodierung von "Rot" oder "Grün" möglich. Zum Beispiel sind Ampeln eine allgegenwärtige Farbe, mit der Sie einfach davonkommen können (und die Sie jederzeit umgestalten können). Der Unterschied besteht darin, dass "rot" und "grün" in unserer Welt eine universelle, "fest codierte" Bedeutung haben. Es ist unglaublich unwahrscheinlich, dass sich dies jemals ändern wird, und es gibt auch keine wirkliche Alternative.

Ihre erste Sommerzeitmethode ist einfach kaputt. Während es den Spezifikationen entspricht, ist das fest codierte 2018 besonders schlecht, weil a) es nicht im technischen "Vertrag" (in diesem Fall im Methodennamen) erwähnt wird und b) es bald veraltet sein wird, so dass Bruch ist von Anfang an dabei. Für zeit- / datumsbezogene Dinge wäre es sehr selten sinnvoll, einen bestimmten Wert fest zu codieren, da die Zeit weitergeht. Ansonsten steht aber alles zur Diskussion. Wenn Sie ein einfaches Jahr angeben und dann immer das ganze Jahr berechnen, fahren Sie fort. Die meisten der von Ihnen aufgelisteten Dinge (Formatierung, Auswahl eines kleineren Bereichs usw.) schreien, dass Ihre Methode zu viel bewirkt. Stattdessen sollte sie wahrscheinlich eine Liste / ein Array von Werten zurückgeben, damit der Aufrufer die Formatierung / Filterung selbst vornehmen kann.

Aber am Ende des Tages geht es hauptsächlich um Meinung, Geschmack, Erfahrung und persönliche Vorurteile. Ärgern Sie sich also nicht zu sehr darüber.

AnoE
quelle
Betrachten Sie in Bezug auf Ihren vorletzten Absatz die "Anforderungen", auf denen die erste Methode beruhte. Es gibt 2018 an, daher ist der Code technisch korrekt (und würde wahrscheinlich Ihrem funktionsorientierten Ansatz entsprechen).
Dwizum
@dwizum, es ist in Bezug auf die Anforderungen korrekt, aber da ist der Methodenname irreführend. Im Jahr 2019 würde jeder Programmierer, der sich nur den Methodennamen ansieht, davon ausgehen, dass er alles tut (möglicherweise werden die Werte für das aktuelle Jahr zurückgegeben), nicht 2018 ... Ich werde der Antwort einen Satz hinzufügen, um klarer zu machen, was ich meinte.
AnoE
1

Ich bin zu dem Schluss gekommen, dass es zwei Arten von wiederverwendbarem Code gibt:

  • Code, der wiederverwendbar ist, weil er so grundlegend und grundlegend ist.
  • Code, der wiederverwendbar ist, weil er Parameter, Overrides und Hooks für überall hat.

Die erste Art der Wiederverwendbarkeit ist oft eine gute Idee. Dies gilt für Dinge wie Listen, Hashmaps, Schlüssel- / Wertspeicher, String-Matcher (z. B. Regex, Glob, ...), Tupel, Vereinheitlichung, Suchbäume (Tiefe zuerst, Breite zuerst, iterative Vertiefung, ...). , Parser-Kombinatoren, Caches / Memoiser, Datenformat-Lese- / Schreibprogramme (S-Ausdrücke, XML, JSON, Protobuf, ...), Aufgabenwarteschlangen usw.

Diese Dinge sind so allgemein und sehr abstrakt, dass sie in der täglichen Programmierung überall wiederverwendet werden. Wenn Sie feststellen, dass Sie Spezialcode schreiben, der einfacher wäre, wenn er abstrakter / allgemeiner gestaltet würde (z. B. wenn wir eine "Liste von Kundenaufträgen" haben), können wir das Zeug "Kundenauftrag" wegwerfen, um eine "Liste" zu erhalten. ) dann könnte es eine gute Idee sein, das herauszuziehen. Selbst wenn es nicht wiederverwendet wird, können wir damit nicht zusammenhängende Funktionen entkoppeln.

Die zweite Art ist, wo wir einen konkreten Code haben, der ein echtes Problem löst, aber indem wir eine ganze Reihe von Entscheidungen treffen. Wir können es allgemeiner / wiederverwendbarer machen, indem wir diese Entscheidungen "softcodieren", z. B. in Parameter umwandeln, die Implementierung erschweren und noch konkretere Details einbauen (dh wissen, welche Hooks wir für Überschreibungen benötigen). Ihr Beispiel scheint von dieser Art zu sein. Das Problem bei dieser Art der Wiederverwendbarkeit ist, dass wir möglicherweise versuchen, die Anwendungsfälle anderer Menschen oder unser zukünftiges Ich zu erraten. Schließlich könnten wir so viele Parameter am Ende mit , dass unser Code nicht ist verwendbargeschweige denn wiederverwendbar! Mit anderen Worten, wenn Sie anrufen, ist es aufwändiger, als nur unsere eigene Version zu schreiben. Hier ist YAGNI (du wirst es nicht brauchen) wichtig. Oftmals werden solche Versuche, Code "wiederverwendbar" zu machen, nicht wiederverwendet, da sie mit diesen Anwendungsfällen möglicherweise grundlegender inkompatibel sind, als es Parameter ausmachen können, oder diese potenziellen Benutzer würden lieber ihre eigenen machen (zum Teufel, sehen Sie sich alle an) Standards und Bibliotheken da draußen, deren Autoren das Wort "Einfach" vorangestellt haben, um sie von den Vorgängern zu unterscheiden!).

Diese zweite Form der "Wiederverwendbarkeit" sollte grundsätzlich nach Bedarf erfolgen. Sicher, Sie können einige "offensichtliche" Parameter festlegen, aber versuchen Sie nicht, die Zukunft vorherzusagen. YAGNI.

Warbo
quelle
Können wir sagen, dass Sie mir zustimmen, dass meine erste Aufnahme in Ordnung war, bei der sogar das Jahr fest codiert war? Oder würden Sie bei der ersten Implementierung dieser Anforderung das Jahr zu einem Parameter in Ihrer ersten Aufnahme machen?
Koray Tugay
Ihre erste Aufnahme war in Ordnung, da die Anforderung ein einmaliges Skript war, um "etwas zu überprüfen". Sie besteht ihren "ethischen" Test nicht, aber sie besteht den "kein Dogma" Test nicht. "Sie könnte sagen ..." erfindet Anforderungen, die Sie nicht brauchen werden.
Warbo
Wir können nicht sagen, welche "Stadt zerstören" "besser" ist, ohne weitere Informationen: Es destroyBaghdadhandelt sich um ein einmaliges Skript (oder zumindest ist es idempotent). Vielleicht wäre es eine Verbesserung, eine Stadt zu zerstören, aber was ist, wenn destroyBaghdadder Tigris überflutet wird? Das kann für Mosul und Basra wiederverwendbar sein, aber nicht für Mekka oder Atlanta.
Warbo
Ich sehe, Sie stimmen dem Nathaniel Borenstein, dem Besitzer des Zitats, nicht zu. Ich versuche langsam zu verstehen, denke ich, indem ich all diese Antworten und die Diskussionen lese.
Koray Tugay
1
Ich mag diese Unterscheidung. Es ist nicht immer klar und es gibt immer "Grenzfälle". Aber im Allgemeinen bin ich auch ein Fan von "Bausteinen" (oft in Form von staticMethoden), die rein funktional und einfach sind, und im Gegensatz dazu ist die Entscheidung über die "Konfiguration von Parametern und Hooks" in der Regel genau die richtige Sie müssen einige Strukturen aufbauen , die gerechtfertigt werden sollen.
Marco13
1

Es gibt bereits viele hervorragende und durchdachte Antworten. Einige von ihnen gehen tief in spezifische Details ein, legen bestimmte Standpunkte zu Softwareentwicklungsmethoden im Allgemeinen dar, und einige von ihnen enthalten sicherlich kontroverse Elemente oder "Meinungen".

In der Antwort von Warbo wurde bereits auf verschiedene Arten der Wiederverwendbarkeit hingewiesen . Nämlich, ob etwas wiederverwendbar ist, weil es ein grundlegender Baustein ist, oder ob etwas wiederverwendbar ist, weil es in irgendeiner Weise "generisch" ist. In Bezug auf Letzteres gibt es etwas, das ich als eine Art Maß für die Wiederverwendbarkeit betrachten würde:

Ob eine Methode eine andere emulieren kann.

Zum Beispiel aus der Frage: Stellen Sie sich vor, dass die Methode

void dayLightSavings()

war die Implementierung einer Funktionalität, die von einem Kunden angefordert wurde. Es wird also etwas sein, das andere Programmierer verwenden sollen , und somit eine öffentliche Methode wie in

publicvoid dayLightSavings()

Dies könnte implementiert werden, wie Sie in Ihrer Antwort gezeigt haben. Jetzt möchte jemand es mit dem Jahr parametrisieren. Sie können also eine Methode hinzufügen

publicvoid dayLightSavings(int year)

und ändern Sie die ursprüngliche Implementierung, um gerade zu sein

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

Die nächsten "Featureanforderungen" und Verallgemeinerungen folgen demselben Muster. Also , wenn und nur wenn es eine Nachfrage für die allgemeinste Form ist, können Sie es implementieren, zu wissen , dass diese allgemeinste Form für triviale Implementierungen des speziellere erlaubt:

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

Wenn Sie zukünftige Erweiterungen und Feature - Anfragen erwartet hatten, und hatten einige Zeit zu Ihrer Verfügung und wollten ein langweiliges Wochenende mit (möglicherweise nutzlos) Verallgemeinerungen verbringen, Sie könnten mit den meisten generischen haben damit begonnen , von Anfang an . Aber nur als private Methode. Solange Sie nur die vom Kunden angeforderte einfache Methode als öffentliche Methode verfügbar gemacht haben, sind Sie auf der sicheren Seite.

tl; dr :

Die Frage ist eigentlich nicht so sehr, "wie wiederverwendbar eine Methode sein sollte". Die Frage ist, wie viel von dieser Wiederverwendbarkeit verfügbar gemacht wird und wie die API aussieht. Das Erstellen einer zuverlässigen API, die den Test der Zeit bestehen kann (auch wenn später weitere Anforderungen gestellt werden), ist eine Kunst und ein Handwerk, und das Thema ist viel zu komplex, um es hier zu behandeln. Schauen Sie sich zunächst diese Präsentation von Joshua Bloch oder das API-Designbuch-Wiki an.

Marco13
quelle
dayLightSavings()Anrufen dayLightSavings(2018)scheint mir keine gute Idee zu sein.
Koray Tugay
@KorayTugay Wenn die erste Anforderung lautet, dass "die Sommerzeit für 2018" gedruckt werden soll, ist dies in Ordnung. Dies ist genau die Methode, die Sie ursprünglich implementiert haben. Wenn es die "Sommerzeit" für das aktuelle JahrdayLightSavings(computeCurrentYear());
ausgeben sollte
0

Eine gute Faustregel lautet: Ihre Methode sollte so wiederverwendbar sein wie ... wiederverwendbar.

Wenn Sie erwarten, dass Sie Ihre Methode nur an einer Stelle aufrufen, sollte sie nur Parameter enthalten, die der aufrufenden Site bekannt und für diese Methode nicht verfügbar sind.

Wenn Sie mehr Anrufer haben, können Sie neue Parameter einführen, solange andere Anrufer diese Parameter möglicherweise übergeben. ansonsten brauchst du eine neue methode.

Da die Anzahl der Anrufer mit der Zeit zunehmen kann, müssen Sie auf das Refactoring oder die Überlastung vorbereitet sein. In vielen Fällen bedeutet dies, dass Sie sich sicher fühlen sollten, den Ausdruck auszuwählen und die Aktion "Parameter extrahieren" Ihrer IDE auszuführen.

Karol
quelle
0

Ultrakurze Antwort: Je weniger Kopplung oder Abhängigkeit von anderem Code Ihr generisches Modul aufweist, desto wiederverwendbarer kann es sein.

Ihr Beispiel hängt nur von ab

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

Theoretisch kann es also in hohem Maße wiederverwendbar sein.

In der Praxis denke ich nicht, dass Sie jemals einen zweiten Verwendungszweck haben werden, der diesen Code benötigt, so dass ich ihn nach dem Yagni- Prinzip nicht wiederverwendbar machen würde, wenn es nicht mehr als 3 verschiedene Projekte gibt, die diesen Code benötigen.

Weitere Aspekte der Wiederverwendbarkeit sind Benutzerfreundlichkeit und Dokumentation, die mit Test Driven Development korrelieren : Es ist hilfreich, wenn Sie einen einfachen Komponententest haben, der die einfache Verwendung Ihres generischen Moduls als Codierungsbeispiel für Benutzer Ihrer Bibliothek demonstriert / dokumentiert.

k3b
quelle
0

Dies ist eine gute Gelegenheit, eine Regel zu formulieren, die ich kürzlich geprägt habe:

Ein guter Programmierer zu sein bedeutet, die Zukunft vorhersagen zu können.

Das ist natürlich absolut unmöglich! Schließlich wissen Sie nie genau , welche Verallgemeinerungen Sie später nützlich finden, welche zugehörigen Aufgaben Sie ausführen möchten, welche neuen Funktionen Ihre Benutzer benötigen usw. Aber Erfahrung gibt Ihnen manchmal eine ungefähre Vorstellung davon, was sich als nützlich erweisen könnte.

Die anderen Faktoren, die Sie gegeneinander abwägen müssen, sind der zusätzliche Zeit- und Arbeitsaufwand und die Komplexität des Codes. Manchmal hat man Glück und die Lösung des allgemeineren Problems ist tatsächlich einfacher! (Zumindest konzeptionell, wenn nicht in der Menge an Code.) Oft sind jedoch sowohl Komplexitätskosten als auch Zeit- und Arbeitsaufwand zu erwarten.

Wenn Sie also der Meinung sind, dass eine Verallgemeinerung sehr wahrscheinlich erforderlich ist, lohnt sich dies häufig (es sei denn, dies bringt viel Arbeit oder Komplexität mit sich). aber wenn es weniger wahrscheinlich erscheint, ist es wahrscheinlich nicht (es sei denn, es ist sehr einfach und / oder vereinfacht den Code).

(Für ein aktuelles Beispiel: Letzte Woche wurde mir eine Spezifikation für Aktionen gegeben. Ein System sollte genau 2 Tage nach Ablauf von etwas dauern. Deshalb habe ich den Zeitraum von 2 Tagen zu einem Parameter gemacht. Diese Woche waren die Geschäftsleute begeistert, wie sie waren Ich hatte das Glück, dass es eine einfache Änderung war, und ich nahm an, dass es sehr wahrscheinlich erwünscht war. Oft ist es schwerer zu beurteilen. Aber es lohnt sich immer noch, vorherzusagen, und Erfahrung ist oft ein guter Leitfaden .)

Scherze
quelle
0

Die beste Antwort auf "Woher weiß ich, wie wiederverwendbar meine Methoden sein sollten?" Ist "Erfahrung". Wenn Sie dies einige tausend Mal tun, erhalten Sie in der Regel die richtige Antwort. Als Teaser kann ich Ihnen jedoch die letzte geben Diese Antwort lautet: Ihr Kunde teilt Ihnen mit, wie viel Flexibilität und wie viele Verallgemeinerungsebenen Sie anstreben sollten.

Viele dieser Antworten enthalten spezifische Ratschläge. Ich wollte etwas generischeres geben ... weil die Ironie zu viel Spaß macht, um darauf zu verzichten!

Wie einige der Antworten festgestellt haben, ist die Allgemeinheit teuer. Wirklich nicht. Nicht immer. Das Verständnis der Kosten ist für das Spielen des Wiederverwendbarkeitsspiels von wesentlicher Bedeutung.

Ich konzentriere mich darauf, Dinge auf eine Skala von "irreversibel" bis "umkehrbar" zu bringen. Es ist eine glatte Skala. Das einzig wirklich Irreversible ist, "Zeit für das Projekt aufzuwenden". Sie werden diese Ressourcen niemals zurückbekommen. Etwas weniger umkehrbar sind möglicherweise Situationen mit "goldenen Handschellen" wie die Windows-API. Veraltete Funktionen bleiben jahrzehntelang in dieser API, da das Geschäftsmodell von Microsoft dies erfordert. Wenn Sie Kunden haben, deren Beziehung durch das Rückgängigmachen einer API-Funktion dauerhaft beschädigt würde, sollte dies als irreversibel behandelt werden. Wenn Sie sich das andere Ende der Skala ansehen, sehen Sie Dinge wie den Prototypcode. Wenn Sie nicht mögen, wohin es geht, können Sie es einfach wegwerfen. Etwas weniger umkehrbar sind möglicherweise APIs für die interne Verwendung. Sie können überarbeitet werden, ohne einen Kunden zu stören,Zeit (die irreversibelste Ressource von allen!)

Legen Sie diese also auf eine Skala. Jetzt können Sie eine Heuristik anwenden: Je umkehrbarer etwas ist, desto mehr können Sie es für zukünftige Aktivitäten verwenden. Wenn etwas irreversibel ist, verwenden Sie es nur für konkrete kundenorientierte Aufgaben. Aus diesem Grund sehen Sie Prinzipien wie jene der extremen Programmierung, die nur das tun, was der Kunde verlangt, und nicht mehr. Mit diesen Grundsätzen können Sie sicherstellen, dass Sie nichts tun, was Sie bereuen.

Dinge wie das DRY-Prinzip legen einen Weg nahe, dieses Gleichgewicht zu bewegen. Wenn Sie feststellen, dass Sie sich wiederholen, ist dies eine Gelegenheit, eine im Grunde genommen interne API zu erstellen. Kein Kunde sieht es, daher können Sie es jederzeit ändern. Sobald Sie diese interne API haben, können Sie mit zukunftsweisenden Dingen spielen. Wie viele verschiedene zeitzonenbasierte Aufgaben werden Sie Ihrer Meinung nach von Ihrer Frau erledigt? Haben Sie andere Kunden, die zeitzonenbasierte Aufgaben wünschen? Ihre Flexibilität wird dabei von den konkreten Anforderungen Ihrer aktuellen Kunden mitgetragen und unterstützt die potenziellen zukünftigen Anforderungen zukünftiger Kunden.

Dieser Ansatz des vielschichtigen Denkens, der natürlich von DRY stammt, bietet natürlich die gewünschte Verallgemeinerung ohne Verschwendung. Aber gibt es eine Grenze? Natürlich gibt es. Aber um es zu sehen, muss man den Wald vor lauter Bäumen sehen.

Wenn Sie über viele Ebenen der Flexibilität verfügen, führt dies häufig zu einem Mangel an direkter Kontrolle über die Ebenen, mit denen Ihre Kunden konfrontiert sind. Ich hatte eine Software, bei der ich die brutale Aufgabe hatte, einem Kunden zu erklären, warum er nicht das haben kann, was er will, und zwar aufgrund der Flexibilität in 10 Schichten, die er nie sehen sollte. Wir haben uns in eine Ecke geschrieben. Wir banden uns mit der Flexibilität, die wir für nötig hielten, zu einem Knoten zusammen.

Behalten Sie also immer den Puls Ihres Kunden, wenn Sie diesen Generalisierungs- / DRY-Trick ausführen . Was glaubst du, wird deine Frau als nächstes fragen? Versetzen Sie sich in die Lage, diese Bedürfnisse zu erfüllen? Wenn Sie den Dreh raus haben, wird der Kunde Ihnen effektiv seine zukünftigen Bedürfnisse mitteilen. Wenn Sie nicht die Kunst haben, verlassen sich die meisten von uns nur auf Rätselraten! (Vor allem mit Ehepartnern!) Einige Kunden wünschen sich große Flexibilität und sind bereit, die zusätzlichen Kosten zu akzeptieren, die Ihnen durch die Entwicklung all dieser Ebenen entstehen, da sie direkt von der Flexibilität dieser Ebenen profitieren. Andere Kunden haben eher feste Anforderungen und bevorzugen eine direktere Entwicklung. Ihr Kunde teilt Ihnen mit, wie viel Flexibilität und wie viele Verallgemeinerungsstufen Sie anstreben sollten.

Cort Ammon
quelle
Nun, es sollte andere Leute geben, die das 10000-mal gemacht haben. Warum sollte ich das 10000-mal machen und Erfahrungen sammeln, wenn ich von anderen lernen kann? Weil die Antwort für jedes Individuum unterschiedlich sein wird, sodass Antworten von erfahrenen nicht auf mich zutreffen? Auch Your customer will tell you how much flexibility and how many layers of generalization you should seek.welche Welt ist das?
Koray Tugay
@KorayTugay Es ist eine Geschäftswelt. Wenn Ihre Kunden Ihnen nicht sagen, was Sie tun sollen, dann hören Sie nicht genau genug zu. Natürlich sagen sie es nicht immer in Worten, aber sie sagen es auf andere Weise. Erfahrung hilft Ihnen, ihre subtileren Botschaften zu hören. Wenn Sie noch nicht über die erforderlichen Kenntnisse verfügen, suchen Sie in Ihrem Unternehmen jemanden, der die erforderlichen Kenntnisse besitzt, um auf diese subtilen Kundenhinweise zu hören und ihnen Anweisungen zu geben. Jemand wird diese Fähigkeit haben, selbst wenn er der CEO oder im Marketing ist.
Cort Ammon
Wenn Sie in Ihrem speziellen Fall den Papierkorb nicht herausgenommen hätten, weil Sie zu beschäftigt waren, eine allgemeine Version dieses Zeitzonenproblems zu programmieren, anstatt die spezifische Lösung zu hacken, wie würde sich Ihr Kunde fühlen?
Cort Ammon
Sie stimmen also zu, dass mein erster Ansatz der richtige war, 2018 in der ersten Einstellung festzukodieren, anstatt das Jahr zu parametrisieren? (Übrigens, das hört meinem Kunden nicht wirklich zu, denke ich, das Müllbeispiel. Das kennt Ihren Kunden. Selbst wenn Sie Unterstützung von The Oracle erhalten, gibt es keine subtilen Nachrichten, die Sie anhören müssen, wenn sie sagt, ich brauche eine Liste mit Tageslicht Änderungen für 2018.) Danke für deine Zeit und antworte übrigens.
Koray Tugay
@KorayTugay Ohne zusätzliche Details zu kennen, würde ich sagen, dass Hardcoding der richtige Ansatz ist. Sie hatten keine Möglichkeit zu wissen, ob Sie zukünftigen DLS-Code benötigen würden oder welche Art von Anfrage sie als Nächstes stellen könnte. Und wenn Ihr Kunde versucht, Sie zu testen, bekommt er, was er bekommt = D
Cort Ammon
0

Kein ethisch geschulter Softwareingenieur würde jemals zustimmen, eine DestroyBaghdad-Prozedur zu schreiben. Eine grundlegende Berufsethik würde stattdessen erfordern, dass er eine DestroyCity-Prozedur schreibt, für die Bagdad als Parameter angegeben werden könnte.

Dies ist in fortgeschrittenen Softwareentwicklungskreisen als "Scherz" bekannt. Witze müssen nicht das sein, was wir "wahr" nennen, obwohl sie, um lustig zu sein, im Allgemeinen auf etwas Wahres hinweisen müssen.

In diesem speziellen Fall ist der "Witz" nicht "wahr". Wir können davon ausgehen, dass die Arbeit, die mit dem Schreiben eines allgemeinen Verfahrens zur Zerstörung einer Stadt verbunden ist, Größenordnungen über die für die Zerstörung einer bestimmten Stadt erforderliche Menge hinausgehtStadt. Andernfalls könnte jeder, der eine oder eine Handvoll Städte zerstört hat (der biblische Josua, oder Präsident Truman), das, was er getan hat, trivial verallgemeinern und in der Lage sein, jede Stadt nach Belieben zu zerstören. Dies ist jedoch nicht der Fall. Die Methoden, mit denen diese beiden Menschen eine kleine Anzahl bestimmter Städte zerstörten, würden nicht unbedingt in jeder Stadt zu jeder Zeit funktionieren. Eine andere Stadt, deren Mauern eine andere Resonanzfrequenz aufwiesen oder deren Luftverteidigung in großer Höhe besser war, würde entweder geringfügige oder grundlegende Änderungen des Anfluges erfordern (eine anders aufgestellte Trompete oder eine Rakete).

Dies führt auch zu einer Codewartung gegen Änderungen im Laufe der Zeit: Es gibt mittlerweile ziemlich viele Städte, die dank moderner Konstruktionsmethoden und des allgegenwärtigen Radars keinem dieser Ansätze mehr unterliegen würden.

Die Entwicklung und Erprobung eines ganz allgemeinen Mittels, das jede Stadt zerstört, bevor Sie zustimmen, nur eine Stadt zu zerstören , ist ein verzweifelt ineffizienter Ansatz. Kein ethisch geschulter Softwareentwickler würde versuchen, ein Problem in einem Ausmaß zu verallgemeinern, das um Größenordnungen mehr Arbeit erfordert, als der Arbeitgeber / Kunde tatsächlich bezahlen muss, ohne dass dies nachgewiesen werden muss.

Was ist also wahr? Manchmal ist das Hinzufügen von Allgemeingültigkeit trivial. Sollten wir dann immer Allgemeingültigkeit hinzufügen, wenn dies trivial ist? Ich würde immer noch "nein, nicht immer" wegen des Problems der langfristigen Wartung argumentieren. Angenommen, zum Zeitpunkt des Schreibens sind alle Städte im Grunde gleich, dann fahre ich mit DestroyCity fort. Nachdem ich dies geschrieben habe, werden zusammen mit Integrationstests, die (aufgrund des endlich zählbaren Bereichs von Eingaben) über jede bekannte Stadt iterieren und sicherstellen, dass die Funktion auf jeder funktioniert (nicht sicher, wie das funktioniert. Ruft wahrscheinlich City.clone () und auf zerstört den Klon?

In der Praxis wird die Funktion nur verwendet, um Bagdad zu zerstören. Angenommen, jemand baut eine neue Stadt, die gegen meine Techniken resistent ist (es ist tief im Untergrund oder so). Jetzt habe ich einen Integrationstestfehler für einen Anwendungsfall , der noch nicht einmal existiert , und bevor ich meine Terrorkampagne gegen die unschuldigen Zivilisten im Irak fortsetzen kann, muss ich herausfinden, wie Subterrania zerstört werden kann. Egal ob das ethisch ist oder nicht, es ist dumm und eine Verschwendung meiner verdammten Zeit .

Wollen Sie wirklich eine Funktion, die die Sommerzeit für jedes Jahr ausgibt, nur um Daten für 2018 auszugeben? Vielleicht, aber es wird sicherlich einen kleinen zusätzlichen Aufwand erfordern, nur Testfälle zusammenzustellen. Möglicherweise ist viel Aufwand erforderlich, um eine bessere Zeitzonendatenbank zu erhalten als die, die Sie tatsächlich haben. So hatte zum Beispiel die Stadt Port Arthur in Ontario im Jahr 1908 einen Zeitraum, in dem die Sommerzeit am 1. Juli begann. Befindet sich das in der Zeitzonendatenbank Ihres Betriebssystems? Dachte nicht, also ist deine verallgemeinerte Funktion falsch . Das Schreiben von Code ist nicht besonders ethisch und verspricht, dass er nicht gehalten werden kann.

Okay, mit entsprechenden Einschränkungen ist es einfach, eine Funktion zu schreiben, die Zeitzonen für eine Reihe von Jahren festlegt, beispielsweise für die Gegenwart von 1970. Aber es ist genauso einfach, die Funktion, die Sie tatsächlich geschrieben haben, zu übernehmen und zu verallgemeinern, um das Jahr zu parametrisieren. Es ist also nicht wirklich ethischer / vernünftiger, jetzt zu verallgemeinern, das zu tun, was Sie getan haben, und dann zu verallgemeinern, wenn und wann Sie es brauchen.

Wenn Sie jedoch wüssten, warum Ihre Frau diese DST-Liste überprüfen wollte, würden Sie eine fundierte Meinung darüber haben, ob sie diese Frage 2019 wahrscheinlich erneut stellen wird, und wenn ja, ob Sie sich aus dieser Schleife herausholen können, indem Sie angeben sie eine Funktion, die sie aufrufen kann, ohne sie neu kompilieren zu müssen. Sobald Sie diese Analyse durchgeführt haben, könnte die Antwort auf die Frage "Sollte dies auf die letzten Jahre verallgemeinern" "Ja" lauten. Aber Sie stellen sich selbst vor ein weiteres Problem, nämlich dass die Zeitzonendaten in Zukunft nur vorläufig sind und sie es daher für 2019 heute ausführen, Sie kann oder kann nicht erkennen, dass es sie eine Best-Rate füttert. Sie müssen also noch eine Reihe von Dokumentationen schreiben, die für die weniger allgemeine Funktion nicht benötigt werden ("Die Daten stammen aus der Zeitzonen-Datenbank bla bla hier ist der Link, um ihre Richtlinien zum Pushen von Updates bla bla zu sehen"). Wenn Sie den Sonderfall ablehnen, kommt sie die ganze Zeit nicht mit der Aufgabe klar, für die sie die Daten für 2018 benötigt, weil sie sich noch nicht einmal um irgendeinen Unsinn für 2019 kümmert.

Mach keine schwierigen Dinge, ohne sie richtig durchzudenken, nur weil es dir ein Witz gesagt hat. Ist es nützlich? Ist es billig genug für diesen Grad an Nützlichkeit?

Steve Jessop
quelle
0

Dies ist eine einfache Linie, da die Wiederverwendbarkeit, wie sie von Architekturastronauten definiert wird, ein Blödsinn ist.

Fast der gesamte von Anwendungsentwicklern erstellte Code ist extrem domänenspezifisch. Dies ist nicht 1980. Fast alles, was die Mühe wert ist, ist bereits in einem Rahmen.

Abstraktionen und Konventionen erfordern Dokumentation und Lernaufwand. Hören Sie bitte auf, neue zu erstellen, um dies zu erreichen. (Ich sehe dich an , JavaScript Leute!)

Lassen Sie uns der unplausiblen Fantasie frönen, dass Sie etwas gefunden haben, das wirklich in Ihrem Entscheidungsrahmen enthalten sein sollte. Sie können den Code nicht einfach so hochdrehen, wie Sie es normalerweise tun. Oh nein, Sie benötigen Testabdeckung nicht nur für den beabsichtigten Gebrauch, sondern auch für Abweichungen vom beabsichtigten Gebrauch, für alle bekannten Randfälle, für alle erdenklichen Fehlermodi, Testfälle für Diagnose, Testdaten, technische Dokumentation, Benutzerdokumentation, Freigabeverwaltung, Support Skripte, Regressionstests, Änderungsmanagement ...

Ist Ihr Arbeitgeber glücklich, für all das zu bezahlen? Ich werde nein sagen.

Abstraktion ist der Preis, den wir für Flexibilität zahlen. Dadurch wird unser Code komplexer und schwerer zu verstehen. Wenn die Flexibilität nicht einem realen und gegenwärtigen Bedarf dient, tun Sie es einfach nicht, weil YAGNI.

Schauen wir uns ein Beispiel aus der Praxis an, mit dem ich mich gerade befassen musste: HTMLRenderer. Es wurde nicht richtig skaliert, als ich versuchte, im Gerätekontext eines Druckers zu rendern. Ich habe den ganzen Tag gebraucht, um festzustellen, dass standardmäßig GDI (das nicht skaliert) anstelle von GDI + (das tut, aber kein Antialias) verwendet wurde, da ich in zwei Assemblys sechs Indirektionsebenen durchlaufen musste, bevor ich es fand Code, der alles tat .

In diesem Fall werde ich dem Autor vergeben. Die Abstraktion ist tatsächlich notwendig , weil dies ist Framework - Code, der fünf sehr unterschiedliche Rendering - Ziele Ziele: WinForms, WPF, dotnet Kern, Mono und PDFsharp.

Dies unterstreicht jedoch nur meinen Standpunkt: Sie führen mit ziemlicher Sicherheit keine extrem komplexen Vorgänge (HTML-Rendering mit Stylesheets) durch, die auf mehrere Plattformen abzielen und das erklärte Ziel einer hohen Leistung auf allen Plattformen verfolgen.

Ihr Code ist mit ziemlicher Sicherheit ein weiteres Datenbankraster mit Geschäftsregeln, die nur für Ihren Arbeitgeber gelten, und Steuerregeln, die nur in Ihrem Bundesstaat gelten, in einer Anwendung, die nicht zum Verkauf steht.

Alles , was indirection löst ein Problem , Sie haben nicht und macht den Code viel schwieriger zu lesen, die massiv die Kosten für die Wartung erhöht und ist ein großer schlechter Dienst an Ihren Arbeitgeber. Zum Glück können die Leute, die sich darüber beschweren sollten, nicht nachvollziehen, was Sie ihnen antun.

Ein Gegenargument ist, dass diese Art der Abstraktion eine testgetriebene Entwicklung unterstützt, aber ich denke, TDD ist auch ein Blödsinn, da es voraussetzt, dass das Unternehmen seine Anforderungen klar, vollständig und korrekt versteht. TDD eignet sich hervorragend für die NASA und Steuerungssoftware für medizinische Geräte und selbstfahrende Autos, ist aber für alle anderen viel zu teuer.


Im Übrigen ist es nicht möglich, die gesamte Sommerzeit der Welt vorherzusagen . Insbesondere Israel hat jedes Jahr ungefähr 40 Übergänge, die überall hüpfen, weil wir nicht zur falschen Zeit beten können und Gott keine Sommerzeit macht.

Peter Wone
quelle
Obwohl ich mit dem, was Sie über Abstraktionen und Lernaufwand gesagt haben, einverstanden bin, und insbesondere, wenn es um den Gräuel geht, der als "JavaScript" bezeichnet wird, muss ich dem ersten Satz nachdrücklich widersprechen. Wiederverwendbarkeit kann auf vielen Ebenen auftreten, sie kann zu weit unten liegen , und Wegwerfcode kann von Wegwerfprogrammierern geschrieben werden. Aber es gibt Menschen , die zumindest idealistisch genug sind , um Ziel zu wieder verwendbarem Code. Schade für Sie, wenn Sie nicht sehen, welche Vorteile dies haben kann.
Marco13
@ Marco13 - Da dein Einwand begründet ist, werde ich den Punkt erweitern.
Peter Wone
-3

Wenn Sie mindestens Java 8 verwenden, schreiben Sie die WorldTimeZones-Klasse, um im Wesentlichen eine Sammlung von Zeitzonen bereitzustellen.

Fügen Sie dann der WorldTimeZones-Klasse eine Filtermethode (Prädikatfilter) hinzu. Auf diese Weise kann der Aufrufer nach Belieben filtern, indem er einen Lambda-Ausdruck als Parameter übergibt.

Im Wesentlichen unterstützt die Einzelfiltermethode das Filtern nach allem, was in dem an das Prädikat übergebenen Wert enthalten ist.

Alternativ können Sie Ihrer WorldTimeZones-Klasse eine stream () -Methode hinzufügen, die beim Aufruf einen Stream von Zeitzonen erzeugt. Dann kann der Anrufer nach Belieben filtern, zuordnen und reduzieren, ohne dass Sie Spezialisierungen vornehmen müssen.

Rodney P. Barbati
quelle
3
Dies sind gute Verallgemeinerungsideen. Diese Antwort verfehlt jedoch völlig die Markierung dessen, was in der Frage gestellt wird. Die Frage ist nicht, wie die Lösung am besten verallgemeinert werden kann, sondern wo wir bei Verallgemeinerungen die Grenze ziehen und wie wir diese Überlegungen ethisch abwägen.
maple_shaft
Ich sage also, dass Sie sie nach der Anzahl der Anwendungsfälle abwägen sollten, die sie unterstützen, abhängig von der Komplexität der Erstellung. Eine Methode, die einen Anwendungsfall unterstützt, ist nicht so wertvoll wie eine Methode, die 20 Anwendungsfälle unterstützt. Auf der anderen Seite, wenn Sie nur einen Anwendungsfall kennen und es 5 Minuten dauert, um ihn zu codieren - machen Sie es. Häufig informieren Codierungsmethoden, die bestimmte Verwendungen unterstützen, Sie über die Art der Verallgemeinerung.
Rodney P. Barbati