The evil Static Method

Dec 17th, 2008 | By | Category: design

The keyword static possibly started as the first attempt at encapsulation. C Programs consisted of functions that spanned across multiple files. However, the programmers wanted some of these functions to be “private” within the file that they were contained in. The static keyword provided that small veneer of privacy. When java and other derived languages came in, the static was still continued. But this time it was used to signify that the variable or the method was static across instances of a class. We also started seeing a significant number of static “utility” libraries. Browse through any project and you would find “StringUtils”, “XmlUtils” and the like. Soon, static methods were mentally equated to any kind of “helper” methods. This resulted in a lot of people using this artifice in places that it should not be used.

This post talks about static methods and why IMO I consider these as evil. 

Origins and Introduction

Typically, it starts off in a small way. An entire application is coded in a class. The class has one gargantuan “main” method that accomplishes most of the functionality. Then one fine day a new person is called to maintain this application. The new person realizes that it is impossible to decipher the “main” method which by now is almost venomous! Touch any part of the method and it stings!

So the new person decides to do some re-factoring. Some functionality is taken off into multiple methods which reside within the same class. These methods are then called from the “main” method.  Now the “main” method is small but the class still is huge! So, one fine day out of sheer frustration of loading this class into the IDE, the developers decide to refactor this class now into multiple “helper” classes containing “helper” methods – which are responsible for implementing part of the functionality. Thus the application functionality is now scattered across multiple helper methods. The main method is responsible for invoking these multiple helper methods. 

But wait! Did I say that the application now has multiple classes?  Who is responsible to instantiate all these classes and call these “helper” methods. Initially, the main() method takes over this responsibility. So the main method instantiates the helper class and calls the helper method. But some brilliant guy thinks it is too much of a waste of time to do this instantiation. After all, the helper class does not typically contain any class level (aka instance) variables. So why instantiate this helper class if all that we are interested is to invoke the helper method? With this reasoning, all the helper methods suddenly become static methods of the helper class! Thus the static method is born!

The snippet of code in main() becomes:

  1. public SomeClass{
  2.  public static void main(String[] args){
  3.  // bla bla bla..
  4.   SomeHelper.helperMethod(some arguments);
  5.   // more of the same.
  6.  }
  7. }

The Customization Problem

What is the problem with this approach? Nothing much until we start considering a more concrete example. Let us try aping the Jakarta Commons utils and come up with a class like ConversionUtils. This class contains various methods to convert to and fro from any object to a string. So we might have a class called DateConversionUtils for instance. This class has two methods. String toString(Date) and Date fromString(String). The two methods accomplish the task of converting between a Date object and a String object. The string is assumed to contain the date in the form “YYYY-MM-DD” and this utility quickly converts it from that form to a date and vice versa. Now, let us use this DateConversionUtils class and its static methods in some method.

  1. public SomeClass {
  2.  public void someMethod(){
  3.    Date d = … ; // initialize the date object
  4.    String s = DateConversionUtils.toString(d);
  5.    // — somewhere else in the method.
  6.    String s1 = … ; // initialize it to a string that is formatted as YYYY-MM-DD
  7.    // get the date from the string representation of it.
  8.    Date d1 = DateConversionUtils.fromString(s1);
  9.   }
  10. }

The evident simplicity of this approach is undeniable. Hence this class DateConversionUtils will now be shipped along with the application and we start using it profusely from all parts of the application for converting strings to dates and vice versa. As we are about to ship the application, there is a new customer who asks that we use “MM/DD/YYYY” to represent the dates! Now the application must be able to handle this simple feature. Right? As it turns out, the implementation of this feature is very simply handled thereby re-affirming the validity of the approach that we took in putting the entire date conversion logic to one method.

We must alter DateConversionUtils to handle the new customer. Because, we know that the date conversion must change for that particular customer alone. Not for everyone else. So we come up with a configuration file that specifies which format of date the DateConversionUtils class must support. We state that the format supported would be either “YYYY-MM-DD” or “DD/MM/YYYY”. The ConversionUtils.fromString() and ConversionUtils.toString() methods would now need to read this configuration file and alter their behavior according to the format supported. So with one change, we are able to accommodate the new customer. Yahoo! The customization problem has been solved. But what did we do? We have started complicating our static method to start reading configuration files. But it is a small problem, we say. Sure. So let us move on.
 

