Versteckt sich Information mehr als eine Konvention?

20

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 protectedsowohl ä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 protectedmich 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 ?

blubb
quelle
10
Entschuldigung, Eric Lippert ist im Urlaub :)
Benjol
Ihre Frage ist etwas schwer zu verstehen. Fragen Sie im Grunde nur, ob jemand von außerhalb auf ein privates Mitglied zugreifen kann?
Morgan Herlocker
1
Zugehörige Frage: programmers.stackexchange.com/questions/92380/…
user281377
@ironcode: Ich kann sagen, dass ich mich aus einigen Antworten nicht klar genug ausdrücke. Kannst du sagen, was es schwer zu verstehen macht? Ich werde versuchen zu klären.
blubb
2
"Es ist Unsinn, Informationen zur Kompilierungszeit bereitzustellen und sie dann erst zur Laufzeit zu überprüfen" - wirklich? Sagen Sie das besser Microsoft, denn darauf basiert ein wesentlicher Teil des aktuellen .NET-Frameworks: das selektive Aktivieren oder Deaktivieren der statischen Typisierung. Ich denke nicht, dass das wirklich das ist, was du meinst. Die Frage spricht wiederholt von "statischen" und "dynamischen" Überprüfungen, aber das sind Merkmale des Typsystems. Hier geht es wirklich um die Bindung zwischen Kompilierungszeit und Laufzeit .
Aaronaught

Antworten:

4

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): passder 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

Verbergen sich Informationen mehr als eine Konvention zwischen Programmierern, um zu definieren, was Teil der offiziellen Schnittstelle einer Klasse ist?

Nach allem, was ich gesagt habe, ja.

Kann es verwendet werden, um den geheimen Zustand einer Klasse vor Angriffen zu schützen? Kann Reflexion diesen Mechanismus außer Kraft setzen? Gibt es weitere Vorteile, die ein vom Compiler erzwungener formaler Mechanismus bietet?

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.

wleao
quelle
3
Reflexion kann normalerweise den Zugriffsschutz umgehen. Bei Java und C # können beide auf private Daten zugreifen.
Mark H
Vielen Dank! Die Antworten hier: stackoverflow.com/questions/1565734/… erklären Sie es!
wleao
1
Die Javascript-Kultur unterscheidet sich stark von der Python-Kultur. Python hat pep-08 und fast alle Python-Entwickler folgen diesen Konventionen. Verstöße gegen PEP-08 werden oft als Fehler angesehen. Daher denke ich, dass Javascript / PHP / Perl / Ruby-Erfahrung sehr wenig in Python-Erfahrung übersetzt: _private funktioniert sehr gut in Python.
Mike Korobov
1
In einigen (aber nicht allen) Programmiersprachen kann das Ausblenden von Informationen zum Schutz vor feindlichem Code verwendet werden. Das Ausblenden von Informationen kann nicht zum Schutz vor feindlichen Benutzern verwendet werden.
Brian
2
@Mike Wenn PEP-08-Verstöße so oft als "Fehler" betrachtet werden und kein Python-Entwickler davon träumen würde, die Konventionen nicht zu befolgen, können Sie sie auch durch die Sprache erzwingen lassen! Warum menial arbeiten, wenn die Sprache es automatisch kann?
Andres F.
27

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:

  • public: Die sichere Verwendung in Instanzen der Klasse, der Zweck der Klasse, ändert sich nicht.
  • protected: Wird beim Erweitern (Ableiten von) der Klasse verwendet - kann sich ändern, wenn sich die Implementierung drastisch ändern muss.
  • privat: Nicht anfassen! Kann sich nach Belieben ändern, um eine bessere Implementierung der erwarteten Schnittstellen zu ermöglichen.
