Talks

Summarized using AI

Opening up to Change

Andy Pike • October 01, 2015 • Ghent, Belgium

In the talk titled Opening up to Change, Andy Pike discusses the importance of writing adaptable software code, centralizing on the Open-Closed Principle (OCP), one of the key components of SOLID principles in object-oriented design. He emphasizes the necessity of designing code that is 'open for extension but closed for modification'—meaning that software should allow for new features to be added without altering existing code.

Key Points Discussed:
- The significance of the Open-Closed Principle was introduced, explaining how it allows software evolution without impacting stable, working code, thus reducing the risk of introducing bugs when making updates.
- A comparison was made between 'simple' and 'easy' coding practices, emphasizing that upfront effort in building a beautiful and changeable codebase pays off in the long run.
- Pike highlights the dangers of conditionals in code, as they tend to complicate business logic by multiplying paths and increasing code duplication, thus making future changes difficult.
- Several design patterns were illustrated to adhere to the Open-Closed Principle, including:
- Inheritance: Creating new classes based on existing ones.
- Dependency Injection: Passing dependencies into classes to retain flexibility for future changes.
- Decorator Pattern: Allowing extensions of existing class behaviors without altering their structures.
- Command Pattern: Encapsulating behavior in classes to allow seamless changes by introducing new classes for additional functionalities.
- Service Locator Pattern: Enabling runtime behavior lookups to prevent modification of existing code when adding new features.
- Pike also advocates for creating numerous well-defined classes rather than overly complex ones, underlining that code should be easy to read and understandable for future developers.
- His concluding advice encourages embracing change, writing code that is cleaner and more modular, and the consideration of various design patterns to keep future modifications smooth and maintainable.

By emphasizing careful class construction and reducing complexity in decision-making structures, Pike leaves the audience with a better grasp of how to implement design practices that welcome change and reduce long-term maintenance costs.

Opening up to Change
Andy Pike • October 01, 2015 • Ghent, Belgium

The one constant in software is change. Your code will need to change and adapt over time. We should try to write code that is easy to change in the future. If possible, change without having to edit existing code. This talk is all about the O in SOLID: the Open-Closed principle. I'll explain what the Open-Closed principle is, why this is important and how to structure and refactor code to make it "open for extension but closed for modification". In this talk I'll show you, by example, how to make your code more changeable. In fact, so changeable that you will be able to extend what your program does and how it behaves without modifying a single line of existing code. Starting with a real world example that is painful to extend, I’ll refactor it over many iterations until it truly is Open-Closed. I'll show techniques, trade-offs and some gotchas from real world experience which will help you write more flexible programs in the future. If you’ve never heard of the Open-Closed principle or are unsure how to put it into practice then this talk is for you. I would love to help open the door of this technique and give you the ability to alter your system in a painless way by opening it up to change.

Help us caption & translate this video!

http://amara.org/v/H498/

ArrrrCamp 2015

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)
Explore all talks recorded at ArrrrCamp 2015
+14