Le informazioni nascondono più di una convenzione?

20

In Java, C # e in molti altri linguaggi strongmente controllati staticamente controllati, siamo abituati a scrivere codice come questo:

public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }

Alcune lingue controllate dinamicamente non forniscono parole chiave per esprimere il livello di "privateness" di un dato membro della classe e si basano invece su convenzioni di codifica. Python ad esempio prefissa i membri privati con un carattere di sottolineatura:

_m(self): pass

Si può sostenere che fornire tali parole chiave in linguaggi controllati dinamicamente non sarebbe molto utile dato che è controllato solo in fase di runtime.

Tuttavia, non riesco a trovare una buona ragione per fornire queste parole chiave anche in lingue controllate staticamente. Trovo il requisito per riempire il mio codice con parole chiave piuttosto verbose come protected sia fastidiose che distraenti. Finora, non sono stato in una situazione in cui un errore del compilatore causato da queste parole chiave mi avrebbe salvato da un bug. Al contrario, sono stato in situazioni in cui un protected erroneamente posizionato mi ha impedito di utilizzare una libreria.

Con questo in mente, la mia domanda è:

Le informazioni nascondono più di una convenzione tra i programmatori utilizzati per definire ciò che fa parte dell'interfaccia ufficiale di una classe?

Può essere usato per proteggere lo stato segreto di una classe dall'attacco? La riflessione può ignorare questo meccanismo? Cosa renderebbe utile per il compilatore nascondere forzare le informazioni?

    
posta blubb 05.08.2011 - 15:22
fonte

11 risposte

4

Sto studiando per la certificazione Java e un bel po 'di esso riguarda come gestire i modificatori di accesso. E hanno senso e dovrebbero essere usati correttamente.

Ho lavorato con Python e, durante il mio percorso di apprendimento, ho sentito che in Python, la convenzione è lì perché le persone, che lavorano con esso, dovrebbero sapere cosa significa e come applicarlo. Detto questo, in _m(self): pass il carattere di sottolineatura mi avvisava di non scherzare con quel campo. Ma tutti seguiranno quella convenzione? Lavoro con javascript e devo dire, loro non . A volte ho bisogno di verificare un problema e il motivo era che la persona stava facendo qualcosa che non avrebbe dovuto fare ...

leggi questa discussione riguardante il carattere di sottolineatura principale di python

Is information hiding more than a convention between programmers used to define what is part of the official interface of a class?

Da quello che ho detto, sì.

Can it be used to secure a class' secret state from being attacked? Can reflection override this mechanism? Are there any other advantages provided by a more formal mechanism enforced by the compiler?

Sì, può essere usato per proteggere lo stato segreto di una classe e non dovrebbe essere usato solo per questo, ma per impedire agli utenti di interferire con lo stato degli oggetti per cambiare i loro comportamenti a qualcosa che pensano dovrebbe essere il modo in cui oggetto avrebbe dovuto funzionare. Tu, come sviluppatore, dovresti pianificare e riflettere su di esso e progettare la tua classe in un modo che abbia senso, in modo che il suo comportamento non venga manomesso. E il compilatore, come un buon amico, ti aiuterà a mantenere il codice nel modo in cui lo hai progettato applicando le politiche di accesso.

A CURA Sì, riflessione può, controllare i commenti

L'ultima domanda è interessante e sono disposto a leggere le risposte a riguardo.

    
risposta data 05.08.2011 - 15:45
fonte
27

L'identificatore di accesso "privato" non riguarda l'errore del compilatore che genera la prima volta che lo vedi. In realtà si tratta di impedire a di accedere a qualcosa che è ancora soggetto a modifiche quando l'implementazione della classe che tiene il membro privato cambia.

In altre parole, non permettendoti di usarlo quando funziona ancora ti impedisce di usarlo accidentalmente mentre lo usi quando non funziona più.

Come Delnan ha osservato al di sotto della convenzione prefisso scoraggia l'uso accidentale di membri che sono soggetti a modifiche fintanto che la convenzione è seguita e compresa correttamente. Per un utente malintenzionato (o ignorante) non fa nulla per impedirgli di accedere a quel membro con tutte le possibili conseguenze. Nelle lingue con supporto integrato per gli specificatori di accesso questo non accade nell'ignoranza (errore del compilatore), e si distingue come un pollice dolente quando è dannoso (costruzioni strane per arrivare al membro privato).

L'identificatore di accesso "protetto" è una storia diversa - non pensare a questo semplicemente come "non abbastanza pubblico" o "molto simile a privato". "Protetto" significa che probabilmente vuoi usare questa funzionalità quando si ottiene dalla classe che contiene il membro protetto. I membri protetti fanno parte della "interfaccia di estensione" che utilizzerai per aggiungere funzionalità al di sopra delle classi esistenti senza modificare le stesse classi esistenti.

