Wie notwendig ist es, defensive Programmierpraktiken für Code zu befolgen, der niemals öffentlich verfügbar sein wird?

45

Ich schreibe eine Java-Implementierung eines Kartenspiels, also habe ich eine spezielle Art von Sammlung erstellt, die ich als Zone bezeichne. Alle Änderungsmethoden von Javas Sammlung werden nicht unterstützt, es gibt jedoch eine Methode in der Zonen-API, move(Zone, Card)mit der eine Karte von der angegebenen Zone zu sich selbst verschoben wird (mithilfe von paketprivaten Techniken). Auf diese Weise kann ich sicherstellen, dass keine Karten aus einer Zone genommen werden und einfach verschwinden. Sie können nur in eine andere Zone verschoben werden.

Meine Frage ist, wie notwendig ist diese Art der defensiven Codierung? Es ist "richtig" und es fühlt sich wie die richtige Praxis an, aber es ist nicht so, dass die Zone-API jemals Teil einer öffentlichen Bibliothek sein wird. Es ist nur für mich, also ist es so, als würde ich meinen Code vor mir selbst schützen, wenn ich wahrscheinlich effizienter sein könnte, wenn ich nur Standard-Sammlungen verwende.

Wie weit sollte ich mit dieser Zonenidee gehen? Kann mir jemand einen Rat geben, inwieweit ich darüber nachdenken sollte, die Verträge in den von mir geschriebenen Klassen beizubehalten, insbesondere für solche, die nicht wirklich öffentlich verfügbar sein werden?

Codeknacker
quelle
4
= ~ s / required / recommended / gi
GrandmasterB
2
Datentypen sollten konstruktionsbedingt korrekt sein, oder worauf bauen Sie sonst auf? Sie sollten so gekapselt sein, dass sie sich, ob veränderlich oder nicht, immer nur in gültigen Zuständen befinden können. Nur wenn es unmöglich ist, dies statisch (oder unangemessen schwierig) durchzusetzen, sollten Sie einen Laufzeitfehler auslösen.
Jon Purdy
1
Sag niemals nie. Wenn Ihr Code nicht verwendet wird, können Sie nie genau wissen, wo Ihr Code landen wird. ;)
Izkata
1
@codebreaker GrandmasterBs Kommentar ist ein Ersetzungsausdruck. Es bedeutet: "Notwendig" durch "Empfohlen" ersetzen.
Ricardo Souza
1
Das Codeless Code # 116 Trust No One ist hier wahrscheinlich besonders geeignet.

Antworten:

72

Ich werde das Designproblem nicht ansprechen - nur die Frage, ob die Dinge in einer nicht öffentlichen API "richtig" gemacht werden sollen.

Es ist nur für mich, also ist es so, als würde ich meinen eigenen Code vor mir selbst schützen

Genau darum geht es. Vielleicht gibt es Codierer, die sich an die Nuancen jeder Klasse und Methode erinnern, die sie jemals geschrieben haben, und die sie nie versehentlich mit dem falschen Vertrag aufrufen. Ich bin keiner von ihnen. Ich vergesse oft, wie Code, den ich geschrieben habe, innerhalb weniger Stunden nach dem Schreiben funktionieren soll. Nachdem Sie denken , dass Sie es richtig einmal bekommen haben, neigen dazu , Ihren Geist Gänge , um das Problem zu schalten Sie gerade arbeiten jetzt .

Sie haben Werkzeuge, um das zu bekämpfen. Diese Tools umfassen (in keiner bestimmten Reihenfolge) Konventionen, Komponententests und andere automatisierte Tests, Voraussetzungsprüfungen und Dokumentationen. Ich selbst habe Unit-Tests für von unschätzbarem Wert befunden, da Sie beide gezwungen sind, über die Verwendung Ihres Vertrags nachzudenken und später zu dokumentieren, wie die Benutzeroberfläche entworfen wurde.

