public static is harmful. It has no home in modern programming.

In the modern era of software development, "public static" is a relic of a past age. It reflects a bygone era where the concepts of composition and dependency injection were not yet well understood. In the modern world of software, where we understand both of these concepts relatively well - it's important to know that public static has no future. Let's see why:

1. public static forces static coupling, breaks composition and dependency injection

This one is fairly obvious - it's in the name. A call to a public static function typically looks like:

void myCallingFunction() {
  MyUtils.publicStaticFunction(args);
}

As we can see clearly, the call to the public static function is happening with a direct reference through the containing class. The calling function is tied down to using only the implementation in MyUtils. The caller may not choose another implementation even if the caller may be calling the code in a test environment (brittle composition). This "hidden composition" breaks dependency injection and testability.

2. public static produces "virulent" recursive refactors

Assuming that there are stateless "utility" classes containing public static methods: if these public static methods become typical OOP methods (non-static) [and there are good reasons to make these changes], it will force a recursive-refactor of ALL call sites of the public static methods within the codebase, causing painful cascading changes. This one is really nasty and a bit tricky to see - here's an example:

Old code:

class ExceptionReporter {
  
// Public Static method; Hardcoded urls & libs.
  public static void logException() {
    HttpClient.call("http://exceptions.example.com/report");
  }
}

class MyUtils {
  public static void myWidelyUsedUtilsFunction() {
    ExceptionReporter.logException();  // Brittle dependency.
  }
}

Problems:

The ExceptionReporter will always make the network call (even if it's running within a test environment). The URL is non controllable, the Http client library is non controllable, the protocol is non controllable.

Let's say we decide to make this code test friendly and we parameterize ExceptionReporter:

Refactor to:

class ExceptionReporter {
  // Parameterized constructor.
  public ExceptionReporter(URI uri, HttpClient httpClient) { ... }
  public void logException() { ... }  // Use member vars.
}


The call sites are all badly impacted. We can either:
1. Pass through the ExceptionReporter as a function argument
  (Con: the URLs and HTTP Clients are strewn in 100s of call sites -- a large number of identical ExceptionReporter objects get created for little value, the copy-paste is tech-debt).  OR
2. Convert MyUtils to contain a member variable.
  (Con:  MyUtils cascades through function arguments and constructor changes to clients. If classes are constructing MyUtils objects, they have to pass through constructor dependencies, forcing recursive refactoring. Additionally, the stateless method call becomes a stateful class + object dispatch, this may also affect thread safety guarantees). To put it mildly, this is a difficult change.

To see this clearly, see these options in code:

Option 1: Pass through ExceptionReporter as function arguments.

// All call sites need a new function parameter passed in.
class MyUtils {
  public static void myWidelyUsedUtilsFunction(ExceptionReporter e) { ... }
  public static void myWidelyUsedOtherFunction(ExceptionReporter e) { ... }
}

// The constructor arguments keep getting copy-pasted
public static void myCallSite1() {
  ExceptionReporter e = new ExceptionReporter(
    "http://exceptions.example.com/report",
    new HttpClient());  // Copy-paste everywhere.

  MyUtils.myWidelyUsedUtilsFunction(e);
  MyUtils.myWidelyUsedOtherFunction(e);
} 

Option 2: Convert all the public static call sites to proper OOP function calls.

class MyUtils {
  // Convert to "proper" class + object.
  public MyUtils(ExceptionReporter e) { ... }; 
  ... 
}

// All call sites create 2 objects!
public static void myCallSite1() {
  // The number of objects created may be large here.
  // Depends on constructor parameters.
  ExceptionReporter e = new ExceptionReporter(
    "http://exceptions.example.com/report", new HttpClient());
  MyUtils myUtils = new MyUtils(e);

  myUtils.myWidelyUsedUtilsFunction();
  myUtils.myWidelyUsedOuterFunction();
}

This cascading process of adding constructors, member variables, object creation and function arguments is a long, painful "virulent" recursive refactor. The compile errors while doing this may seem like a virus spreading outwards from the site of the original public static methods to all their call sites and all their further call sites and all enclosing objects and methods. If this is a widely used library, several days of intensive, laborious work will need to be done. I've been there and done it - it's not enjoyable.

3. Every public static method can trivially be replaced with a non-static implementation at the cost of a single stack pointer bump.

The only difference is the call site:

Before: 
public void callSite() {
  MyStaticClass.myUtilsFunction();  // no object.
}


After:
// testable, dependency injected code.
public void callSite(MyNonStaticClass dependency) {
  dependency.myNonStaticUtilsFunction();  // requires object alloc.
}


The JVM has a nifty feature called Escape Analysis, which will determine whether an object is retained on the stack or placed on the heap. If an object never escapes a function or a set of functions (through a data-flow trace), the JVM will avoid allocating it on heap (instead, it's space will be allocated through an %(esp) stack pointer bump). As soon as the function returns, the %(esp) register reset automatically deallocates the object (no GC needed!). In practice, this means that the empty object allocation costs (approximately) 1 add instruction (0.3 nanoseconds) only! In my view, this is a very cheap price to pay.

To Conclude

For the price of a single add instruction and (often) zero impact to GC, we can:
a) Improve testability and dependency injection, make the code cleaner.
b) Encourage well encapsulated, well parameterized codebases and good coding practices. 
c) Completely remove the possibility of "virulent" refactors.

The result is clear - "public static" should be eliminated. Please avoid it your code and discourage its use in code reviews. If there's a debate, please refer them to this post. Happy hunting! 

Comments

Popular posts from this blog

Coke Studio: Madari English Meaning and Lyrics

AJAX और हिंदी

Sadi Gali - Punjabi Lyrics and Meaning (in English) - Tanu Weds Manu

Tune Meri Jaana Kabhi Nahin Jaana - Lonely (Emptiness) - IIT Guwahati - Rohan Rathore