Friday 11 January 2013

SOLID Understanding

I am actually personally very surprised as to why SOLID principles get explicitly called out for as part of interview processes for both development and architecture roles as if it is a benchmark for capability. What interests me is why this is anything more than the absolute basics of software development. Indeed, I don't even have a prominent place for it in my CV. However, often being both sides of the interview table, I can definitely attest to the difficulty the typical industry candidate at any level has with robust OO principles.

In my anecdotal experience, 85% of developers do not apply SOLID principles correctly, often preferring to  look at shiny toys and tech from other organisations before they get their own house in order. This can often lead to badly used frameworks, poor NFRs etc. However, in my book, this should be so basic it shouldn't even be mentioned on a CV. To an OO programmer, this should be akin to typing. The industry assumes that you work with computers so can type. If you sit in front of me at interview and claim to be an OOP/AD expert, I am going to assume you know the SOLID principles. Indeed, when I test you on your design patterns and your evaluation of what patterns to use when and what anti-patterns you know of or can find, if you don't know SOLID principles it will be very evident. Given 85% of software developers can't do the top end roles, I will be looking for any excuse not to employ you but also, I will be pushing to see how far you can go if you do know them. Hey, it's the way I do things.

So What's SOLID About SOLID Principles?...

Absolutely nothing in my book! SOLID principles have been around for a lot longer than Uncle Bob's coined acronym. I took my leap into the OO style of development in the days of OWL and MFC (the Object Windows Library). In those days we were more concerned with doing the role than shouting about doing the role and we were generally required to be more capable in areas not directly associated with programming. Such as memory management, optimisation etc. which modern developers either don't need to worry about due to abstracting  this away using .NET or Java VMs and dynamic languages or have a different focus on. So to 'know OO' was passé. We would say "So what?"

SOLID stands for a set of 5 principles of good OO-design. They generally follow good system thinking practises anyway. They are:

Single Responsibility

Any class should not have more than one responsibility for one context. For example, a shopping basket class (ShoppingBasket) stores items to purchase from a store. In itself, the shopping basket doesn't do the calculation of the subtotal of the items within it.

You can spot SRP violations by looking for code-smells. For example, the name ShoppingBasketInvoice makes you ask the question, what is it? Is it shopping? A basket? or an invoice? If you see this happen, refactor it!

This principle also applies to the methods within the class. Methods such as:


        public string SumAndDisplaySubtotal()
        {
            //...
        }


The above should first be refactored into Sum() and then Display(decimal value) but this then brings the containing class into focus and makes you ask "Why is this class summing and displaying the information?"

If working code first, you can note they don't belong in the same place and so would extract out the Sum() method into an TillAccumulator class, say, and the Display() into a TillScreen class. Then you can ask the question "Why do I have 'TillAccumulator' class and 'TillScreen' classes?" and so extract out Display and Screen classes into objects which the Till is then composed of.

If working design and contract-first/DbC and think about how the world of your shopping works in real-life.  The Till would Sum the shopping basket items using an accumulating total in an Accumulator and displays this on a Screen. So you can get there that way too.

All these give the benefit that if you need to change something, you can do it in one place, in one context.

Open-Closed 

Classes should be open for extension but closed for modification. This is why we have encapsulation in OO languages. There should be no direct access to the fields within the class and good use of polymorphism and virtual/abstract classes to get what you want out of it (leaving out the discussion of inheritance versus composition, as if devs can't get SOLID, that discussion won't make sense). A non-exhaustive list of ways to make this happen include:

  • Use abstract classes to define the public and protected methods you wish to allow to extensions to.
  • Make everything else private.
  • Definitely don't make all your methods public if you are only ever going to use it internally or in derived classes.

The litmus test is how many classes have to change if you make one change to your programme. This is also the same litmus test you can use for discovering coupling, as the higher the coupling the greater the issue.

Liskov Substitution

In any design, a class should be able to be substitutable for its base class or any derived class from that base class (including derivations of its children ad infinitum). This is a simple one, as it just involves using abstract or base classes (or indeed interfaces) to allow any derivation/implementation of a class/interface to be put in the place of the original abstract entity.

This makes sense mainly because otherwise you violate open-closed (as you have to modify classes inside other classes if you have not injected it) and also cause a lot of work, as you are dealing with very specific cases all the time, exploding the amount of coupling involved. Learning to use encapsulation, inheritance, composition, abstraction and polymorphic techniques will save you a lot of code and time in the long run.

Interface Segregation

This is an interesting one for me. Interfaces are a bit weird. Those without any understanding of interfaces never use them. So this principle never gets applied to interfaces (but can equally well be applied to pure abstract classes in languages that support multiple inheritance). Those who understand interfaces and why Fowler's definition of 'header interfaces' are a bad thing are already most likely using this principle but the ones to watch the most are those who do know what interfaces are and use them, but do not understand header interfaces! They will use interfaces as if they are just abstract classes, which is a huge mistake! Definitely a case of a little knowledge is dangerous.

Unfortunately, the way developers use dependency injection and mocking frameworks tends to encourage (if not forces) them to create these header interfaces and the results can be pointless at best or catastrophic at worst.

Interface segregation aims to classify a class' functionality into role specific 'contract structures' (I refuse to use the word 'contract' as interfaces only solve half the problem, i.e. the statics, but give no indication of the dynamics of the system).

Consider it this way. You are a person, a class if you will. You perform many roles. You are a child, a worker, a parent maybe, a friend to someone (hopefully), a spouse etc. and you interact with the different people in your lives in different ways in those different roles. Each of your faces is the way you communicate inter-personally (See what I did there with the mnemonic? Ha-Haaa! I still got it! :o) ).

So look at your objects in context and determine if you really want to be using that header interface just to mock something and thereby "designing-by-tool" - Constraining the design by what the framework can support, makes you a tool!... (Two from two)

Dependency Inversion

This ties in nicely with Liskov substitution, since you should never depend on the concrete classes you create. So always make a link to the abstract class or better still interface rather then the concrete version of the code. This will massively help reduce coupling but also allow for the Liskov substitution to take place much more easily.

Closing Remarks

Back in the day, this was bread and butter stuff. So know it!! It will help you understand design patterns, develop your own usage of patterns, find anti-patterns, apply code smells, understand why coupling is important etc. etc. etc. It's the basics! 

What surprises me is that even with the introduction of fully OO languages into the mainstream, this has not actually improved OO code that much. Indeed, in the case of the mid-level developer, the introduction of OO languages that force the use of OO, seems to have made that standard worse. I would hope that people actually start to learn these again as part of self-tutorship, degree programmes, programming courses etc. but it isn't fun and sexy (though more now than ever), so it doesn't get the recognition and understanding that it deserves. 

0 comments:

Post a Comment

Whadda ya say?