00:00:07.889
Hey, hello everyone! That's me on Twitter, so feel free to follow me and tweet at me. If you want to take photos or anything like that, no problem.
00:00:14.020
Before I start, I want to say a big thank you to the organizers for putting on a really good show and for really looking after us today. Thank you very much for that.
00:00:20.529
I'm here in Belgium, and I'm all on my own, so if you see me around, I'm quite shy. Just put me in the corner hiding somewhere, so feel free to come and say hi and ask some questions.
00:00:26.259
This is my first time in Belgium. Before I came, I knew Belgium was famous for a few things.
00:00:32.560
You’re famous for your waffles, your beer, and your chocolate. But I wanted to do a bit more research first before I came.
00:00:40.449
Did you know that Belgium was the first country in the world to have a national lottery? So congrats on that!
00:00:46.840
You also invented the Smurfs, which have kept my children quiet on many a weekend. Thank you for that.
00:00:54.219
You’ve also got more car sales than any other country, so I’ll definitely come back and visit and try to take a look at those spectacles.
00:01:01.859
I'm from the UK, and we're famous for a few things as well, like our British cuisine for example.
00:01:07.230
And the affordable housing - you can buy this garage, I mean, a studio flat, for a mere one hundred and thirty-six thousand euros.
00:01:13.030
Also, our work-life balance, says a guy over here with a baby, a laptop, and a music stand who doesn't know what he's doing.
00:01:18.370
Anyway, I’m here to talk about changing software and how to make software easier to change. It’s said that the one constant in software is change.
00:01:28.000
So how can we make our software easier to change? That’s what I’m going to try to discuss today.
00:01:38.000
In particular, one thing I want to talk about is an acronym - OCP, but not on the computer which relates to consumer products because they make some horrible things.
00:01:44.000
I want to talk about the Open-Closed Principle. So what is it? This guy named Bertrand Meyer invented the Eiffel programming language.
00:01:50.000
He also conceived the concept of 'design by contract' and in his book 'Object-Oriented Software Construction' in 1988, he coined the term OCP.
00:01:55.000
The OCP states that your classes should be open for extension but closed for modification. In this talk, I will explore what that means.
00:02:03.000
I will show you some examples of how you can actually implement that in your code.
00:02:10.000
More specifically, Meyer states that classes should only be modified to correct errors. If you need to change or add new features, you should create a new class.
00:02:18.140
Shortly after Meyer, his principle became part of the SOLID principles of object-oriented design. SOLID is a set of guiding principles that complement each other.
00:02:26.400
It helps us achieve better object design. SOLID was introduced by Robert C. Martin, also known as Uncle Bob, and consists of five principles.
00:02:34.970
They are: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion.
00:02:41.010
So why is the Closed Principle important? It's all about risk and cost.
00:02:48.410
It’s said that if you’re changing stable, working code, you have a relatively high probability of introducing bugs.
00:02:54.200
Thus, it’s less risky if you can just create new code without changing existing code.
00:03:03.000
Also, on the cost front, it takes more time to change existing code because you have to look at it, understand it, and figure out what’s going on.
00:03:10.000
You then need to make those changes without introducing bugs and fix any bugs that arise.
00:03:19.000
So, there's this risk and cost idea, and when we’re changing code, we tend to have these two options: simple versus easy.
00:03:27.000
On the left side, simple means making your code as easy to understand and change as possible.
00:03:34.200
It has this kind of graph: cost is on the vertical axis, and time is along the bottom.
00:03:40.000
Initially, it takes a bit more work up front to make your code nice and changeable, but as time goes on, you reap those rewards.
00:03:49.000
Your code becomes much easier to change if you've invested that upfront time.
00:03:56.000
On the other hand, there's easy. This is where a lot of startups tend to fall down, as they need to implement the simplest change possible.
00:04:05.000
It’s really fast at the beginning; you’re shipping features without much consideration.
00:04:12.000
But at some point in the future, changes will become hard, and even the simplest change will feel like it takes forever.
00:04:18.000
In my opinion, you should be gambling on success.
00:04:25.000
Whatever product or project you’re working on, you’re gambling that it’s going to succeed and succeed for the long term.
00:04:32.000
So you should really be thinking about the simple side of things, because in the long term you don't want pain.
00:04:39.000
Dave Thomas gave a talk at lunch during RubyConf this year called 'My Book Taught Me to Code.' It's a really good talk.
00:04:46.000
Definitely go and watch it! Among other things, he says, "Forget all the rules.
00:04:53.000
Forget SOLID, forget C.R.U.D.; this is the only rule you need: good design is easier to change than bad design."
00:04:58.000
As developers, we need to embrace change.
00:05:05.000
You all know what it’s like – you work on a feature, it gets shipped, and almost immediately they want to change it.
00:05:11.000
You’re like, 'Man, I just worked on that feature!' But this is our job, so stop whining.
00:05:18.000
Let’s embrace change. Let’s make our code easier to change and in turn make our lives easier.
00:05:25.000
Now, slight sidetrack from OCP for a second: what do you think is the number one reason that makes our programs harder to change?
00:05:31.000
According to, well, me, it’s conditionals.
00:05:38.000
The reason I say that is because conditionals are magnets for business rules.
00:05:44.000
If certain conditions are met, do a thing. At the beginning, when these things are simple, it's fine.
00:05:50.000
But what tends to happen is they become magnets for your business rules, and your conditionals get more complex.
00:05:58.000
You end up with more branches, and then what happens is they end up being duplicated around your system.
00:06:04.200
This is where changing becomes really hard.
00:06:09.200
So if you can, try to avoid using 'if' statements.
00:06:15.000
I'm not saying to never use an 'if'. That would be silly, but just try to avoid them.
00:06:21.000
There are other techniques you can use, and some of those we'll see shortly.
00:06:27.000
If you can’t avoid using a conditional, at least try to name the concept.
00:06:32.000
For example, if we have a book that's on loan and it's due date is in the past and hasn't been returned,
00:06:39.000
instead of having that conditional directly, name that concept.
00:06:46.000
In terms of the book line, we can describe it as the book being overdue.
00:06:52.000
So name the concept and capture that logic somewhere to make it easier to change and understand.
00:06:57.000
Now, I’m going to go through a few examples of how we can use the Open-Closed Principle in a few different ways.
00:07:04.000
Remember that the advertised principle is about creating new classes without changing existing classes.
00:07:11.000
First, let's talk about inheritance.
00:07:18.000
This is the original idea that Meyer had for implementing the Open-Closed Principle.
00:07:25.000
It seems quite simple at the start.
00:07:31.000
For example, we have a car, and a car can accelerate, change gear, and probably do a whole bunch of other things.
00:07:38.000
If I wanted to create new functionality like a convertible, I can create a new class called 'Convertible' that extends the 'Car' class.
00:07:45.000
I simply add the new functionality to that class, and we’re done.
00:07:52.000
So this shows that inheritance was the basis of the original Open-Closed thinking.
00:07:59.000
But there are trade-offs; inheritance is relatively inflexible.
00:08:06.000
It’s harder to compose things if you want to mix and match.
00:08:12.000
So let’s look at some other ways we can achieve this.
00:08:19.000
First of all, there's dependency injection.
00:08:27.000
This is where you can pass collaborators into a class and then use them internally.
00:08:34.000
For instance, here we have a library with a method called 'loan'.
00:08:41.000
It takes a book and does a bunch of stuff.
00:08:48.000
One aspect we’re discussing here is how it logs to a file whenever we take a loan.
00:08:54.000
This is not particularly open-closed.
00:09:00.000
Suppose, in a few years, we want to change the logging to a database or some third-party service.
00:09:06.000
If we wanted to do that, we’d have to go into this class and change it.
00:09:12.000
If we want it to be open-closed, we can extract the logging part into its class.
00:09:19.000
We can call that class a 'FileLogger'.
00:09:26.000
Then we can pass that in to the library.
00:09:33.000
Here we've got a constructor that takes the logger. We can give it a default logger so it maintains its existing behavior.
00:09:41.000
Then we use that logger internally.
00:09:48.000
So in the future, if we want to change how logging is done, we don’t need to touch this class. It stays closed.
00:09:56.000
But we can extend it by creating a new class and injecting in that instead.
00:10:03.000
This is how it would look when you call it, using the default.
00:10:10.000
The API remains the same as before, but if you want different loggers, you just pass them in.
00:10:16.000
This gives you more flexibility.
00:10:22.000
For example, you could log to different places by creating a logger that logs everywhere.
00:10:28.000
Internally, it will just call all of your other loggers.
00:10:34.000
Dependency injection is useful when you have a role that needs to be changed for a class.
00:10:41.000
Sandi Metz gave a really good talk at RailsConf this year.
00:10:48.000
If you want a more in-depth explanation and more examples, you should definitely check that out.
00:10:54.000
The next topic is the decorator pattern.
00:11:01.000
This allows you to wrap an existing object to extend its behavior.
00:11:08.000
Here we have an example where you have a method for importing products.
00:11:15.000
It takes a file path and reads it as a CSV file, looping through all the rows.
00:11:23.000
Then it validates, so if the name is present, it does some stuff.
00:11:30.000
For this example, there are a couple of problems.
00:11:37.000
First, it's reliant on it being a CSV, which dependency injection could resolve.
00:11:43.000
But instead, I’m going to focus on the validation and the conditionals.
00:11:50.000
As I said before, conditionals can be magnets for business rules.
00:11:56.000
So what's likely to happen is those rules will change.
00:12:03.000
Currently, it just checks if the name is valid, but in the future, it could require a product ID or description.
00:12:08.000
So we want to extract that functionality out to make this method open-closed.
00:12:15.000
Let’s create another class, perhaps called 'ProductRow', to encapsulate the validation.
00:12:23.000
This class can take the CSV row into its constructor and store it.
00:12:29.000
Then it can have a valid method, encapsulating that logic.
00:12:36.000
We can then use that in our class.
00:12:43.000
Here’s a tip: you can also make this cleaner by using SimpleDelegator.
00:12:51.000
If you’re not aware of it, this is part of the standard library.
00:12:57.000
Whatever object you pass into the constructor, its methods will be delegated to.
00:13:05.000
So now let's apply this to our original example.
00:13:11.000
We’ll split this up into two methods.
00:13:19.000
The top method now just deals with reading the file from CSV.
00:13:26.000
For each row, we’ll wrap it with the new ProductRow class.
00:13:33.000
Underneath, we will loop through the rows.
00:13:41.000
If a row is valid, we can do some work, making it easier to read.
00:13:47.000
We’ve extracted our validation logic, although we still have a conditional.
00:13:54.000
However, we can remove that by using 'select,' which is on Enumerable.
00:14:00.000
Now, in our top method, we’re just reading the CSV.
00:14:06.000
For each of the rows, we wrap them with the new ProductRow.
00:14:12.000
Then we select only the valid ones.
00:14:19.000
In the method that does the actual business logic, we can simply loop through the valid rows and do the necessary work.
00:14:26.000
Now we don’t have any conditionals left, and our validation logic is extracted.
00:14:34.000
The decorator pattern is useful for separating concerns.
00:14:41.000
Also, when you don’t control when objects are created, you can extend behavior by wrapping them.
00:14:48.000
Next, let's discuss the command pattern.
00:14:55.000
This pattern is used for encapsulating behavior in a class.
00:15:02.000
For example, we have an image class.
00:15:09.000
An image can open a file and store it.
00:15:16.000
You might also want to add dimensions or color information.
00:15:22.000
We also have methods for effects like blur and grayscale.
00:15:29.000
If you see this kind of structure, it’s easy to foresee future effects being added.
00:15:37.000
If you did, you would have to modify this image class.
00:15:44.000
So I want to make this image class more open-closed by taking out the effects.
00:15:51.000
Currently, to call it, you create an image, and whenever you call an effect, it operates on the current image.
00:15:59.000
It applies the effect and returns a copy of that image.
00:16:05.000
You can chain those calls together.
00:16:11.000
But the class itself isn't open-closed.
00:16:18.000
We can extract the effects into separate classes.
00:16:25.000
Now, we should add an 'apply' method that takes a list of commands.
00:16:32.000
We can use 'reduce' to iterate through these commands.
00:16:39.000
For those unfamiliar with 'reduce', it takes a list of things, and in our case, commands.
00:16:46.000
It will iterate through them and ultimately produce one single result.
00:16:53.000
It works on an accumulator, which we can set the initial value by passing it to reduce.
00:17:00.000
In this example, we pass 'self', which represents the current image and its state.
00:17:08.000
Each iteration, we pass the current state and the current command to apply it.
00:17:16.000
The result of that application is our new accumulator.
00:17:24.000
So we can use it like this now: instead of chaining commands, we can just call 'image.apply'.
00:17:32.000
You pass in the class of the command for the particular effect.
00:17:41.000
The key point is that when there’s a new effect - say something like a polarizing filter.
00:17:48.000
I don’t need to change the image class. It remains stable.
00:17:55.000
I simply create a new class for that effect and it's responsible for it.
00:18:04.000
Then I just pass it in here.
00:18:11.000
The command pattern is all about encapsulating behavior and passing it around.
00:18:18.000
Now, the next example is slightly longer, so hopefully you’re ready.
00:18:25.000
Let’s discuss the service locator pattern.
00:18:32.000
This focuses on defining behavior at runtime based on your needs.
00:18:39.000
For example, we have a class called 'NotificationCenter'.
00:18:46.000
It has a method that sends a welcome message to a user via email.
00:18:53.000
This method is called 'send_email', which takes an email address and some content.
00:19:00.000
This is fine, so far, but what happens when new requirements arise?
00:19:07.000
Your boss might say, 'Users love getting emails, but some prefer SMS instead.'
00:19:15.000
We want to add that option in their settings.
00:19:22.000
Typically, we do the easy thing: add a conditional.
00:19:31.000
You check what the notification method is and implement the corresponding method, like 'send_sms'.
00:19:37.000
Initially, this seems to work well and your boss is happy.
00:19:44.000
A few weeks later, though, they request another option: webhooks.
00:19:50.000
Now, you need another conditional to check for this functionality.
00:19:57.000
You see where this is going: your codebase becomes bloated with conditionals.
00:20:04.000
Humans tend to follow patterns. You see how things are done and just copy the current method.
00:20:10.000
This leads to a messy codebase.
00:20:17.000
So when another requirement arises, you end up adding yet another conditional.
00:20:25.000
Instead of continuing down this path, you should create classes for each notification method.
00:20:32.000
So, for email, SMS, and webhooks, you create a new 'Notify' class.
00:20:39.000
Each of these classes will have a 'deliver' method, taking the user and allowing access to the necessary information.
00:20:46.000
You can then implement a small refactoring over time.
00:20:52.000
The first step eliminates all internal methods; you're just using the new classes.
00:20:59.000
Next, you can turn big 'if' statements into case statements.
00:21:05.000
This doesn’t change the logic; it just makes it easier to read.
00:21:12.000
Now, you can see some duplication in your code.
00:21:19.000
You can refactor and remove that.
00:21:26.000
By splitting the lookup process, you remove the duplication and ensure better readability.
00:21:32.000
You create a method called 'notify_for' that identifies the class to deliver notifications.
00:21:40.000
So, in your existing notify method, you just call this lookup method.
00:21:48.000
Inside the 'Notify Registry', you can start incorporating methods.
00:21:55.000
The registry can have a mapping of notification methods.
00:22:02.000
This keeps your class open-closed, as new classes are added without altering existing ones.
00:22:09.000
The registry maps methods to classes, fetching what the user needs.
00:22:16.000
But when a new class is created, you must add it to the hash!
00:22:22.000
To ensure the registry populates in an open-closed way, we need a few options.
00:22:29.000
The first option is treating it as simple configuration.
00:22:36.000
You initialize your registry, and just add classes accordingly.
00:22:44.000
However, the downside is you can forget to add classes, leading to inconsistencies.
00:22:51.000
We could also auto-register classes, allowing them to add themselves.
00:22:58.000
This requires a change in the registry, removing the existing hash entirely.
00:23:05.000
You create a new method called 'register' which adds classes to the registry.
00:23:12.000
This works well but requires naming conventions for the classes.
00:23:20.000
So classes must be named carefully to avoid confusion.
00:23:27.000
Another approach is to use self-registration, modifying the registry setup.
00:23:35.000
The register method will only take the class, not a method.
00:23:42.000
The class will treat itself and fill in the hash.
00:23:49.000
Every time a class is loaded, it will check its method.
00:23:55.000
If the method is met, it registers itself in the registry.
00:24:02.000
This way, each new class handles its registration.
00:24:08.000
When the app initializes, it can automatically find all notifiers.
00:24:15.000
There will be the potential for unwanted classes, posing a risk.
00:24:22.000
So implementing best practices makes sense.
00:24:30.000
When you want to notify users, you just look it up and deliver.
00:24:36.000
In production, this approach works well, but in development, things get tricky.
00:24:43.000
Classes might not load correctly until they’re referenced.
00:24:49.000
To solve this, we can verify if it has been loaded.
00:24:55.000
If not, we require that directory, allowing classes to register correctly.
00:25:01.000
This will streamline class registration.
00:25:08.000
Let’s run this in real time.
00:25:15.000
We have a dropdown for users to pick notification methods.
00:25:22.000
Once saved, the selected method notifies accordingly.
00:25:29.000
After adding a new class, your implementation dynamically adjusts.
00:25:36.000
All users can select how they want their notifications.
00:25:43.000
I didn’t need to restart the app, and this method improves user options.
00:25:49.000
Your code remains decoupled, adhering to OCP principles.
00:25:55.000
This showcase demonstrates that functionality can integrate smoothly.
00:26:02.000
Let’s recap how we populate our registry with three different methods.
00:26:10.000
Configuration is simple, providing certain benefits but it can create errors.
00:26:17.000
Convention eases development but requires strict naming.
00:26:23.000
Self-registration will automate but carries risks.
00:26:30.000
The service locator pattern lets you look up behavior at runtime without changes.
00:26:37.000
It’s all about balance and trade-offs.
00:26:44.000
This is not a license to go overboard on patterns.
00:26:50.000
Use patterns wisely, implementing as they best fit your code.
00:26:57.000
Maintain good object design practices to enhance changeability.
00:27:04.000
Good design benefits codebases, regardless of growth.
00:27:10.000
It's important to consider the readability of your code for others.
00:27:18.000
Code is read far more often than it is written.
00:27:24.000
Think of your coworkers and your future self.
00:27:32.000
Remember that maintaining code that’s easy to understand saves considerable time.
00:27:39.000
Naming things well, and keeping code small helps it stay changeable.
00:27:46.000
As we’ve seen, following established patterns matters in code design.
00:27:52.000
It’s important to create small focused classes.
00:27:59.000
When working in Rails, you might feel overwhelmed with class placement.
00:28:06.000
You don't have to worry as much; just group classes by functionality.
00:28:14.000
For example, have a security or an authentication folder.
00:28:21.000
Finally, a few considerations: create focused classes.
00:28:29.000
Take a good look at SOLID principles.
00:28:36.000
Lastly, think about striking the right balance between simple and easy.
00:28:42.000
If deadlines are tight, do what you need to get things done, but consider the future.
00:28:50.000
Balance short-term needs with long-term vision.
00:28:57.000
As for best practices, concentrate on naming things clearly.
00:29:03.000
Aim to keep things small, minimize conditionals, and avoid nils.
00:29:09.000
You don’t have to always manage every aspect; seek smart solutions.
00:29:15.000
I appreciate your time, and I’m here to answer any questions.
00:29:22.000
Also, I work for LoyaltyLine, and we’re actively hiring.
00:29:29.000
If you want stickers or t-shirts, come see me afterward!
00:29:35.000
Feel free to tweet me as well with questions. Thank you!
00:29:41.000
(Applause)