Michael K
quelle
Gut zu wissen. In der Vergangenheit habe ich nur so effizient programmiert, wie ich konnte, und deshalb fällt es mir manchmal schwer, mich an solche Ideen zu gewöhnen. Ich bin froh, dass ich in die richtige Richtung gegangen bin.
Codebreaker
15
"Effizient" kann viele verschiedene Dinge bedeuten! Nach meiner Erfahrung übersehen Anfänger (nicht dass ich behaupte, dass Sie einer sind) oft, wie effizient sie das Programm unterstützen können. Code verbringt in der Support-Phase seines Produktlebenszyklus in der Regel viel mehr Zeit als in der Phase "Schreiben von neuem Code". Daher denke ich, dass dies eine Effizienz ist, die sorgfältig abgewogen werden sollte.
Charlie Kilian
2
Ich stimme definitiv zu. Als ich noch am College war, musste ich nie darüber nachdenken.
Codebreaker
25

Ich befolge normalerweise einige einfache Regeln:

  • Versuchen Sie immer nach Vertrag zu programmieren .
  • Wenn eine Methode öffentlich verfügbar ist oder Eingaben von außen erhält , setzen Sie einige Abwehrmaßnahmen durch (z IllegalArgumentException. B. ).
  • Verwenden Sie für alles andere, was nur intern zugänglich ist, Assertions (z assert input != null. B. ).

Wenn ein Client wirklich daran interessiert ist, wird er immer einen Weg finden, Ihren Code schlecht zu machen. Sie können es zumindest immer durch Reflektion tun. Aber das ist das Schöne an Vertragsgestaltung . Sie stimmen einer solchen Verwendung Ihres Codes nicht zu und können daher nicht garantieren, dass er in solchen Szenarien funktioniert.

Wenn Zonedie Klasse in Ihrem speziellen Fall nicht von Außenstehenden verwendet und / oder auf sie zugegriffen werden soll, machen Sie sie entweder paketprivat (und möglicherweise final), oder verwenden Sie vorzugsweise die Sammlungen, die Java Ihnen bereits zur Verfügung stellt. Sie sind getestet und Sie müssen das Rad nicht neu erfinden. Beachten Sie, dass Sie dadurch nicht daran gehindert werden, Zusicherungen im gesamten Code zu verwenden, um sicherzustellen, dass alles wie erwartet funktioniert.

afsantos
quelle
1
+1 für die Erwähnung von Design by Contract. Wenn Sie Verhalten nicht vollständig verbieten können (und das ist schwer zu tun), stellen Sie zumindest klar, dass es keine Garantien für schlechtes Verhalten gibt. Ich mag es auch, eine IllegalStateException oder eine UnsupportedOperationException auszulösen.
user949300
@ user949300 Sicher. Ich glaube gern, dass solche Ausnahmen mit einem sinnvollen Zweck eingeführt wurden. Die Einhaltung von Verträgen scheint eine solche Rolle zu spielen.
Afsantos
16

Defensive Programmierung ist eine sehr gute Sache.
Bis es anfängt, dem Schreiben von Code im Wege zu stehen. Dann ist es keine so gute Sache.


Ein bisschen pragmatischer sprechen ...

Es hört sich so an, als ob Sie kurz davor stehen, Dinge zu weit zu bringen. Die Herausforderung (und die Antwort auf Ihre Frage) besteht darin, die Geschäftsregeln oder Anforderungen des Programms zu verstehen.

Am Beispiel Ihrer Kartenspiel-API gibt es einige Umgebungen, in denen alles, was getan werden kann, um Betrug zu verhindern, von entscheidender Bedeutung ist. Möglicherweise sind große Beträge an echtem Geld erforderlich. Es ist daher sinnvoll, eine große Anzahl von Schecks einzulegen, um sicherzustellen, dass kein Betrug auftritt.

Auf der anderen Seite müssen Sie die SOLID-Prinzipien beachten, insbesondere die Einzelverantwortung. Die Containerklasse zu bitten, effektiv zu prüfen, wohin die Karten gehen, kann ein bisschen viel sein. Möglicherweise ist es besser, eine Audit- / Controller-Schicht zwischen dem Kartencontainer und der Funktion zu haben, die die Verschiebungsanforderungen empfängt.


