How to Write Code That Embraces Change

Writing code, which is easy to change is the Holy Grail of programming. Welcome to programming nirvana! But things are much more difficult in reality: source code is difficult to understand, dependencies point in countless directions, coupling is annoying, and you soon feel the heat of programming hell. In this tutorial, we will discuss a few principles, techniques and ideas that will help you write code that is easy to change.

Some Object-Oriented Concepts

Object-oriented programming (OOP) became popular, due to its promise of code organization and reuse; it utterly failed in this endeavor. We've been using OOP concepts for many years now, yet we continue to repeatedly implement the same logic in our projects. OOP introduced a set of good basic principles which, if properly used, can lead to better, cleaner code.

Cohesion

The things that belong together should be kept together; otherwise, they should be moved elsewhere. This is what the term, cohesion, refers to. The best example of cohesion can be demonstrated with a class:

This example defines a class with fields that represent numbers and sizes. These properties, judged only by their names, do not belong together. We then have two methods, add() and substract(), which operate on only the two number variables. We further have an area() method, that operates on the length and width fields.

It's obvious that this class is responsible for separate groups of information. It has very low cohesion. Let's refactor it.

This is a highly cohesive class. Why? Because every section of this class belongs with one another. You should strive for cohesion, but beware, it's can be difficult to achieve.

Orthogonality

In simple terms, orthogonality refers to the isolation or elimination of side effects. A method, class or module that changes the state of other unrelated classes or modules is not orthogonal. For example, an airplane's black box is orthogonal. It has its inner functionality, inner power source, microphones and sensors. It has no effect on the airplane it resides in, or in the exterior world. It only provides a mechanism to record and retrieve flight data.

An example of one such non-orthogonal system is your car's electronics. Increasing your vehicle's speed has several side-effects, such as increasing radio volume (among other things). Speed is not orthogonal to the car.

In this example, the Calculator class's add() method exhibits unexpected behavior: it creates an AlertMechanism object and calls one of its methods. This is unexpected and unwanted behavior; library consumers will never expect a message printed to the screen. Instead, they only expect the sum of the provided numbers.

This is better. AlertMechanism has no effect on Calculator. Instead, AlertMechanism uses whatever it needs in order to determine if an alert should be issued.

Dependency and Coupling

In most cases, these two words are interchangeable; but, in some cases, one term is preferred over another.

So what is a dependency? When object A needs to use object B, in order to perform its prescribed behavior, we say that A depends on B. In OOP, dependencies are extremely common. Objects frequently work with and depend on one another. So, while eliminating dependency is a noble pursuit, it is nearly impossible to do so. Controlling dependencies and reducing them is, however, preferable.

The terms, heavy-coupling and loose-coupling, usually refer to how much an object depends on other objects.

In a loosely-coupled system, changes in one object have a reduced effect on the other objects which depend on it. In such systems, classes depends on interfaces instead of concrete implementations (we will talk more about that later). This is why loosely-coupled systems are more open to modifications.

Coupling in a Field

It's common to see this type of code. A class, Display in this case, depends upon the Calculator class by directly referencing that class. In the above code, Display's $calculator field is of type Calculator. The object that field contains is a result of directly calling Calculator's constructor.

Coupling by Accessing the Other Class Methods

Review the following code for a demonstration of this kind of coupling:

This code introduces the CanCompute interface. An interface is as abstract as you can get in OOP; it defines the members that a class must implement. In the case of the above example, Calculator implements the CanCompute interface.

Display's constructor expects an object that implements CanCompute. At this point, Display's dependency with Calculator is effectively broken. At any time, we can create another class that implements CanCompute and pass an object of that class to Display's constructor. Display now depends only on the CanCompute interface, but even that dependency is optional. If we pass no arguments to Display's constructor, it will simply create a classic Calculator object by calling makeCalculator(). This technique is frequently used, and is extremely helpful for test-driven development (TDD).

The SOLID Principles

SOLID is a set of principles for writing clean code, which then makes it easier to change, maintain and extend in the future. They are recommendations that, when applied to source code, have a positive effect on maintainability.

A Little History

The SOLID principles, also known as Agile principles, were initially defined by Robert C. Martin. Even though he did not invent all of these principles, he was the one who put them together. You can read more about them in his book: Agile Software Development, Principles, Patterns, and Practices. SOLID's principles cover a wide array of topics, but I will present them in as simple a way as I'm capable. Feel free to ask for additional details in the comments, if needed.

