Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Background

Tony Hoare's billion dollar mistake was the invention of null. Subsequently, a lot of code has become riddled with null pointer exceptions (segfaults) when software developers try to use (dereference) uninitialized variables.

In 1989, Wirfs-Brock and Wikerson wrote:

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.

Problem

A lot of software, especially in Java, but likely in C# and C++, often uses the following pattern:

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;
  }
}

Sometimes a band-aid solution is used by checking for null throughout the code base:

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

The band-aid does not always avoid the null pointer problem: a race condition exists. The race condition is mitigated using:

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

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

Yet that requires two statements (assignment to local copy and check for null) every time a class-scoped variable is used to ensure it is valid.

Self-Encapsulation

Ken Auer's Reusability Through Self-Encapsulation (Pattern Languages of Program Design, Addison Wesley, New York, pp. 505-516, 1994) advocated self-encapsulation combined with lazy initialization. The result, in Java, would resemble:

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"; }
}

All duplicate checks for null are superfluous: getAttribute() ensures the value is never null at a single location within the containing class.

Efficiency arguments should be fairly moot -- modern compilers and virtual machines can inline the code when possible.

As long as variables are never referenced directly, this also allows for proper application of the Open-Closed Principle.

Question

What are the disadvantages of self-encapsulation, if any?

(Ideally, I would like to see references to studies that contrast the robustness of similarly complex systems that use and don't use self-encapsulation, as this strikes me as a fairly straightforward testable hypothesis.)

share|improve this question
    
If I'm reading this correctly, you're asserting that nulls should be considered harmful and never used? Would self-encapsulation prevent the perceived need to test for if getAttribute() == defaultValue? –  Dan Pichelman Nov 12 '13 at 19:41
    
The hypothesis is that using class-scoped variables directly results in code that crashes more often than using variables indirectly (and consistently) via accessor methods that force all uninitialized variables to be initialized immediately prior to use. In the example shown, calling setAttribute( null ) will cause the attribute's value to be re-initialized prior to use (e.g., makes a database call to find the current value). –  Dave Jarvis Nov 12 '13 at 21:35
    
@BryanChen: Thank you. Fixed. –  Dave Jarvis Nov 14 '13 at 18:05
add comment

1 Answer 1

up vote 6 down vote accepted

The disadvantages are the inefficiency of the extra indirection, as you pointed out, and the fact that the compiler doesn't enforce it. All it takes is your worst programmer using one unencapsulated reference to destroy the benefits.

Also, the right way to solve a null pointer problem isn't to replace it with a non-null default value with essentially the same characteristics. The problem with null pointer dereferences isn't that they cause a segfault. That's just a symptom. The problem is that the programmer might not always handle an unexpected default/uninitialized value. That problem still must be handled separately with your self-encapsulation pattern.

The right way to solve a null pointer problem is to not create the object until a semantically valid non-null value can be put into the attribute, and to destroy the object before it is necessary to set any of its attributes to null. If there is never the possibility for a pointer to be null, there is never a need to check it.

Usually when people think an attribute must be null, they are trying to do too much in one class. It often makes the code much cleaner to split it into two classes. You can also split functions to avoid null assignments. Here's an example from another question where I refactored a function to avoid a problematic null assignment.

share|improve this answer
    
+1. I neglected to mention that the createDefaultValue() method is used, lazily, to provide a valid non-null value -- this method abstracts how the value is initialized while allowing subclasses to override how the initial value is obtained. This provides reusability through the Open-Closed Principle. –  Dave Jarvis Nov 12 '13 at 21:19
    
While developers must handle unexpected values, eliminating null pointer dereferences would help produce crash-free software (other flaws would still be present). You mention needing to handle uninitialized values, but self-encapsulation ensures all values are initialized (i.e., never null), which means that no extra code is required to handle the null (i.e., uninitialized) case. –  Dave Jarvis Nov 12 '13 at 21:38
1  
In the case of data not being initialized properly, I'd much rather have a crash at the time of the bad data being encountered rather than silently using an incorrect value. The incorrect value will manifest itself later as a problem that's located away from the original source. This makes things much harder to debug. –  17 of 26 Nov 12 '13 at 21:52
    
I disagree about making issues harder to debug. By consistently applying self-encapsulation, it makes the entire system easier to debug. Add a stack trace in the accessor to show exactly when the unexpected value was set and how. Combined with Aspect Oriented Programming, debugging the system becomes trivial. I cannot imagine a manager saying, "The system should crash for our users." –  Dave Jarvis Nov 12 '13 at 22:02
1  
A crash that a developer can't ignore is better than a subtle data corruption issue he doesn't notice. –  Karl Bielefeldt Nov 13 '13 at 1:26
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.