Im Zusammenhang mit diesen Bedenken müssen Sie verstehen, welche Komponenten Ihrer API öffentlich zugänglich (und damit anfällig) sind und welche weniger privat und anfällig sind. Ich bin kein Befürworter einer "harten Außenbeschichtung mit einer weichen Innenseite", aber die beste Rendite Ihrer Bemühungen besteht darin, die Außenseite Ihrer API zu härten.

Ich denke nicht, dass der beabsichtigte Endbenutzer einer Bibliothek so kritisch gegenüber einer Entscheidung ist, wie viel defensive Programmierung Sie implementiert haben. Sogar mit Modulen, die ich für meinen eigenen Gebrauch schreibe, habe ich trotzdem ein gewisses Maß an Überprüfung vorgenommen, um sicherzustellen, dass ich in Zukunft keinen versehentlichen Fehler beim Aufrufen der Bibliothek gemacht habe.


quelle
2
+1 für "Bis es anfängt, Code zu schreiben." Insbesondere für kurzfristige persönliche Projekte kann das defensive Codieren viel mehr Zeit in Anspruch nehmen, als es sich lohnt.
Corey
2
Stimmen Sie zu, obwohl ich hinzufügen möchte, dass es eine gute Sache ist, defensiv programmieren zu können, aber es ist auch entscheidend, prototypisch programmieren zu können. Die Fähigkeit, beides zu tun, ermöglicht es Ihnen, die am besten geeignete Aktion zu wählen, die weitaus besser ist als viele der mir bekannten Programmierer, die nur defensiv programmieren können.
David Mulder
13

Defensive Codierung ist nicht nur für öffentlichen Code eine gute Idee. Es ist eine großartige Idee für jeden Code, der nicht sofort weggeworfen wird. Sicher, Sie wissen, wie es jetzt heißen soll , aber Sie haben keine Ahnung, wie gut Sie sich an diese sechs Monate erinnern werden, wenn Sie zum Projekt zurückkehren.

Die grundlegende Syntax von Java bietet Ihnen im Vergleich zu einer niedrigeren oder interpretierten Sprache wie C oder Javascript eine Menge eingebauten Schutz. Vorausgesetzt, Sie benennen Ihre Methoden eindeutig und verfügen nicht über eine externe "Methodensequenzierung", können Sie wahrscheinlich einfach Argumente als korrekten Datentyp angeben und vernünftiges Verhalten einschließen, wenn ordnungsgemäß eingegebene Daten immer noch ungültig sein können.

(Abgesehen davon, wenn Karten immer in der Zone sein müssen, ist es meiner Meinung nach günstiger, wenn alle im Spiel befindlichen Karten von einer globalen Sammlung auf Ihr Spielobjekt referenziert werden und Zone eine Eigenschaft von ist Aber da ich nicht weiß, was Ihre Zonen tun, außer Karten zu halten, ist es schwer zu wissen, ob das angemessen ist.)

DougM
quelle
1
Ich betrachtete die Zone als Eigentum der Karte, aber da meine Karten besser als unveränderliche Objekte funktionieren, entschied ich, dass dies der beste Weg ist. Danke für den Hinweis.
Codebreaker
3
@codebreaker Eine Sache, die in diesem Fall helfen kann, ist das Einkapseln der Karte in ein anderes Objekt. Ein Pik-Ass ist, was es ist. Der Standort definiert nicht seine Identität, und eine Karte sollte wahrscheinlich unveränderlich sein. Möglicherweise enthält eine Zone Karten: Möglicherweise enthält eine Zone CardDescriptoreine Karte, deren Position, Status (Bild oben / unten) oder sogar eine Rotation für Spiele, die sich darum kümmern. Dies sind alles veränderbare Eigenschaften, die die Identität einer Karte nicht verändern.
1

