Quali sono gli svantaggi dell'autocapsulazione?

3

Sfondo

L'errore miliardi di dollari di Tony Hoare è stato l'invenzione di %codice%. Successivamente, un sacco di codice è diventato pieno di eccezioni del puntatore nullo (segfaults) quando gli sviluppatori software tentano di utilizzare (dereferenziamento) le variabili non inizializzate.

Nel 1989, Wirfs-Brock e Wikerson hanno scritto:

Direct references to variables severely limit the ability of programmers to refine existing classes. The programming conventions described here structure the use of variables to promote reusable designs. We encourage users of all object-oriented languages to follow these conventions. Additionally, we strongly urge designers of object-oriented languages to consider the effects of unrestricted variable references on reusability.

Problema

Molti software, specialmente in Java, ma probabilmente in C # e C ++, spesso usano il seguente schema:

public class SomeClass {
  private String someAttribute;

  public SomeClass() {
    this.someAttribute = "Some Value";
  }

  public void someMethod() {
    if( this.someAttribute.equals( "Some Value" ) ) {
      // do something...
    }
  }

  public void setAttribute( String s ) {
    this.someAttribute = s;
  }

  public String getAttribute() {
    return this.someAttribute;
  }
}

A volte viene utilizzata una soluzione di cerotto controllando la presenza di null in tutto il codebase:

  public void someMethod() {
    assert this.someAttribute != null;

    if( this.someAttribute.equals( "Some Value" ) ) {
      // do something...
    }
  }

  public void anotherMethod() {
    assert this.someAttribute != null;

    if( this.someAttribute.equals( "Some Default Value" ) ) {
      // do something...
    }
  }

Il cerotto non sempre evita il problema del puntatore nullo: esiste una condizione di competizione. La condizione della competizione viene mitigata usando:

  public void anotherMethod() {
    String someAttribute = this.someAttribute;
    assert someAttribute != null;

    if( someAttribute.equals( "Some Default Value" ) ) {
      // do something...
    }
  }

Tuttavia questo richiede due istruzioni (assegnazione alla copia locale e controllo di null ) ogni volta che viene utilizzata una variabile con scope class per assicurarsi che sia valida.

Auto-incapsulamento

La riusabilità attraverso l'autoincapsulamento di Ken Auer (Pattern Languages of Program Design, Addison Wesley, New York, pp. 505-516, 1994) propugnava l'autoincapsulamento combinato con un'inizializzazione pigra. Il risultato, in Java, sarebbe simile a:

public class SomeClass {
  private String someAttribute;

  public SomeClass() {
    setAttribute( "Some Value" );
  }

  public void someMethod() {
    if( getAttribute().equals( "Some Value" ) ) {
      // do something...
    }
  }

  public void setAttribute( String s ) {
    this.someAttribute = s;
  }

  public String getAttribute() {
    String someAttribute = this.someAttribute;

    if( someAttribute == null ) {
      someAttribute = createDefaultValue();
      setAttribute( someAttribute );
    }

    return someAttribute;
  }

  protected String createDefaultValue() { return "Some Default Value"; }
}

Tutti i controlli duplicati per null sono superflui: null garantisce che il valore non sia mai getAttribute() in una singola posizione all'interno della classe contenente.

Gli argomenti di efficienza dovrebbero essere abbastanza discutibili: i compilatori e le macchine virtuali moderni possono allineare il codice quando possibile.

Fintanto che le variabili non vengono mai referenziate direttamente, ciò consente anche una corretta applicazione del principio Open-Closed .

Domanda

Quali sono gli svantaggi dell'autocapsulazione, se ce ne sono?

(Idealmente, mi piacerebbe vedere riferimenti a studi che contrastano la robustezza di sistemi altrettanto complessi che usano e non usano l'autoincapsulamento, poiché questo mi sembra un'ipotesi testabile abbastanza semplice.)

    
posta Dave Jarvis 12.11.2013 - 20:13
fonte

2 risposte

8

Gli svantaggi sono l'inefficienza dell'extradection, come hai sottolineato, e il fatto che il compilatore non lo impone. Tutto ciò che serve è il peggior programmatore che utilizza un riferimento non codificato per distruggere i benefici.

Inoltre, il modo corretto per risolvere un problema con un puntatore nullo non è quello di sostituirlo con un valore predefinito non nullo con essenzialmente le stesse caratteristiche. Il problema con le dereferenze del puntatore nullo non è che causino un segfault. Questo è solo un sintomo. Il problema è che il programmatore potrebbe non gestire sempre un valore predefinito / non inizializzato imprevisto. Questo problema deve ancora essere gestito separatamente con il tuo modello di autoincapsulamento.

Il modo giusto per risolvere un problema con un puntatore nullo è non creare l'oggetto fino a quando un valore non null valido semanticamente può essere inserito nell'attributo e distruggere l'oggetto prima che sia necessario impostare uno qualsiasi dei suoi attributi su null . Se non c'è mai la possibilità che un puntatore sia nullo, non è mai necessario controllarlo.

Di solito quando le persone pensano che un attributo debba essere nullo, stanno cercando di fare troppo in una classe. Spesso rende il codice molto più pulito per dividerlo in due classi. È anche possibile dividere le funzioni per evitare assegnazioni nulle. Ecco un esempio tratto da un'altra domanda in cui ho rifatto una funzione per evitare un'assegnazione nulla problematica.

    
risposta data 12.11.2013 - 21:12
fonte
0

Ci sono momenti in cui sarebbe utile essere in grado di definire un tipo di dati che terrebbe un riferimento a un oggetto immutabile, ma si comporterebbe come un oggetto immutabile, piuttosto che un riferimento, come quel codice scritto come:

thing.foo(bar);

si compilerebbe come una chiamata a un metodo statico:

classOfThing.do_foo(thing, bar);

in cui il metodo statico potrebbe quindi gestire il caso in cui il primo argomento è null in qualsiasi modo lo ritenga opportuno. Un sacco di codice per la gestione delle stringhe in Java avrebbe potuto essere più pulito se String fosse un tipo simile; una variabile non inizializzata di tipo string potrebbe quindi comportarsi come una stringa vuota anziché come riferimento null. Le conversioni tra tali tipi e Object potrebbero essere un po 'complicate [ognuno di questi tipi potrebbe forse definire un oggetto singleton per rappresentare il valore predefinito per le istanze del suo tipo, quindi convertire string con valore predefinito a digitare Object o String produrrebbe un riferimento a String.defaultInstance , la conversione di un riferimento nullo in String darebbe un NPE] ma tali tipi potrebbero aver reso alcune cose molto più pulite.

    
risposta data 11.07.2015 - 18:40
fonte

Leggi altre domande sui tag