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.DESTROY
dann 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.
quelle
destroyCity(target)
ist viel unethischer alsdestroyBagdad()
! 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.Antworten:
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:
"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 .
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.
Das hängt sehr von mehreren Dingen ab:
takeActionOnCity
Methode 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 alsaction.TakeOn(city);
, sollten Sie sich fragen, ob dietakeActionOnCity
Methode wirklich einen Zweck hat oder nicht nur eine zusätzliche Ebene ist, die nichts von Wert hinzufügt.Die gleiche Frage taucht hier auf:
Wenn Sie alle drei definitiv mit "Ja" beantworten können, ist eine Abstraktion gerechtfertigt.
quelle
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.println
zum 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, dieSystem.out
standardmäßig verwendet wird).quelle
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:
ist eine ganz andere Reihe von Anforderungen an:
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
DestroyBaghdad
Funktion tatsächlich die Funktion sein kann, die über dieses Recht verfügt.quelle
Hier gibt es viele langwierige Antworten, aber ehrlich gesagt finde ich es super einfach
also in deiner funktion
Du hast:
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.
quelle
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,
Object1
damit sie sagen könnenObject2
, 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 destroyBaghdad
Anstatt 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:Dann haben Sie eine Stadt, die diese Schnittstelle implementiert:
Nichts, das die Zerstörung einer Instanz von
City
erfordert, wird sich jemals darum kümmern, wie dies geschieht. Es gibt also keinen Grund, warum dieser Code irgendwo außerhalb von sich existiertCity::destroy
, und in der Tat wäre eine genaue Kenntnis der inneren FunktionsweiseCity
von 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üssenCity
. 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
City
oderBaghdad
davon abhängt, wie allgemein sich der Prozess der Zerstörung der Stadt herausstellt. Es ist sehr wahrscheinlich, dass einCity
Wille 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ührtDestroyable
und jeder von ihnen angewiesen,City
zu zerstören sich in der gleichen Weise jemand von außen forderte dieCity
, sich selbst zu zerstören.Wenn Sie wirklich verrückt werden und die Idee eines Objekts implementieren möchten, das
Bomb
an einem Ort abgelegt wird und alles in einem bestimmten Radius zerstört, sieht es möglicherweise so aus:ObjectsByRadius
Stellt einen Satz von Objekten dar, der für dieBomb
aus den EingabenBomb
berechnet 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 AblegensBomb
und 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:
Da Ihr Beispiel Klassen aus der Java-Bibliothek verwendet, die dieses Design nicht unterstützen, können Sie einfach die API von
ZonedDateTime
direkt 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 derResult
ausschließlich mit der Kapselung des Berechnungsergebnisses und derFormattedResult
ausschließlich mit der Interaktion mit dem,Result
um 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: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.
quelle
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 :
(Fügen Sie alle Parameter , die Sie bereits wissen , die Sie benötigen, natürlich ...)
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".
quelle
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?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 ...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 Antwortis takeActionOnCity(Action action, City city) better?
null
?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.
1 Dies ist ein Codekonstrukt. In diesem Fall sind Sie der "Benutzer" - der Benutzer des Quellcodes
quelle
Es gibt einen klaren Prozess, dem Sie folgen können:
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.
quelle
Ich bin zu dem Schluss gekommen, dass es zwei Arten von wiederverwendbarem Code gibt:
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.
quelle
destroyBaghdad
handelt sich um ein einmaliges Skript (oder zumindest ist es idempotent). Vielleicht wäre es eine Verbesserung, eine Stadt zu zerstören, aber was ist, wenndestroyBaghdad
der Tigris überflutet wird? Das kann für Mosul und Basra wiederverwendbar sein, aber nicht für Mekka oder Atlanta.static
Methoden), 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.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
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
public
void 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
public
void dayLightSavings(int year)
und ändern Sie die ursprüngliche Implementierung, um gerade zu sein
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:
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.
quelle
dayLightSavings()
AnrufendayLightSavings(2018)
scheint mir keine gute Idee zu sein.dayLightSavings(computeCurrentYear());
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.
quelle
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
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.
quelle
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 .)
quelle
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.
quelle
Your customer will tell you how much flexibility and how many layers of generalization you should seek.
welche Welt ist das?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?
quelle
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.
quelle
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.
quelle