Erstellen Sie zunächst eine Klasse, die eine Liste von Zonen enthält, damit Sie keine Zone oder die darin enthaltenen Karten verlieren. Sie können dann überprüfen, ob sich eine Übertragung in Ihrer Zonenliste befindet. Diese Klasse wird wahrscheinlich eine Art Singleton sein, da Sie nur eine Instanz benötigen. Möglicherweise möchten Sie jedoch später mehrere Zonen festlegen. Lassen Sie Ihre Optionen also offen.

Zweitens: Lassen Sie Zone oder ZoneList keine Collection oder etwas anderes implementieren, es sei denn, Sie erwarten, dass Sie dies benötigen. Das heißt, wenn eine Zone oder ZoneList wird etwas übergeben werden, der eine Sammlung erwartet, dann es umzusetzen. Sie können eine Reihe von Methoden deaktivieren, indem sie eine Ausnahme auslösen (UnimplementedException oder ähnliches) oder indem sie einfach nichts tun. (Denken Sie gut nach, bevor Sie die zweite Option verwenden. Wenn Sie dies tun, weil es einfach ist, werden Sie feststellen, dass Sie Fehler vermissen, die Sie möglicherweise frühzeitig entdeckt haben.)

Es gibt echte Fragen darüber, was "richtig" ist. Aber sobald Sie herausgefunden haben, was es ist, möchten Sie die Dinge auf diese Weise tun. In zwei Jahren haben Sie all das vergessen, und wenn Sie versuchen, den Code zu verwenden, werden Sie über den Kerl, der ihn auf eine so kontraproduktive Art und Weise geschrieben und nichts erklärt hat, wirklich verärgert.

RalphChapin
quelle
2
Ihre Antwort konzentriert sich ein wenig zu sehr auf das vorliegende Problem, anstatt auf die allgemeineren Fragen, die das OP zur defensiven Programmierung im Allgemeinen stellt.
Ich übergebe die Zonen tatsächlich an Methoden, die Collections verwenden, daher ist die Implementierung erforderlich. Eine Art Registrierung von Zonen innerhalb des Spiels ist jedoch eine interessante Idee.
Codebreaker
@ GlenH7: Ich finde, das Arbeiten mit bestimmten Beispielen hilft oft mehr als abstrakte Theorie. Das OP lieferte ein ziemlich interessantes, also ging ich damit um.
RalphChapin
1

Bei der defensiven Codierung im API-Design geht es im Allgemeinen um die Validierung von Eingaben und die sorgfältige Auswahl eines geeigneten Fehlerbehandlungsmechanismus. Dinge, die andere Antworten erwähnen, sind ebenfalls erwähnenswert.

Darum geht es in Ihrem Beispiel eigentlich nicht. Sie beschränken Ihre API-Oberfläche aus einem ganz bestimmten Grund. Wie GlenH7 erwähnt, sollten Sie, wenn der Kartensatz in einem tatsächlichen Spiel verwendet werden soll, zum Beispiel mit einem ('benutzten' und 'unbenutzten') Deck, einem Tisch und Händen, auf jeden Fall Schecks anbringen, um dies sicherzustellen Karte aus dem Set ist einmal und nur einmal vorhanden.

Dass Sie dies mit "Zonen" gestaltet haben, ist eine willkürliche Wahl. Abhängig von der Implementierung (eine Zone kann im obigen Beispiel nur eine Hand, ein Deck oder ein Tisch sein) kann es sich durchaus um eine gründliche Planung handeln.

Diese Implementierung klingt jedoch wie ein abgeleiteter Typ eines mehr Collection<Card>ähnlichen Kartensatzes mit einer weniger restriktiven API. Wenn Sie zum Beispiel einen Handwertrechner oder eine KI bauen möchten, möchten Sie sicherlich frei wählen können, über welche und wie viele Karten Sie iterieren.

Es ist also gut, eine solche einschränkende API freizulegen, wenn das einzige Ziel dieser API darin besteht, sicherzustellen, dass sich jede Karte immer in einer Zone befindet.

CodeCaster
quelle