Single Responsibility Principle (SRP)

A class has a single responsibility. This may sound simple, but it can sometimes be difficult to understand and put into practice.

Who do you think benefits from this class' behavior? Well, an accounting department is an option (for the balance), the finance department may be another (for income/payment reports), and even the archiving department could print and archive the reports.

There are four reasons why you might have to change this class; each department may want their respective methods customized for their needs.

The SRP recommends breaking such classes into smaller, beahvior-specific classes, each having just one reason to change. Such classes tend to be highly cohesive and loosely coupled. In a sense, SRP is cohesion defined from the point of view of the users.

Open-Closed Principle (OCP)

Classes (and modules) should welcome the extension of their functionality, as well as resist modifications of their current functionality. Let's play with the classic example of an electric fan. You have a switch and you want to control the fan. So, you could write something along the lines of:

This code defines a Switch_ class that creates and controls a Fan object. Please note the underscore after "Switch_". PHP doesn't allow you to define a class with the name "Switch."

Your boss decides that he wants to control the light with the same switch. This is a problem, because you have to changeSwitch_.

Any modifications to existing code is a risk; other parts of the system may be affected and require even further modification. It is always preferable to leave existing functionality alone, when adding new functionlaity.

In OOP terminology, you can see that Switch_ has a strong dependency on Fan. This is where our problem lies, and where we should make our changes.

This solution introduces the Switchable interface. It defines the methods that all switch-enabled objects need to implement. The Fan implements Switchable, and Switch_ accepts a reference to a Switchable object within its constructor.

How does this help us?

First, this solution breaks the dependency between Switch_ and Fan. Switch_ has no idea that it starts a fan, nor does it care to. Second, introducing a Light class will not affect Switch_ or Switchable. Do you want to control a Light object with your Switch_ class? Simply create a Light object and pass it to Switch_, like this:

Liskov Substitution Principle (LSP)

LSP states that a child class should never break the functionality of the parent class. This is extremely important because consumers of a parent class expect the class to behave in a certain way. Passing a child class to a consumer should just work and not affect the original functionality.

This is confusing at first glance, so let's look at another classic example:

The rectArea() method accepts a Rectangle object as an argument, sets its height and width, and returns the shape's area.

In school, we're taught that squares are rectangles. This hints that if we model our program to our geometrical object, a Square class should extend a Rectangle class. How would such a class look like?

class Square extends Rectangle {
// What code to write here?
}

I have a hard time figuring out what to write in the Square class. We have several options. We could override the area() method and return the square of $width:

Note that I changed Rectangle's fields to protected, giving Square access to those fields. This looks reasonable from a geometrical point-of-view. A square has equal sides; returning the square of width is reasonable.

However, we have a problem from a programming point-of-view. If Squareis a Rectangle, we should have no problem feeding it into the Geometry class. But, by doing so, you can see that Geometry's code doesn't make much sense; it sets two different values for height and width. This is why a square is not a rectangle in programming. LSP violated.

Interface Segregation Principle (ISP)

Unit tests should run fast - very fast.

This principle concentrates on breaking large interfaces into small, specialized interfaces. The basic idea is that different consumers of the same class should not know about the different interfaces - only the interfaces the consumer needs to use. Even if a consumer does not directly use all the public methods on an object, it still depends on all the methods. So why not provide interfaces with that only declare the methods that each user needs?

This is in close accordance that interfaces should belong to the clients and not to the implementation. If you tailor your interfaces to the consuming classes, they will respect ISP. The implementation itself can be unique, as a class can implement several interfaces.

Let's imagine that we implement a stock market application. We have a broker that buys and sells stocks, and it can report its daily earnings and losses. A very simple implementation would include something like a Broker interface, a NYSEBroker class that implements Broker and a couple of user interface classes: one for creating transactions (TransactionsUI) and one for reporting (DailyReporter). The code for such a system could be similar to the following:

While this code may work, it violates the ISP. Both DailyReporter and TransactionUI depend on the Broker interface. However, they each only use a fraction of the interface. TransactionUI uses the buy() and sell() methods, while DailyReporter uses the dailyEarnings() and dailyLoss() methods.

You may argue that Broker is not cohesive because it has methods that are unrelated, and thus don't belong together.

