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:
class ANOTCohesiveClass { private $firstNumber; private $secondNumber; private $length; private $width; function __construct($firstNumber, $secondNumber) { $this->firstNumber = $firstNumber; $this->secondNumber = $secondNumber; } function setLength($length) { $this->length = $length; } function setHeight($height) { $this->width = $height; } function add() { return $this->firstNumber + $this->secondNumber; } function subtract() { return $this->firstNumber - $this->secondNumber; } function area() { return $this->length * $this->width; } }
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.
class ACohesiveClass { private $firstNumber; private $secondNumber; function __construct($firstNumber, $secondNumber) { $this->firstNumber = $firstNumber; $this->secondNumber = $secondNumber; } function add() { return $this->firstNumber + $this->secondNumber; } function subtract() { return $this->firstNumber - $this->secondNumber; } }
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.
class Calculator { private $firstNumber; private $secondNumber; function __construct($firstNumber, $secondNumber) { $this->firstNumber = $firstNumber; $this->secondNumber = $secondNumber; } function add() { $sum = $this->firstNumber + $this->secondNumber; if ($sum > 100) { (new AlertMechanism())->tooBigNumber($sum); } return $sum; } function subtract() { return $this->firstNumber - $this->secondNumber; } } class AlertMechanism { function tooBigNumber($number) { echo $number . 'is too big!'; } }
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.
class Calculator { private $firstNumber; private $secondNumber; function __construct($firstNumber, $secondNumber) { $this->firstNumber = $firstNumber; $this->secondNumber = $secondNumber; } function add() { return $this->firstNumber + $this->secondNumber; } function subtract() { return $this->firstNumber - $this->secondNumber; } } class AlertMechanism { function checkLimits($firstNumber, $secondNumber) { $sum = (new Calculator($firstNumber, $secondNumber))->add(); if ($sum > 100) { $this->tooBigNumber($sum); } } function tooBigNumber($number) { echo $number . 'is too big!'; } }
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
Let's consider an example:
class Display { private $calculator; function __construct() { $this->calculator = new Calculator(1,2); } }
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:
class Display { private $calculator; function __construct() { $this->calculator = new Calculator(1, 2); } function printSum() { echo $this->calculator->add(); } }
The Display
class calls the Calculator
object's add()
method. This is another form of coupling, because one class accesses the other's method.
Coupling by Method Reference
You can couple classes with method references, too. For example:
class Display { private $calculator; function __construct() { $this->calculator = $this->makeCalculator(); } function printSum() { echo $this->calculator->add(); } function makeCalculator() { return new Calculator(1, 2); } }
It's important to note that the makeCalculator()
method returns a Calculator
object. This is a dependency.
Coupling by Polymorphism
Inheritance is probably the strongest form of dependency:
class AdvancedCalculator extends Calculator { function sinus($value) { return sin($value); } }
Not only can AdvancedCalculator
not do its job without Calculator
, but it couldn't even exist without it.
Reducing Coupling by Dependency Injection
One can reduce coupling by injecting a dependency. Here's one such example:
class Display { private $calculator; function __construct(Calculator $calculator = null) { $this->calculator = $calculator ? : $this->makeCalculator(); } // ... // }
By injecting the Calculator
object through Display
's constructor, we reduced Display
's dependency upon the Calculator
class. But this is only half of the solution.
Reducing Coupling with Interfaces
We can further reduce coupling by using interfaces. For example:
interface CanCompute { function add(); function subtract(); } class Calculator implements CanCompute { private $firstNumber; private $secondNumber; function __construct($firstNumber, $secondNumber) { $this->firstNumber = $firstNumber; $this->secondNumber = $secondNumber; } function add() { return $this->firstNumber + $this->secondNumber; } function subtract() { return $this->firstNumber - $this->secondNumber; } } class Display { private $calculator; function __construct(CanCompute $calculator = null) { $this->calculator = $calculator ? : $this->makeCalculator(); } function printSum() { echo $this->calculator->add(); } function makeCalculator() { return new Calculator(1, 2); } }
You can think of ISP as a higher-level cohesion principle.
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.
class Reporter { function generateIncomeReports(); function generatePaymentsReports(); function computeBalance(); function printReport(); }
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:
class Switch_ { private $fan; function __construct() { $this->fan = new Fan(); } function turnOn() { $this->fan->on(); } function turnOff() { $this->fan->off(); } }
Inheritance is probably the strongest form of dependency.
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 change Switch_
.
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.
interface Switchable { function on(); function off(); } class Fan implements Switchable { public function on() { // code to start the fan } public function off() { // code to stop the fan } } class Switch_ { private $switchable; function __construct(Switchable $switchable) { $this->switchable = $switchable; } function turnOn() { $this->switchable->on(); } function turnOff() { $this->switchable->off(); } }
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:
class Light implements Switchable { public function on() { // code to turn ligh on } public function off() { // code to turn light off } } class SomeWhereInYourCode { function controlLight() { $light = new Light(); $switch = new Switch_($light); $switch->turnOn(); $switch->turnOff(); } }
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:
class Rectangle { private $width; private $height; function setWidth($width) { $this->width = $width; } function setHeigth($heigth) { $this->height = $heigth; } function area() { return $this->width * $this->height; } }
This example defines a simple Rectangle
class. We can set its height and width, and its area()
method provides the rectangle's area. Using the Rectangle
class could look like the following:
class Geometry { function rectArea(Rectangle $rectangle) { $rectangle->setWidth(10); $rectangle->setHeigth(5); return $rectangle->area(); } }
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
:
class Rectangle { protected $width; protected $height; // ... // } class Square extends Rectangle { function area() { return $this->width ^ 2; } }
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 Square
is 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:
interface Broker { function buy($symbol, $volume); function sell($symbol, $volume); function dailyLoss($date); function dailyEarnings($date); } class NYSEBroker implements Broker { public function buy($symbol, $volume) { // implementsation goes here } public function currentBalance() { // implementsation goes here } public function dailyEarnings($date) { // implementsation goes here } public function dailyLoss($date) { // implementsation goes here } public function sell($symbol, $volume) { // implementsation goes here } } class TransactionsUI { private $broker; function __construct(Broker $broker) { $this->broker = $broker; } function buyStocks() { // UI logic here to obtain information from a form into $data $this->broker->buy($data['sybmol'], $data['volume']); } function sellStocks() { // UI logic here to obtain information from a form into $data $this->broker->sell($data['sybmol'], $data['volume']); } } class DailyReporter { private $broker; function __construct(Broker $broker) { $this->broker = $broker; } function currentBalance() { echo 'Current balace for today ' . date(time()) . "\n"; echo 'Earnings: ' . $this->broker->dailyEarnings(time()) . "\n"; echo 'Losses: ' . $this->broker->dailyLoss(time()) . "\n"; } }
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:
interface BrokerTransactions { function buy($symbol, $volume); function sell($symbol, $volume); } interface BrokerStatistics { function dailyLoss($date); function dailyEarnings($date); } class NYSEBroker implements BrokerTransactions, BrokerStatistics { public function buy($symbol, $volume) { // implementsation goes here } public function currentBalance() { // implementsation goes here } public function dailyEarnings($date) { // implementsation goes here } public function dailyLoss($date) { // implementsation goes here } public function sell($symbol, $volume) { // implementsation goes here } } class TransactionsUI { private $broker; function __construct(BrokerTransactions $broker) { $this->broker = $broker; } function buyStocks() { // UI logic here to obtain information from a form into $data $this->broker->buy($data['sybmol'], $data['volume']); } function sellStocks() { // UI logic here to obtain information from a form into $data $this->broker->sell($data['sybmol'], $data['volume']); } } class DailyReporter { private $broker; function __construct(BrokerStatistics $broker) { $this->broker = $broker; } function currentBalance() { echo 'Current balace for today ' . date(time()) . "\n"; echo 'Earnings: ' . $this->broker->dailyEarnings(time()) . "\n"; echo 'Losses: ' . $this->broker->dailyLoss(time()) . "\n"; } }
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.
A more complex example of this principle can be found in one of Robert C. Martin's first papers on the subject: The Interface Segregation Principle.
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.
If you'd like to learn more about design patterns, I created a Tuts+ Premium course on them!
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.
Comments