In Java, C # und vielen anderen stark typisierten, statisch überprüften Sprachen schreiben wir Code wie folgt:
public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }
Einige dynamisch überprüfte Sprachen bieten keine Schlüsselwörter, um den Grad der "Privatsphäre" eines bestimmten Klassenmitglieds auszudrücken, und stützen sich stattdessen auf Codierungskonventionen. Python zum Beispiel stellt privaten Mitgliedern einen Unterstrich voran:
_m(self): pass
Es kann argumentiert werden, dass die Bereitstellung solcher Schlüsselwörter in dynamisch überprüften Sprachen wenig Sinn macht, da sie nur zur Laufzeit überprüft werden.
Ich kann jedoch auch keinen guten Grund finden, diese Keywords in statisch überprüften Sprachen bereitzustellen. Ich finde die Anforderung, meinen Code mit ziemlich ausführlichen Stichwörtern zu füllen, die protected
sowohl ärgerlich als auch ablenkend sind. Bisher war ich nicht in einer Situation, in der ein durch diese Keywords verursachter Compilerfehler mich vor einem Fehler bewahrt hätte. Ganz im Gegenteil, ich befand mich in Situationen, in denen protected
mich ein versehentlicher Platz daran hinderte, eine Bibliothek zu benutzen.
In diesem Sinne lautet meine Frage:
Verbergen sich Informationen mehr als eine Konvention zwischen Programmierern, um zu definieren, was Teil der offiziellen Schnittstelle einer Klasse ist?
Kann es verwendet werden, um den geheimen Zustand einer Klasse vor Angriffen zu schützen? Kann Reflexion diesen Mechanismus außer Kraft setzen? Was würde es für den Compiler lohnen, das Verbergen von Informationen zu erzwingen ?
quelle
Antworten:
Ich studiere für die Java-Zertifizierung und eine ganze Reihe davon befasst sich damit, wie Zugriffsmodifikatoren verwaltet werden. Und sie machen Sinn und sollten richtig eingesetzt werden.
Ich habe mit Python gearbeitet und während meiner Lernreise habe ich gehört, dass die Konvention in Python vorhanden ist, weil Leute, die damit arbeiten, wissen sollten, was es bedeutet und wie man es anwendet. Das heißt, in
_m(self): pass
der Unterstreichung würde mich warnen, nicht mit diesem Feld herumzuspielen. Aber wird jeder dieser Konvention folgen? Ich arbeite mit Javascript und ich muss sagen, dass sie es nicht tun . Manchmal muss ich ein Problem überprüfen und der Grund war, dass die Person etwas tat, was sie nicht tun sollte ...Lesen Sie diese Diskussion zum führenden Python-Unterstrich
Nach allem, was ich gesagt habe, ja.
Ja, es kann verwendet werden, um den geheimen Status einer Klasse zu sichern. Es sollte nicht nur dazu verwendet werden, sondern auch, um zu verhindern, dass Benutzer mit dem Objektstatus in Konflikt geraten, um ihr Verhalten so zu ändern, wie es das Objekt haben sollte habe gearbeitet. Als Entwickler sollten Sie darüber nachdenken und Ihre Klasse auf sinnvolle Weise gestalten, damit ihr Verhalten nicht verfälscht wird. Und der Compiler wird Ihnen als guter Freund dabei helfen, den Code so zu halten, wie Sie ihn entworfen haben, um die Zugriffsrichtlinien durchzusetzen.
BEARBEITET Ja, Reflexion kann, überprüfen Sie die Kommentare
Die letzte Frage ist interessant und ich bin bereit, die Antworten zu lesen.
quelle
Der "private" Zugriffsbezeichner bezieht sich nicht auf den Compilerfehler, der beim ersten Anzeigen auftritt. In Wirklichkeit geht es darum , Sie daran zu hindern , auf etwas zuzugreifen, das sich noch ändern kann, wenn sich die Implementierung der Klasse ändert, die das private Mitglied enthält.
Mit anderen Worten, so dass Sie es nicht benutzen , wenn es immer noch funktioniert verhindert Sie versehentlich mit ihm noch , wenn es nicht mehr funktioniert.
Wie Delnan weiter unten bemerkte, rät die Präfix-Konvention von der versehentlichen Verwendung von Mitgliedern ab, die Änderungen unterliegen, solange die Konvention befolgt und richtig verstanden wird. Für einen böswilligen (oder ignoranten) Benutzer bedeutet dies nichts, um ihn davon abzuhalten, auf dieses Mitglied zuzugreifen, mit allen möglichen Konsequenzen. In Sprachen mit integrierter Unterstützung für Zugriffsspezifizierer geschieht dies nicht in Unkenntnis (Compilerfehler) und sticht bei böswilliger Absicht (seltsame Konstruktionen, um an das private Mitglied zu gelangen) wie ein schmerzender Daumen hervor.
Der "geschützte" Zugriffsspezifizierer ist eine andere Geschichte - betrachten Sie dies nicht einfach als "nicht ganz öffentlich" oder "viel wie privat". "Geschützt" bedeutet, dass Sie diese Funktionalität wahrscheinlich verwenden möchten , wenn Sie von der Klasse abgeleitet sind, die das geschützte Element enthält. Die geschützten Member sind Teil der "Erweiterungsschnittstelle", mit der Sie Funktionen zu vorhandenen Klassen hinzufügen können, ohne die vorhandenen Klassen selbst zu ändern.
Also, kurzer Rückblick:
quelle
Wenn Sie Code schreiben, der von einer anderen Person verwendet wird, kann das Verbergen von Informationen eine viel einfachere und verständlichere Schnittstelle bieten. "Jemand anderes" könnte ein anderer Entwickler in Ihrem Team sein, Entwickler, die eine von Ihnen kommerziell geschriebene API verwenden, oder sogar Ihr zukünftiges Ich, das sich "einfach nicht erinnern kann, wie das blöde Ding funktioniert". Es ist viel einfacher, mit einer Klasse zu arbeiten, für die nur 4 Methoden verfügbar sind, als mit einer mit 40.
quelle
_member
oderprivate member
in meiner Klasse verwende, ist vernachlässigbar, solange der andere Programmierer versteht, dass er nurpublic
Mitglieder ohne Präfix betrachten soll.Ich würde vorschlagen, dass Sie als Hintergrundinformationen zunächst über Klasseninvarianten lesen .
Eine Invariante ist, um es kurz zu machen, eine Annahme über den Zustand einer Klasse, die während des gesamten Lebens einer Klasse wahr bleiben soll.
Verwenden wir ein wirklich einfaches C # -Beispiel:
Was ist hier los?
addresses
wird bei der Klassenerstellung initialisiert.readonly
, sodass es nach dem Bau von innen nicht mehr berührt werden kann (dies ist nicht immer korrekt / notwendig, aber hier nützlich).Send
Methode eine Annahme treffen,addresses
die niemals eintreten wirdnull
. Es muss nicht , dass der Check durchführt , weil es keine Möglichkeit , dass der Wert geändert werden kann.Wenn andere Klassen in das
addresses
Feld schreiben könnten (dh wenn dies derpublic
Fall wäre), wäre diese Annahme nicht mehr gültig. Jede andere Methode in der Klasse, die von diesem Feld abhängt, müsste explizite Nullprüfungen durchführen , oder das Programm könnte abstürzen.Also ja, es ist viel mehr als eine "Konvention"; Alle Zugriffsmodifikatoren für Klassenmitglieder bilden zusammen eine Reihe von Annahmen darüber, wann und wie dieser Status geändert werden kann. Diese Annahmen werden anschließend in abhängige und voneinander abhängige Mitglieder und Klassen eingearbeitet, damit Programmierer nicht gleichzeitig über den gesamten Status des Programms nachdenken müssen. Die Fähigkeit, Annahmen zu treffen, ist ein entscheidendes Element für das Management der Komplexität von Software.
Zu diesen anderen Fragen:
Ja und nein. Sicherheitscode wird, wie der meiste Code, von bestimmten Invarianten abhängen. Zugriffsmodifikatoren sind sicherlich hilfreich als Wegweiser für vertrauenswürdige Anrufer, die sich nicht damit anlegen sollen. Bösartiger Code kümmert sich nicht darum, aber bösartiger Code muss auch nicht den Compiler durchlaufen.
Natürlich kann es. Für die Reflexion ist es jedoch erforderlich, dass der aufrufende Code über diese Berechtigungs- / Vertrauensebene verfügt. Wenn Sie bösartigen Code mit vollem Vertrauen und / oder Administratorrechten ausführen, haben Sie diesen Kampf bereits verloren.
Der Compiler bereits tut es erzwingen. Dies gilt auch für die Laufzeit in .NET, Java und anderen derartigen Umgebungen. Der zum Aufrufen einer Methode verwendete Opcode ist nicht erfolgreich, wenn die Methode privat ist. Die einzigen Möglichkeiten, um diese Einschränkung zu umgehen, erfordern vertrauenswürdigen Code mit erhöhten Rechten, und Code mit erhöhten Rechten kann immer direkt in den Programmspeicher geschrieben werden. Es wird so viel erzwungen, wie erzwungen werden kann , ohne dass ein benutzerdefiniertes Betriebssystem erforderlich ist.
quelle
_
Zuständen des Vertrags, erzwingt ihn jedoch nicht. Das kann es schwierig zu sein , macht unwissend den Vertrages, aber es macht es nicht schwer zu brechen , den Vertrag, vor allem nicht im Vergleich zu mühsam und oft restriktive Methoden wie Reflexion. Eine Konvention sagt, "Sie sollten dies nicht brechen". Ein Zugriffsmodifikator sagt, "Sie können dies nicht brechen". Ich versuche nicht zu sagen, dass ein Ansatz besser ist als der andere - beide sind in Ordnung, je nach Ihrer Philosophie, aber sie sind dennoch sehr unterschiedliche Ansätze.private
/protected
access-Modifikator ist wie ein Kondom. Sie müssen keine verwenden, aber wenn Sie dies nicht tun, sollten Sie sich über Ihr Timing und Ihre Partner verdammt sicher sein. Es wird eine böswillige Handlung nicht verhindern und möglicherweise nicht jedes Mal funktionieren, aber es wird definitiv das Risiko von Unachtsamkeit verringern und dies weitaus effektiver tun, als "Bitte seien Sie vorsichtig" zu sagen. Oh, und wenn du eins hast, aber nicht verwendest, dann erwarte kein Mitgefühl von mir.Das Verbergen von Informationen ist viel mehr als nur eine Konvention. Der Versuch, es zu umgehen, kann in vielen Fällen die Funktionalität der Klasse beeinträchtigen. Zum Beispiel ist es eine gängige Praxis, einen Wert in einer
private
Variablen zu speichern , ihn mit einerprotected
oderpublic
-Eigenschaft derselben Zeit verfügbar zu machen und im Getter auf Null zu prüfen und alle erforderlichen Initialisierungen (dh verzögertes Laden) vorzunehmen. Oder speichern Sie etwas in einerprivate
Variablen, machen Sie es mit einer Eigenschaft verfügbar, und überprüfen Sie im Setter, ob sich der Wert geändert hat und feuern SiePropertyChanging
/PropertyChanged
Ereignisse ab. Aber ohne die interne Implementierung zu sehen, würden Sie nie alles wissen, was sich hinter den Kulissen abspielt.quelle
Das Verbergen von Informationen ist aus einer Top-Down-Designphilosophie entstanden. Python wurde als Bottom-Up-Sprache bezeichnet .
Das Verbergen von Informationen wird auf Klassenebene in Java, C ++ und C # gut erzwungen, sodass es auf dieser Ebene keine wirkliche Konvention ist. Es ist sehr einfach, eine Klasse zu einer "Black Box" zu machen, mit öffentlichen Schnittstellen und versteckten (privaten) Details.
Wie Sie bereits betont haben, liegt es in Python an den Programmierern, die Konvention zu befolgen, nicht das zu verwenden, was versteckt werden soll, da alles sichtbar ist.
Selbst mit Java, C ++ oder C # wird das Ausblenden von Informationen zu einer Konvention. Bei komplexeren Softwarearchitekturen gibt es keine Zugriffskontrollen auf den höchsten Abstraktionsebenen. In Java finden Sie beispielsweise die Verwendung von ".internal". Paketnamen. Dies ist lediglich eine Namenskonvention, da es nicht einfach ist, diese Art von Informationen nur über die Paketzugriffsmöglichkeit zu erzwingen.
Eine Sprache, die sich bemüht, den Zugang formal zu definieren, ist Eiffel. Dieser Artikel weist auf einige andere Schwachstellen hin, die Informationen über Sprachen wie Java verbergen.
Hintergrund: Das Verstecken von Informationen wurde 1971 von David Parnas vorgeschlagen . In diesem Artikel weist er darauf hin, dass die Verwendung von Informationen über andere Module "die Konnektivität der Systemstruktur katastrophal erhöhen kann". Nach dieser Vorstellung kann das Verbergen von Informationen zu eng gekoppelten Systemen führen, die nur schwer zu warten sind. Er fährt fort mit:
quelle
Ruby und PHP haben es und erzwingen es zur Laufzeit.
Der Punkt, an dem Informationen ausgeblendet werden, ist das Anzeigen von Informationen. Indem interne Details "versteckt" werden, wird der Zweck aus einer Außenperspektive ersichtlich. Es gibt Sprachen, die dies annehmen. In Java ist der Zugriff standardmäßig paketintern, in haXe geschützt. Sie erklären sie ausdrücklich für öffentlich, um sie offenzulegen.
Der springende Punkt dabei ist, die Verwendung Ihrer Klassen zu vereinfachen, indem nur eine sehr kohärente Schnittstelle verfügbar gemacht wird. Sie möchten, dass der Rest geschützt wird, damit kein kluger Kerl mit Ihrem inneren Zustand in Konflikt gerät, um Ihre Klasse dazu zu bringen, das zu tun, was er will.
Wenn Zugriffsmodifikatoren zur Laufzeit erzwungen werden, können Sie sie auch verwenden, um eine bestimmte Sicherheitsstufe zu erzwingen. Ich halte dies jedoch nicht für eine besonders gute Lösung.
quelle
pydoc
entfernt_prefixedMembers
von der Schnittstelle. Überfüllte Schnittstellen haben nichts mit der Auswahl eines vom Compiler überprüften Schlüsselworts zu tun.Zugriffsmodifikatoren können definitiv etwas tun, was die Konvention nicht kann.
In Java können Sie beispielsweise nur mit Reflektion auf private Mitglieder / Felder zugreifen.
Wenn ich die Schnittstelle für ein Plugin schreibe und die Rechte zum Ändern privater Felder durch Reflektion (und zum Festlegen des Sicherheitsmanagers :) korrekt verweigere, kann ich daher ein Objekt an Funktionen senden, die von jemandem implementiert wurden, und weiß, dass er nicht auf seine privaten Felder zugreifen kann.
Natürlich kann es einige Sicherheitslücken geben, die es ihm ermöglichen, dies zu überwinden, aber dies ist nicht philosophisch wichtig (aber in der Praxis definitiv).
Wenn der Benutzer meine Benutzeroberfläche in seiner Umgebung ausführt, hat er die Kontrolle und kann so Zugriffsmodifikatoren umgehen.
quelle
Es ist nicht so sehr wichtig, Anwendungsautoren vor Fehlern zu bewahren, sondern vielmehr, dass Bibliotheksautoren entscheiden, welche Teile ihrer Implementierung sie warten möchten.
Wenn ich eine Bibliothek habe
Ich möchte vielleicht in der Lage sein, die
foo
Implementierung zu ersetzen und möglicherweisefooHelper
auf radikale Weise zu ändern . Wenn sich einige Leute dazu entschlossen haben,fooHelper
trotz aller Warnungen in meiner Dokumentation zu verwenden, kann ich das möglicherweise nicht.private
Ermöglicht es Bibliotheksautoren, Bibliotheken in überschaubare Methoden (undprivate
Hilfsklassen) zu unterteilen, ohne befürchten zu müssen, dass sie diese internen Details über Jahre hinweg beibehalten müssen.In Java
private
wird dies nicht vom Compiler, sondern vom Java- Bytecode-Verifizierer erzwungen .In Java kann nicht nur die Reflexion diesen Mechanismus außer Kraft setzen.
private
In Java gibt es zwei Arten . Aufprivate
diese Weise wird verhindert, dass eine äußere Klasse auf dieprivate
Mitglieder einer anderen äußeren Klasse zugreift, was vom Bytecode-Verifizierer überprüft wird, aber auch aufprivate
diejenigen, die von einer inneren Klasse über eine paketprivate synthetische Zugriffsmethode wie in verwendet werdenDa die Klasse
B
(mit wirklichem NamenC$B
) verwendet wirdi
, erstellt der Compiler eine synthetischeB
Zugriffsmethode , die den ZugriffC.i
auf eine Weise ermöglicht, die über den Bytecode-Verifizierer hinausgeht. DaClassLoader
Sie mit a eine Klasse erstellen können , ist es leiderbyte[]
ziemlich einfach, auf die Privaten zuzugreifen,C
die inneren Klassen ausgesetzt waren, indem Sie eine neue Klasse inC
s Paket erstellen. Dies ist möglich, wennC
s Jar nicht versiegelt wurde.Die ordnungsgemäße
private
Durchsetzung erfordert eine Koordinierung zwischen den Klassenladeprogrammen, der Bytecode-Überprüfung und der Sicherheitsrichtlinie, die den reflektierten Zugriff auf Private verhindern kann.Ja. "Sichere Dekomposition" ist möglich, wenn Programmierer zusammenarbeiten können, während die Sicherheitseigenschaften ihrer Module erhalten bleiben. Ich muss dem Autor eines anderen Codemoduls nicht vertrauen, um die Sicherheitseigenschaften meines Moduls nicht zu verletzen.
Object Capabilities-Sprachen wie Joe-E verwenden das Ausblenden von Informationen und andere Mittel, um eine sichere Zerlegung zu ermöglichen:
Das auf dieser Seite verlinkte Dokument gibt ein Beispiel dafür, wie die
private
Durchsetzung eine sichere Zerlegung ermöglicht.quelle
Das Verbergen von Informationen ist eines der Hauptanliegen eines guten Softwaredesigns. Schauen Sie sich die Papiere von Dave Parnas aus den späten 70ern an. Wenn Sie nicht garantieren können, dass der interne Status Ihres Moduls konsistent ist, können Sie im Grunde nichts über sein Verhalten garantieren. Und die einzige Möglichkeit, den internen Zustand zu gewährleisten, besteht darin, ihn privat zu halten und ihn nur mit den von Ihnen angegebenen Mitteln ändern zu lassen.
quelle
Wenn Sie den Schutz zu einem Teil der Sprache machen, erhalten Sie etwas: eine angemessene Sicherheit.
Wenn ich eine Variable als privat deklariere , kann ich mit hinreichender Sicherheit davon ausgehen, dass sie nur von Code in dieser Klasse oder von explizit deklarierten Freunden dieser Klasse berührt wird. Der Umfang des Codes, der diesen Wert vernünftigerweise berühren könnte, ist begrenzt und explizit definiert.
Gibt es nun Möglichkeiten, um den syntaktischen Schutz zu umgehen? Absolut; die meisten Sprachen haben sie. In C ++ können Sie die Klasse immer in einen anderen Typ umwandeln und an ihren Bits herumstöbern. In Java und C # können Sie sich darin wiederfinden. Und so weiter.
Dies zu tun ist jedoch schwierig . Es ist offensichtlich, dass Sie etwas tun, was Sie nicht tun sollten. Sie können dies nicht aus Versehen tun (außerhalb von Wildwrites in C ++). Sie müssen bereitwillig denken: "Ich werde etwas anfassen, das mir von meinem Compiler nicht befohlen wurde." Sie müssen bereitwillig etwas Unangemessenes tun .
Ohne syntaktischen Schutz kann ein Programmierer einfach versehentlich Dinge vermasseln. Sie müssen dem Benutzer eine Konvention beibringen, die er jedes Mal befolgen muss . Wenn nicht, wird die Welt sehr unsicher.
Ohne syntaktischen Schutz liegt die Verantwortung bei den falschen Leuten: den vielen Leuten, die die Klasse benutzen. Sie müssen sich an die Konvention halten, da sonst eine nicht näher bezeichnete Schädlichkeit auftritt. Scratch that: Unbestimmte Bösartigkeit kann auftreten.
Es gibt nichts Schlimmeres als eine API, bei der bei einer falschen Vorgehensweise möglicherweise alles funktioniert. Dies gibt dem Benutzer die falsche Bestätigung, dass er das Richtige getan hat, dass alles in Ordnung ist usw.
Und was ist mit neuen Nutzern dieser Sprache? Sie müssen nicht nur die tatsächliche Syntax lernen und befolgen (vom Compiler erzwungen), sondern auch diese Konvention befolgen (bestenfalls von Gleichaltrigen erzwungen). Und wenn nicht, kann nichts Schlimmes passieren. Was passiert, wenn ein Programmierer nicht versteht, warum die Konvention existiert? Was passiert, wenn er "Mist" sagt und nur an deinen Privaten herumstochert? Und was denkt er, wenn alles funktioniert?
Er findet die Konvention dumm. Und er wird es nie wieder verfolgen. Und er wird allen seinen Freunden sagen, dass sie sich auch nicht darum kümmern sollen.
quelle