This may be true, but the answer depends on the implementations of Broker; selling and buying may be strongly related to current losses and earnings. For example, you may not be allowed to buy stocks if you are losing money.

You may also argue that Broker also violates SRP. Because we have two classes that use it in different ways, there may be two different users. Well, I say no. The only user is probably the actual broker. He/she wants to buy, sell, and view their current funds. But again, the actual answer depends on the whole system and business.

ISP is surely violated. Both UI classes depend on the whole Broker. This is a common problem, if you think interfaces belong to their implementations. However, shifting your point-of-view can suggest the following design:

This actually makes sense and respects the ISP. DailyReporter depends only on BrokerStatistics; it doesn't care and does not have to know about any selling and buying operations. TransactionsUI, on the other hand, knows only about buying and selling. The NYSEBroker is identical to our previous class, except it now implements the BrokerTransactions and BrokerStatistics interfaces.

You can think of ISP as a higher-level cohesion principle.

When both UI classes depended on the Broker interface, they were similar to two classes, each having four fields, of which two were used in a method and the other two in another method. The class would have not been very cohesive.

Dependency Inversion Principle (DIP)

This principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend upon details; details should depend upon abstractions. Put simply, you should depend on abstractions as much as possible and never on concrete implementations.

The trick with DIP is that you want to reverse the dependency, but always want to keep the flow of control. Let's review our example from the OCP (the Switch and Light classes). In the original implementation, we had a switch directly controlling a light.

As you can see, both dependency and control flow from Switch toward Light. While this is what we want, we do not want to directly depend on Light. So we introduced an interface.

It's amazing how simply introducing an interface makes our code respect both DIP and OCP. As you can see no, class depends on the concrete implementation of Light, and both Light and Switch depend on the Switchable interface. We inverted the dependency, and the flow of control was unchanged.

High Level Design

Another important aspect of your code is your high-level design and general architecture. An entangled architecture produces code that is hard to modify. Keeping a clean architecture is essential, and the first step is understanding how to separate your code's different concerns.

In this image, I attempted to summarize the main concerns. In the center of the schema is our business logic. It should be well-isolated from the rest of the world, and be able to work and behave as expected without the existence of any of the other parts. See it as orthogonality on a higher level.

Starting from the right, you have your "main" - the entry point to the application - and the factories that create objects. An ideal solution would get its objects from specialized factories, but that is mostly impossible or impractical. Still, you should use factories when you have the opportunity to do so, and keep them outside of your business logic.

Then, at the bottom (in orange), we have persistence (databases, file accesses, network communications) for the purpose of persisting information. No object in our business logic should know how persistence works.

On the left is the delivery mechanism.

An MVC, like Laravel or CakePHP, should only be the delivery mechanism, nothing more.

This lets you swap one mechanism with another without touching your business logic. This may sound outrageous to some of you. We're told that our business logic should be placed in our models. Well, I disagree. Our models should be "request models", i.e. dumb data objects used for passing information from MVC to the business logic. Optionally, I see no problem including input validation in the models, but nothing more. Business logic should not be in the models.

When you look at your application's architecture or directory structure, you should see a structure that suggests what the program does as opposed to what framework or database you used.

Lastly, make sure that all dependencies point toward our business logic. User interfaces, factories, databases are very concrete implementations, and you should never depend on them. Inverting the dependencies to point toward our business logic modularizes our system, allowing us to change dependencies without modifying the business logic.

Some Thoughts About Design Patterns

Design patterns play an important role in making code easier to modify, by offering a common design solution that every programmer can understand. From a structural point-of-view, design patterns are obviously advantageous. They are well tested and thought-out solutions.

The Force of Testing

Test-Driven Development encourages writing code that is easy to test. TDD forces you to respect most of the above principles in order to make your code easy to test. Injecting dependencies and writing orthogonal classes are essential; otherwise, you end up with huge test methods. Unit tests should run fast - very fast, actually, and everything that is not tested should be mocked. Mocking many complex classes for a simple test can be overwhelming. So when you find yourself mocking ten objects to test a single method on a class, you may have a problem with your code... not your test.

Final Thoughts

At the end of the day, it all comes down to how much you care about your source code. Having technical knowledge is not enough; you need to apply that knowledge again and again, never being 100% satisfied with your code. You have to want to make your code easy to maintain, clean and open to change.

Thanks for reading and feel free to contribute your techniques in the comments below.