The Routing problem

Now, let us say that our application must support multiple customers with one installation. (something like a SaaS – Software as a Service-  model). Now, we cannot anymore use the configuration file to dictate one behavior or the other. We need to be able to get more sophisticated. We need to determine during runtime as to what format the customer wants and accordingly branch out to the appropriate portion of the code. This routing logic now would start entering our static method. The static method must first determine who the customer is, find out which format the customer likes (YYYY-DD-MM or DD/MM/YYYY) and actually execute that part of the code to accomplish the conversion! The sample code is given below:

  1. public class DateConversionUtils {
  2.    public String toString(Date date){
  3.    Customer c = … ;// find out who the current customer is.
  4.    if (c.isYYYYMMDD() ){
  5.      // implement YYYY-MM-DD functionality.
  6.    } else if (c.isDDMMYYYY()){
  7.      // implement DD/MM/YYYY functionality.
  8.    }
  9.  }
  10. }

Notice that in the code snippet above that the customer needs to somehow be known to the static method. In practice, it is not so easy to implement this unless the method itself accepts the customer as a parameter. So the signatures of the fromString() and toString() static methods must change to accept a customer. Hmmm. this is is getting a little ugly now. What if the date format depends on other factors besides the customer?  Would we have to start accepting all these parameters into the method? I am not going to explain how we can solve this problem right now. Because, this is symptomatic of a more generic problem that is not just restricted to static methods and hence would warrant a needless digression. So for now let us somehow assume that the static method knows about who the customer is for the “current” request.

This kind of behavior is not restricted to just date formats. Different customers may prefer different date formats, number formats, address formats, social security number formats etc. Pretty soon, we would find multiple ConversionUtils classes and their methods implementing this kind of customer specific branches to accomplish various kinds of conversions. 

Hence an application containing a lot of static methods tends to contain all kinds of routing logic that can potentially be replicated across the static methods. This mushrooming becomes interesting when re-use is considered.  

The Re-use problem

The method above is not re-usable since it contains logic that is very specific to that particular application. (It is finding out customer information which need not be part of other applications) Hence a method like this tends to get replicated multiple times in different applications each with a slightly different variant of the routing logic. That is quite clearly not very amenable for re-use. But this problem still is not specific to static methods. It can manifest itself even for instance methods. It can be solved by fronting the actual date conversion utils with another static method that is only responsible for routing. So every conversion utility will be fronted by a routing static method! 

 

The Inheritance Problem

Let us say that we need to implement a special version of DateConversionUtils that is catering to UTF-16 locales. Now it would seem logical that we should inherit the basic version of the DateConversionUtils and develop on it to cater to the nuances of UTF-16. But wait! We cannot inherit a static method! Hence static methods cannot be “improved upon”. Once we come up with a static method we have said the last word on the implementation! But of course my retractors would say that you can always “call” a static method from another. But that is still not as good  because polymorphism is not possible without inheritance. I cannot for instance use the template method pattern for accomplishing my task. (I would write another post sometime on the template method pattern)

The Instantiation Problem – Testing, Debugging and Deployment 

I want to illustrate the instantiation problem with Log4J.

Log4J implements logging for the application. The actual logging is accomplished by calling a static method in the Log4J library.  Let us say we are writing a web application that uses Log4J. Then we deploy the application in the tomcat application server. Since tomcat also uses Log4J and the method is static, both tomcat and our applications would end up using exactly the same static method!. We will soon observe that all our logs would start going into the tomcat logs. But what can be worse is if tomcat logs start entering our local application logs! To avoid this, the tomcat server maintains a complex class loader hierarchy to insulate itself from the application loaded version of Log4J. While it is acceptable for an application server to come up with complex class loader hierarchies, this strategy is hardly feasible in a normal simple application. It becomes very hard to maintain different “versions” of the same class without resorting to all kinds of class loading semantics. This indeed, is the biggest problem with Log4J. This problem is directly related to Log4J’s rampant use of static methods for logging. 