Quindi, breve riassunto:

  • public: utilizza in sicurezza le istanze della classe, lo scopo della classe non cambierà.
  • protected: da utilizzare quando si estende (derivando dalla) classe - può cambiare se l'implementazione deve cambiare drasticamente.
  • privato: non toccare! Può cambiare a piacimento per fornire una migliore implementazione delle interfacce previste.
risposta data 05.08.2011 - 15:30
fonte
11

Se stai scrivendo il codice che verrà utilizzato da qualcun altro, l'occultamento delle informazioni può fornire un'interfaccia più semplice da comprendere. "Qualcun altro" potrebbe essere un altro sviluppatore del tuo team, gli sviluppatori che consumano un'API che hai scritto in commercio, o persino il tuo sé futuro che "semplicemente non riesce a ricordare come funziona la cosa dang". È molto più facile lavorare con una classe che ha solo 4 metodi disponibili rispetto a una che ha 40.

    
risposta data 05.08.2011 - 15:37
fonte
4

Suggerirei che per lo sfondo inizi a leggere su invarianti di classe .

Un invariant è, per farla breve, un assunto sullo stato di una classe che dovrebbe rimanere vera per tutta la vita di una classe.

Usiamo un esempio C # molto semplice:

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
}

Che sta succedendo qui?

  • Il membro addresses viene inizializzato sulla costruzione della classe.
  • È privato, quindi nulla dall'esterno può toccarlo.
  • Abbiamo anche reso readonly , quindi niente dal inside può toccarlo dopo la costruzione (questo non è sempre corretto / necessario, ma è utile qui).
  • Pertanto, il metodo Send può dare per assunto che addresses non sarà mai null . Non è necessario eseguire tale controllo perché non c'è nessun modo che il valore possa essere modificato.

Se altre classi potevano scrivere nel campo addresses (cioè se fosse public ), allora questa ipotesi non sarebbe più valida. Ogni singolo altro metodo nella classe che dipende da quel campo dovrebbe iniziare a fare controlli nulli espliciti , o rischiare di mandare in crash il programma.

Quindi sì, è molto più di una "convenzione"; tutti i modificatori di accesso sui membri della classe costituiscono collettivamente una serie di ipotesi su quando e come tale stato può essere modificato. Tali presupposti sono successivamente incorporati in membri e classi dipendenti e interdipendenti, in modo che i programmatori non debbano ragionare sull'intero stato del programma allo stesso tempo. La capacità di formulare ipotesi è un elemento critico della gestione della complessità nel software.

A queste altre domande:

Can it be used to secure a class' secret state from being attacked?

Sì e no. Il codice di sicurezza, come la maggior parte del codice, si baserà su alcuni invarianti. I modificatori di accesso sono certamente utili come indicazioni per i chiamanti fidati che non dovrebbero fare confusione con esso. Il codice Malevolo non si preoccuperà, ma il codice dannoso non deve passare attraverso il compilatore.

Can reflection override this mechanism?

Certo che può. Ma la riflessione richiede che il codice chiamante abbia quel livello di privilegio / fiducia. Se stai utilizzando codice dannoso con piena fiducia e / o privilegi amministrativi, hai già perso quella battaglia.

What would make it worthwhile for the compiler to enforce information hiding?

Il compilatore già lo lo applica. Così fa il runtime in .NET, Java e altri ambienti di questo tipo: l'opcode utilizzato per chiamare un metodo non avrà esito positivo se il metodo è privato. L'unico modo per aggirare tale restrizione richiede un codice affidabile / elevato e il codice elevato potrebbe sempre scrivere direttamente nella memoria del programma. Viene applicato tanto quanto può essere applicato senza richiedere un sistema operativo personalizzato.

    
risposta data 05.08.2011 - 16:37
fonte
3

L'hiding delle informazioni è molto più di una semplice convenzione; tentare di aggirarlo può in realtà infrangere la funzionalità della classe in molti casi. Ad esempio, è abbastanza comune memorizzare un valore in una variabile private , esporlo usando una proprietà protected o public della stessa ora, e nel getter, verificare null e fare qualsiasi inizializzazione necessaria ( cioè, caricamento pigro). Oppure archivia qualcosa in una variabile private , esponila usando una proprietà e, nel setter, controlla se il valore è cambiato e gli eventi PropertyChanging / PropertyChanged fuoco. Ma senza vedere l'implementazione interna, non sapresti mai tutto quello che succede dietro le quinte.

    
risposta data 05.08.2011 - 16:15
fonte
3

L'occultamento delle informazioni si è evoluto da una filosofia di progettazione top-down. Python è stato chiamato una lingua bottom-up .