Joris Timmermans
quelle
3
Noch wichtiger ist, dass jede nicht erzwungene Konvention für "Implementierungsdetails, vorbehaltlich Änderungen" (die leicht zu erkennen ist, was z. B. bei führenden Unterstrichen der Fall ist) auch die versehentliche Verwendung privater Mitglieder vermeidet. In Python zum Beispiel schreibt ein vernünftiger Programmierer Code nur dann mit einem privaten Mitglied von außen (und selbst dann ist es verpönt), wenn es fast unvermeidbar ist, er genau weiß, was er tut, und akzeptiert den möglichen Bruch in zukünftigen Versionen . Dies ist eine Situation, in der z. B. Java-Programmierer Reflektion verwenden würden.
3
@ Simon Stelling - nein, ich sage, dass der ursprüngliche Implementierer der Klasse, die etwas privat gemacht hat, Ihnen sagt: "Verwenden Sie dies nicht, irgendein Verhalten hier kann sich in Zukunft ändern." Beispielsweise kann eine private Mitgliedsvariable einer Klasse, die zuerst einen Abstand zwischen zwei Punkten enthält, später den Abstand zwischen zwei Punkten enthalten, die aus Leistungsgründen zum Quadrat gezählt werden. Alles, was sich auf das alte Verhalten verlassen hätte, würde lautlos zerbrechen.
Joris Timmermans
1
@MadKeithV: Wie unterscheidet sich dann ein Schlüsselwort auf Sprachebene, das "Nicht anfassen, weil ..." sagt, von einer Konvention, die dasselbe bedeutet? Nur der Compiler erzwingt es, aber dann sind wir wieder bei der alten statischen vs. dynamischen Diskussion.
7
@ Simon Stelling (und Delnan) - für mich ist das wie die Frage "Wie unterscheidet sich ein Motorbegrenzer von einem Geschwindigkeitsbegrenzungszeichen?".
Joris Timmermans
1
Ich bin mir des Unterschieds sehr wohl bewusst und habe nie gesagt, dass er nicht da ist. Ich finde nur, dass Ihre Antwort und die meisten Ihrer Folgekommentare lediglich die Art der Anmerkungen für Mitglieder und Methoden beschreiben, die mit Konventionen oder Schlüsselwörtern auf Sprachebene vorgenommen werden können, und den tatsächlichen Unterschied (Vorhandensein der Durchsetzung auf Sprachebene) belassen vom Leser gefolgert.
11

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.

Morgan Herlocker
quelle
2
Ich stimme vollkommen zu, aber das beantwortet meine Frage nicht. Ob ich _memberoder private memberin meiner Klasse verwende, ist vernachlässigbar, solange der andere Programmierer versteht, dass er nur publicMitglieder ohne Präfix betrachten soll.
blubb
6
Ja, aber die öffentliche Dokumentation einer Klasse, die in einer dynamischen Sprache geschrieben ist, wirft diese 36 privaten Teile auch nicht auf Sie (zumindest nicht standardmäßig).
3
@ Simon Die Aussage, dass es sich um "nur eine Konvention" handelt, ist irreführend, da sie echte Vorteile hat. "Just a Convention" ist so etwas wie geschweifte Klammern in der nächsten Zeile. Eine Konvention mit Vorteilen ist so etwas wie die Verwendung aussagekräftiger Namen. Ich würde sagen, das ist völlig anders. Verhindert das Setzen von etwas auf privat, dass jemand Sie als "geheime Methode" ansieht? Nein, nicht wenn sie entschlossen sind.
Morgan Herlocker
1
@ Simon - Wenn nur Methoden verfügbar gemacht werden, die sicher sind, erhöht sich auch die Wahrscheinlichkeit, dass der Benutzer beim Versuch, die Klasse zu verwenden, nichts kaputt macht. Es könnte leicht Methoden geben, die einen Webserver zum Absturz bringen, wenn er tausendmal aufgerufen wird. Sie möchten nicht, dass die Verbraucher Ihrer API dies können. Abstraktion ist mehr als nur eine Konvention.
Morgan Herlocker
4

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:

public class EmailAlert
{
    private readonly List<string> addresses = new List<string>();

