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
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.)