Le informazioni nascoste sono applicate bene a livello di classe in Java, C ++ e C #, quindi non è davvero una convenzione a questo livello. È molto facile rendere una classe una "scatola nera", con interfacce pubbliche e dettagli nascosti (privati).

Come hai sottolineato, in Python spetta ai programmatori seguire la convenzione di non utilizzare ciò che è inteso essere nascosto, poiché tutto è visibile.

Anche con Java, C ++ o C #, a un certo punto le informazioni che nascondono diventano una convenzione. Non ci sono controlli di accesso ai più alti livelli di astrazione coinvolti in architetture software più complesse. Ad esempio, in Java puoi trovare l'uso dei ".internal." nome del pacchetto. Questa è puramente una convenzione di denominazione perché non è facile per questo tipo di informazioni nascoste da essere applicate tramite l'accessibilità del pacchetto da solo.

Una lingua che si sforza di definire formalmente l'accesso è Eiffel. Questo articolo indica alcune altre debolezze relative alle informazioni di lingua come Java.

Sfondo: l'occultamento delle informazioni era proposto nel 1971 da David Parnas . Egli sottolinea in quell'articolo che l'uso di informazioni su altri moduli può "aumentare in modo disastroso la connettività della struttura del sistema". Secondo questa idea, la mancanza di informazioni nascoste può portare a sistemi strettamente accoppiati che sono difficili da mantenere. Continua con:

We wish to have the structure of the system determined by the designers explicitly before programming begins, rather than inadvertently by a programmer's use of information.

    
risposta data 26.08.2012 - 20:42
fonte
2

Ruby e PHP lo hanno e lo applicano in fase di runtime.

Il punto di informazioni nascosto è in realtà la visualizzazione di informazioni. "Nascondendo" i dettagli interni, lo scopo diventa evidente da una prospettiva esterna. Ci sono lingue, che abbracciano questo. In Java, l'accesso predefinito per il pacchetto interno, in haXe per protetto. Dichiarali esplicitamente pubblici per esporli.

Il punto essenziale di questo è rendere le tue lezioni facili da usare esponendo solo un'interfaccia altamente coerente. Vuoi che il resto sia protetto, in modo che nessun ragazzo intelligente arrivi e incasini il tuo stato interiore per indurre la tua classe a fare quello che vuole.

Inoltre, quando i modificatori di accesso vengono applicati in fase di runtime, possono utilizzarli per applicare un determinato livello di sicurezza, ma non penso che questa sia una soluzione particolarmente valida.

    
risposta data 05.08.2011 - 15:56
fonte
2

I modificatori di accesso possono sicuramente fare qualcosa che la convenzione non può.

Ad esempio, in Java non è possibile accedere a membri / campi privati a meno che non si usi reflection.

Quindi se scrivo l'interfaccia per un plugin e negato correttamente i diritti per modificare i campi privati tramite reflection (e per impostare security manager :)), posso inviare qualche oggetto a funzioni implementate da chiunque e sapere che non può accedere ai suoi privati campi.

Naturalmente ci possono essere alcuni bug di sicurezza che gli permettono di superare questo, ma questo non è filosoficamente importante (ma in pratica lo è sicuramente).

Se l'utente esegue la mia interfaccia nel proprio ambiente, ha il controllo e quindi può eludere i modificatori di accesso.

    
risposta data 05.08.2011 - 16:44
fonte
2

I have not been in a situation where a compiler error caused by these keywords would have saved me from a bug.

Non è tanto necessario salvare gli autori delle applicazioni dagli errori, ma lasciare che gli autori delle librerie decidano quali parti della loro implementazione si stanno impegnando a mantenere.

Se ho una libreria

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

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

Potrei voler sostituire l'implementazione di foo e possibilmente cambiare fooHelper in modi radicali. Se un gruppo di persone ha deciso di utilizzare fooHelper nonostante tutti gli avvisi nella mia documentazione, potrei non riuscire a farlo.

private consente agli autori di librerie di rompere le librerie in metodi di dimensioni gestibili (e classi di helper private ) senza il timore che saranno costretti a mantenere quei dettagli interni per anni.

What would make it worthwhile for the compiler to enforce information hiding?

Una nota a margine, in Java private non viene applicata dal compilatore, ma da Java bytecode verifier .

Can reflection override this mechanism? What would make it worthwhile for the compiler to enforce information hiding?