    public void AddRecipient(string address)
    {
        if (!string.IsNullOrEmpty(address))
            addresses.Add(address);
    }

    public void Send(string message)
    {
        foreach (string address in addresses)
            SendTo(address, message);
    }

    // Details of SendTo not shown
}

Was ist hier los?

  • Das Mitglied addresseswird bei der Klassenerstellung initialisiert.
  • Es ist privat, daher kann es von außen nicht berührt werden.
  • Wir haben es auch gemacht readonly, sodass es nach dem Bau von innen nicht mehr berührt werden kann (dies ist nicht immer korrekt / notwendig, aber hier nützlich).
  • Daher kann die SendMethode eine Annahme treffen, addressesdie niemals eintreten wird null. 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 addressesFeld schreiben könnten (dh wenn dies der publicFall 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:

Kann es verwendet werden, um den geheimen Zustand einer Klasse vor Angriffen zu schützen?

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.

Kann Reflexion diesen Mechanismus außer Kraft setzen?

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.

Was würde es für den Compiler lohnen, das Verbergen von Informationen zu erzwingen?

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.

Aaronaught
quelle
1
@ Simon: Wir müssen hier "Unwissenheit" klären. Das Präfixieren einer Methode mit _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.
Aaronaught
2
Wie wäre es mit einer Analogie: Ein private/ protectedaccess-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.
Aaronaught
3

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 privateVariablen zu speichern , ihn mit einer protectedoder public-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 einer privateVariablen, machen Sie es mit einer Eigenschaft verfügbar, und überprüfen Sie im Setter, ob sich der Wert geändert hat und feuern Sie PropertyChanging/ PropertyChangedEreignisse ab. Aber ohne die interne Implementierung zu sehen, würden Sie nie alles wissen, was sich hinter den Kulissen abspielt.

Joel C
quelle
3

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:

Wir möchten, dass die Struktur des Systems von den Designern explizit festgelegt wird, bevor die Programmierung beginnt, und nicht versehentlich durch die Verwendung von Informationen durch einen Programmierer.

Fuhrmanator
quelle
1
+1 für das Zitat "Verwendung von Informationen durch Programmierer", genau das ist es (obwohl Sie am Ende ein zusätzliches Komma haben).
Jmoreno
2

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.

back2dos
quelle
1
Wenn Ihre Bibliothek in derselben Firma verwendet wird, in der Sie arbeiten, ist es Ihnen wichtig ... Wenn er seine App zum Absturz bringt, wird es Ihnen schwer fallen, sie zu reparieren.
Wleao
1
Warum vorgeben, einen Sicherheitsgurt zu tragen, wenn Sie einen tragen könnten? Ich würde die Frage umformulieren und fragen, gibt es einen Grund, warum ein Compiler sie nicht erzwingen sollte, wenn ihm alle Informationen zur Verfügung stehen. Sie wollen nicht warten, bis Sie einen Unfall haben, um herauszufinden, ob dieser Sicherheitsgurt das Tragen wert war.
Mark H
1
@ Simon: Warum eine Schnittstelle mit Dingen überladen, die nicht von Leuten verwendet werden sollen? Ich hasse es, dass C ++ erfordert, dass jedes Mitglied in der Klassendefinition in einer Header-Datei vorhanden ist (ich verstehe warum). Schnittstellen sind für andere Menschen und sollten nicht mit Dingen übersät sein, die sie nicht benutzen können.
Phkahler
1
@phkahler: pydocentfernt _prefixedMembersvon der Schnittstelle. Überfüllte Schnittstellen haben nichts mit der Auswahl eines vom Compiler überprüften Schlüsselworts zu tun.
blubb
2

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.

user470365
quelle
2

Ich bin nicht in einer Situation gewesen, in der ein durch diese Schlüsselwörter verursachter Compilerfehler mich vor einem Fehler bewahrt hätte.

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

class C {
  public void foo() { ... }

  private void fooHelper() { /* lots of complex code */ }
}

Ich möchte vielleicht in der Lage sein, die fooImplementierung zu ersetzen und möglicherweise fooHelperauf radikale Weise zu ändern . Wenn sich einige Leute dazu entschlossen haben, fooHelpertrotz aller Warnungen in meiner Dokumentation zu verwenden, kann ich das möglicherweise nicht.

privateErmöglicht es Bibliotheksautoren, Bibliotheken in überschaubare Methoden (und privateHilfsklassen) zu unterteilen, ohne befürchten zu müssen, dass sie diese internen Details über Jahre hinweg beibehalten müssen.


Was würde es für den Compiler lohnen, das Verbergen von Informationen zu erzwingen?

In Java privatewird dies nicht vom Compiler, sondern vom Java- Bytecode-Verifizierer erzwungen .


Kann Reflexion diesen Mechanismus außer Kraft setzen? Was würde es für den Compiler lohnen, das Verbergen von Informationen zu erzwingen?

In Java kann nicht nur die Reflexion diesen Mechanismus außer Kraft setzen. privateIn Java gibt es zwei Arten . Auf privatediese Weise wird verhindert, dass eine äußere Klasse auf die privateMitglieder einer anderen äußeren Klasse zugreift, was vom Bytecode-Verifizierer überprüft wird, aber auch auf privatediejenigen, die von einer inneren Klasse über eine paketprivate synthetische Zugriffsmethode wie in verwendet werden

 public class C {
   private int i = 42;

   public class B {
     public void incr() { ++i; }
   }
 }

Da die Klasse B(mit wirklichem Namen C$B) verwendet wird i, erstellt der Compiler eine synthetische BZugriffsmethode , die den Zugriff C.iauf eine Weise ermöglicht, die über den Bytecode-Verifizierer hinausgeht. Da ClassLoaderSie mit a eine Klasse erstellen können , ist es leider byte[]ziemlich einfach, auf die Privaten zuzugreifen, Cdie inneren Klassen ausgesetzt waren, indem Sie eine neue Klasse in Cs Paket erstellen. Dies ist möglich, wenn Cs Jar nicht versiegelt wurde.

Die ordnungsgemäße privateDurchsetzung erfordert eine Koordinierung zwischen den Klassenladeprogrammen, der Bytecode-Überprüfung und der Sicherheitsrichtlinie, die den reflektierten Zugriff auf Private verhindern kann.


Kann es verwendet werden, um den geheimen Zustand einer Klasse vor Angriffen zu schützen?

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:

Joe-E ist eine Teilmenge der Java-Programmiersprache, die zur Unterstützung der sicheren Programmierung gemäß der Objektfähigkeitsdisziplin entwickelt wurde. Joe-E soll den Aufbau sicherer Systeme sowie die Sicherheitsüberprüfung von in Joe-E eingebauten Systemen erleichtern.

Das auf dieser Seite verlinkte Dokument gibt ein Beispiel dafür, wie die privateDurchsetzung eine sichere Zerlegung ermöglicht.

Bereitstellung einer sicheren Kapselung.

Abbildung 1. Eine reine Append-Protokollierungsfunktion.

public final class Log {
  private final StringBuilder content;
  public Log() {
    content = new StringBuilder();
  }
  public void write(String s) {
    content.append(s);
  }
}

Betrachten Sie Abb. 1, die veranschaulicht, wie eine reine Append-Protokollfunktion erstellt werden kann. Vorausgesetzt, der Rest des Programms ist in Joe-E geschrieben, kann ein Code-Reviewer davon ausgehen, dass Protokolleinträge nur hinzugefügt und nicht geändert oder entfernt werden können. Diese Überprüfung ist praktisch, da nur eine Überprüfung der Log Klasse erforderlich ist und kein anderer Code überprüft werden muss. Für die Überprüfung dieser Eigenschaft sind daher nur lokale Überlegungen zum Protokollierungscode erforderlich.

Mike Samuel
quelle
1

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.

TMN
quelle
1

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.

Nicol Bolas
quelle