The fundamental notion behind a static method is that it gets loaded when the class that contains it is loaded. The class itself is loaded by the class loader. Hence the static method is loaded at the time when the class loader kicks in. Hence, if there are multiple class loaders in the ecosystem, it is possible to have multiple “versions” of the static method residing at the same time in the application. Debugging an application that has multiple echelons which in turn rely on “different” versions of the same static method requires a very thorough knowledge of the class loader hierarchy. The testing becomes complicated as well and so does deployment. 

Dependency OverLoad Problem

Let us say that the DateConversionUtils class needs two more classes to implement its functionality. One class which I am going to call “YYYYMMDDHelper” and another class “MMDDYYYYHelper”. These two helper classes implement the complete logic to accomplish the corresponding conversion. Now, let us say that a caller class wants to use the ConversionUtils class to convert all strings of the form YYYYMMDD to Date objects. Since the class is calling ConversionUtils.fromString() and ConversionUtils.toString() methods, it has a hard dependency on the ConversionUtils class. Since the ConversionUtils class is in turn dependent on the YYYYMMDDHelper and the MMDDYYYYHelper classes, the caller would now have dependency on them as well.  (Hard Dependencies are transitive) But logically speaking the caller class is only interested in converting strings of the form YYYYMMDD to Date object. Hence it should not have a dependency on the MMDDYYYYHelper class! 

Hence static methods usage tends to promote a chain of dependencies that are hard-wired into the system. Classes become dependent on other classes that they should not normally depend on. To be fair, this problem can occur in non static methods as well. But it is a lot easier to solve in an instance method rather than in a static method. A more detailed discussion on this would have to wait for another post. 

How to avoid static methods?

Static methods can be eliminated for the most part by the use of the Strategy pattern supplemented by Interface based design. In this case, the DateConversionUtils static class becomes a DateConverter interface which defines the two methods. There would be two implementations 1. The YYYYMMDDDateConverter and 2. the MMDDYYYY date converter. Each of them can be independently used by different parts of the application. Let us see how this solves most of the ills of the static methods that we talked about :

  • Customization problem: Just use two different implementations of the strategy and use the correct strategy for the correct customer. i.e. for customers who like YYYYMMDD format, use the YYYYMMDDDateConverter. 
  • Routing Problem: Put all the routing logic in a router class. Let the router class implement DateConverter interface and deflect the control to the correct implementation according to the customer.  See the Router pattern in this blog for more details. 
  • Inheritance: Strategies can be inherited since they are normal instance methods in a class.
  • Re-use: Strategies can be re-used since the pesky routing logic is now removed from the DateConverter class and re-factored into a separate Router.
  • Instantiation: Different strategies can now be instantiated. We do not need to rely on class loaders to implement this.
  • Hard Dependency: Since the caller class is only using one strategy, it will only depend on the classes that it needs rather than inheriting an unnecessary chain of dependencies.

So When do I use statics?

So are statics just banned from applications? I would say that they should be significantly reduced. Statics should not be confused with singletons just because they both operate on the same principle of one instance per application. (this is not entirely true since there would be one static method per class loader) Static methods must only be used when we are absolutely sure that there cannot be two implementations of the same method. In reality, most methods admit to multiple implementations and hence would get disqualified if this principle is applied. 

The only methods that can get by with the simplicity offered by statics are the ones that implement what I would call “Universal truths”. For instance, methods in MathUtils that compute the cosine, sine or tan trigonometric functions. Or the static variable that contains the value of ? (pi) . 

Violation of Object Orientation

Static methods violate object oriented design.  These are just  functions which have just been organized into classes. A class containing static methods just serves as a container for loose amorphous, posibly related methods. We don’t need object oriented principles to design such a class. It does not exhibit any of the familiar OO traits such as encapsulation, inheritance,polymorphism etc. Hence static methods must be used with caution.

Be Sociable, Share!

 Raja has been blogging in this forum since 2006. He loves a technical discussion any day. He finds life incomplete without a handy whiteboard and a marker.


Tags: ,

2 comments
Leave a comment »

  1. [...] Invocation: The static dependency would warrant its own approach which is documented in the evil static method post within this [...]

  2. Nice article..

Leave Comment


6 − = one