In Java, non solo la riflessione può ignorare questo meccanismo. Esistono due tipi di private in Java. Il tipo di private che impedisce ad una classe esterna di accedere ai membri private di un'altra classe esterna che è controllata dal verificatore bytecode, ma anche private s che vengono utilizzati da una classe interna tramite un metodo accessor sintetico pacchetto-privato come in

 public class C {
   private int i = 42;

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

Poiché la classe B (in realtà chiamata C$B ) utilizza i , il compilatore crea un metodo di accesso sintetico che consente a B di accedere a C.i in modo da superare il verificatore bytecode. Sfortunatamente, dal momento che ClassLoader ti permette di creare una classe da un byte[] , è abbastanza semplice arrivare ai privati che C ha esposto a classi interne creando una nuova classe nel pacchetto di C che è possibile se il barattolo di C non è stato sigillato.

Un'appropriata implementazione private richiede il coordinamento tra i classloader, il verificatore di codice byte e il criterio di sicurezza che può impedire l'accesso riflessivo ai dati privati.

Can it be used to secure a class' secret state from being attacked?

Sì. "La scomposizione sicura" è possibile quando i programmatori possono collaborare mantenendo ciascuno le proprietà di sicurezza dei loro moduli - Non devo fidarmi dell'autore di un altro modulo di codice per non violare le proprietà di sicurezza del mio modulo.

Lingue delle capacità degli oggetti come Joe-E usa il nascondiglio delle informazioni e altri mezzi per rendere possibile la decomposizione sicura:

Joe-E is a subset of the Java programming language designed to support secure programming according to object-capability discipline. Joe-E is intended to facilitate construction of secure systems, as well as to facilitate security reviews of systems built in Joe-E.

Il documento collegato da quella pagina fornisce un esempio di come l'imposizione di private rende possibile la decomposizione sicura.

Providing secure encapsulation.

Figure 1. An append-only logging facility.

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

Consider Fig. 1, which illustrates how one might build an append-only log facility. Provided that the rest of the program is written in Joe-E, a code reviewer can be confident that log entries can only be added, and cannot be modified or removed. This review is practical because it requires only inspection of the Log class, and does not require review of any other code. Consequently, verifying this property requires only local reasoning about the logging code.

    
risposta data 27.08.2012 - 01:16
fonte
1

L'occultamento delle informazioni è una delle principali preoccupazioni del buon design del software. Dai uno sguardo ai documenti di Dave Parnas degli ultimi anni '70. In sostanza, se non puoi garantire che lo stato interno del tuo modulo sia coerente, non puoi garantire nulla sul suo comportamento. E l'unico modo in cui puoi garantire il suo stato interno è di mantenerlo privato e permetterlo solo di cambiarlo con i tuoi mezzi.

    
risposta data 05.08.2011 - 19:38
fonte
1

Facendo della protezione una parte della lingua, ottieni qualcosa: garanzia ragionevole.

Se faccio una variabile privata, ho ragionevole sicurezza che verrà toccata solo dal codice all'interno di quella classe o da amici esplicitamente dichiarati di quella classe. L'ambito del codice che potrebbe ragionevolmente toccare quel valore è limitato e definito in modo esplicito.

Ora ci sono modi per aggirare la protezione sintattica? Assolutamente; la maggior parte delle lingue li ha In C ++, puoi sempre lanciare la classe su un altro tipo e colpire i suoi bit. In Java e C # puoi riflettere su di esso. E così via.

Tuttavia, fare questo è difficile . È ovvio che stai facendo qualcosa che non dovresti fare. Tu non puoi farlo per errore (al di fuori delle scritture wild in C ++). Devi pensare volontariamente: "Toccherò qualcosa che mi è stato detto di non fare dal mio compilatore". Devi fare volentieri qualcosa irragionevole .

Senza la protezione sintattica, un programmatore può rovinare casualmente le cose. Devi insegnare all'utente una convenzione, e devono seguire quella convenzione ogni volta . Se non lo fanno, il mondo diventa molto pericoloso.

Senza la protezione sintattica, l'onere è sulle persone sbagliate: le molte persone che usano la classe. Devono seguire la convenzione o si verificherà una cattiveria non specificata. Gratta che: potrebbe cattiveria non specificata.

Non c'è niente di peggio di un'API in cui, se fai la cosa sbagliata, tutto potrebbe funzionare comunque. Ciò fornisce false rassicurazioni all'utente che ha fatto la cosa giusta, che tutto va bene, ecc.

E i nuovi utenti di quella lingua? Non solo devono imparare e seguire la sintassi effettiva (applicata dal compilatore), ma ora devono seguire questa convenzione (applicata dai loro pari nel migliore dei casi). E se non lo fanno, allora non può succedere niente di male. Cosa succede se un programmatore non capisce perché la convenzione esiste? Cosa succede quando dice "fanculo" e si limita a dare un'occhiata ai tuoi amici? E cosa pensa se tutto continua a funzionare?

Pensa che la convenzione sia stupida. E lui non lo seguirà mai più. E dirà a tutti i suoi amici di non preoccuparsi troppo.

    
risposta data 06.08.2011 - 01:49
fonte

Leggi altre domande sui tag