RailsConf 2024
Plain, Old, but Mighty: Leveraging POROs in Greenfield and Legacy Code

Summarized using AI

Plain, Old, but Mighty: Leveraging POROs in Greenfield and Legacy Code

Sweta Sanghavi • May 24, 2024 • Detroit, MI

The talk titled "Plain, Old, but Mighty: Leveraging POROs in Greenfield and Legacy Code" by Sweta Sanghavi focuses on the effective use of Plain Old Ruby Objects (POROs) to enhance the development process in both new and legacy code environments.

Key points discussed in the video include:

- Understanding POROs: A PORO is defined as a Ruby object that does not inherit any functionality from Rails, aiming for simplicity and usability within the Rails application.

- Benefits of Using POROs: Utilizing POROs promotes better object-oriented design, flexibility, a usable API, and faster testing, allowing developers to maintain speed in development amidst changing requirements.

- Case Study: BackerKit: Sanghavi shares her experience at BackerKit, illustrating how they managed the transition from B2B to B2C crowdfunding while navigating complex requirements and overcoming challenges by leveraging POROs.

- Example in Greenfield Code: The session illustrates a practical example where a progress bar is integrated for simultaneous crowdfunding projects using a PORO, showcasing how complexities can be managed and separated efficiently.

- Refactoring Legacy Code: The speaker discusses the challenges associated with legacy code, emphasizing the role of POROs in breaking down intricate code structures into manageable parts, enabling incremental improvements.

- Focus on Incremental Improvement: The importance of making small, manageable changes to code using POROs is highlighted, suggesting that even minor adjustments can pave the way for greater clarity and refactoring opportunities in legacy systems.

- Seam Creation: POROs create seams in the code which facilitate future modifications, making the codebase cleaner and easier to extend.

In conclusion, Sweta Sanghavi emphasizes that POROs are essential for creating flexible, maintainable code in both new applications and legacy systems. By promoting incremental change and clarity, developers can leave their codebases in a better state for future work, ultimately inviting simplicity into their designs. The session ends with encouragement to approach code development with curiosity and incremental improvement, modeled on the strategies shared throughout the talk.

Overall, the takeaway is to leverage the power of simple yet effective code structures through the use of POROs, ultimately leading to more adaptable and clean codebases.

Plain, Old, but Mighty: Leveraging POROs in Greenfield and Legacy Code
Sweta Sanghavi • May 24, 2024 • Detroit, MI

Plain, Old, but Mighty: Leveraging POROs in Greenfield and Legacy Code by Sweta Sanghavi

Applications exist in changing ecosystems, but over time, they can become less responsive. Controllers actions slowly grow in responsibilities. Models designed for one requirement, no longer fit the bill, and require multi-step deploy processes to change.

The plain old Ruby object, the mighty PORO, provides flexibility in both new domain and legacy code.

Pair with me on how! We'll explore how we can leverage POROs in our application through some real world examples. We'll use objects as a facade for domain logic in development, and refactor objects out of legacy code to increase readability, ease of testing, and flexibility. You'll leave this session with some strategies to better leverage Ruby objects in your own application.

RailsConf 2024

00:00:11 Our next speaker is a Rails developer, plant mother, and beginner wobbler. I won’t spoil the surprise, so ask her about it.
00:00:18 She enjoys uncovering valuable insights from seemingly simple places when approached with curiosity.
00:00:24 She enables creators to bring their dreams to life through crowdfunding at BackerKit. Please welcome Sweta Sanghavi.
00:00:41 A couple of years ago at BackerKit, we set out to build a crowdfunding platform.
00:00:47 We had been working behind the scenes with creators for over a decade, yet we had never enabled them to run campaigns on our software.
00:00:53 Hence, we aimed to build a product that would compete with an established first-to-market competitor.
00:01:01 Lucky for us, we already had a creator interested in experimenting with us.
00:01:06 However, this also meant we had to be up and running within a few months by the time they wanted to launch.
00:01:14 We needed to build everything from a new payment processing method to routing within a new engine in our Rails monolith.
00:01:20 At that time, we were transitioning BackerKit from a B2B to a B2C product.
00:01:26 This felt audacious, scary, and stressful, but it was also exciting.
00:01:34 It was a chance to build in a new space, solve new problems for new customers, and leave behind our technical debt.
00:01:40 While there were unexpected hiccups—like the entire team catching COVID at the onsite where we started building—Rails supported us in putting up new user flows relatively quickly.
00:01:46 Soon, we launched our first customer on crowdfunding by BackerKit as scheduled, and the project was funded.
00:01:53 Shortly after, other customers lined up to launch on BackerKit, each with new requirements and bugs to fix.
00:02:01 We needed to iterate on features we had already rolled out, while also expanding our engagement methods with backers.
00:02:07 However, while building out this platform, we started hitting some roadblocks.
00:02:12 We were trying to push many new features out as fast as we could to get user feedback, but soon the features took much longer to implement.
00:02:24 The initial patterns that had served us well at the start began to falter as time moved on.
00:02:31 As developers, we were constantly balancing production pressures, weighing speed to release against the time needed to write well-factored code.
00:02:37 Today, we’ll discuss how to maintain speed in development by better utilizing a simple tool: our familiar friend, the Plain Old Ruby Object.
00:02:48 Welcome to 'Plain Old But Mighty: Leveraging POROs in Greenfield and Legacy Code'. My name is Sweta Sanghavi, and I’m a New York transplant living in sunny Oakland, California.
00:02:59 And just to settle the debate, I believe pineapple does belong on pizza.
00:03:05 As a tech lead at BackerKit, I work to build tools that help creators achieve their best crowdfunding campaigns.
00:03:11 We also support them in fulfilling these campaigns and distributing rewards to backers.
00:03:17 Also living in Oakland is my firstborn, Morty, who today will double as my co-pilot.
00:03:23 Morty is a staunch proponent of the PORO in his code, and I hope we can all learn something from him today.
00:03:31 We'll begin today by discussing what a PORO is, how to use them to grow Greenfield code, and how to leverage them in legacy code.
00:03:38 In the next half hour, I aspire for us all to become better at spotting opportunities to leverage POROs and ship more extendable code.
00:03:46 Whether it’s for new or existing features, let’s start by defining what a PORO is.
00:03:54 And I hear Morty meowing for attention. Morty, what is it?
00:04:00 Morty has an answer for us: a Plain Old Ruby Object is an object that does not inherit any functionality from Rails.
00:04:07 We gain many great features from Rails itself, but today, we are focused on code that does not inherit from Rails.
00:04:14 We can write better Rails code by leveraging POROs to encapsulate data and methods within our Rails application.
00:04:21 So simply put, we are talking about simple objects with no additional bells and whistles from Rails.
00:04:28 It’s really just Ruby. And while POROs may seem simple, let’s not be misled into thinking they are unimportant.
00:04:34 They help us factor our code into a more object-oriented design, provide us flexibility in building out our code, give us a usable API, and support faster testing.
00:04:41 Let’s see how we can use POROs to grow Greenfield code.
00:04:48 When building new features, we often encounter high-turn code because many of the requirements are still unknown.
00:04:53 This code is still in development, and POROs allow us to continue to grow this code.
00:05:00 So, everyone put on your BackerKit hats, and let’s look at an example from BackerKit.
00:05:06 First, a little background on the app we are building on today: BackerKit is a crowdfunding platform.
00:05:13 Creators launch projects like a new fantasy role-playing game, which have funding goals. Backers can support these projects in exchange for rewards.
00:05:21 If enough backers are excited about a project and invest in it, the project gets funded.
00:05:29 The funds are collected, and the creator can use that money for production.
00:05:38 Now let's imagine that BackerKit is going to launch 100 pin projects at the same time for an event called Pentopia.
00:05:45 If all the projects get funded, backers who supported these projects will receive commemorative pins.
00:05:52 To celebrate this event and track its progress, we will have a progress bar on the project pages.
00:06:00 This will let the backers know what percentage of projects have been funded, along with a funded count displayed at the bottom left.
00:06:06 In this example, for instance, 89 out of 100 projects were funded.
00:06:12 Let’s start sketching this feature into our codebase.
00:06:20 All right, Morty, your turn! You’re going to write us a red test.
00:06:27 Morty is pulling up our project controller spec and writing a new test in the show action, as this new feature will be visible on project pages.
00:06:35 We expect this progress bar to be displayed there.
00:06:42 We’ll start with a straightforward spec, covering both funded and non-funded Pentopia projects.
00:06:49 When we make a request to the show page, we expect the body to contain the funded count we visualized earlier.
00:06:56 Let’s get Morty’s tests to pass.
00:07:02 We will open up this show action and find a way to represent this new event.
00:07:09 We need to determine the funded count for our new component.
00:07:16 Since this is a one-off feature for Pentopia, I resist committing to anything concrete for now.
00:07:22 So, I think I’ll temporarily store the projects participating in this event in a constant array.
00:07:29 The thermometer should only display for projects involved in Pentopia.
00:07:36 Therefore, I will check if the project we are trying to display is included in the event.
00:07:42 Next, we’re going to need a progress component to show the progress.
00:07:48 Looking through our code, I find a view component called progress component.
00:07:54 The progress component takes three parameters: a title, the progress, and the total.
00:08:01 We can think of it as a numerator and denominator represented by progress and total.
00:08:07 So, I will set the title and total based on our constant representing the Pentopia projects.
00:08:14 To find the total, we can just get the length of that constant.
00:08:22 The last piece is to get the count of funded projects.
00:08:30 I’m working towards a green test without overthinking how to accomplish this.
00:08:36 I will iterate through the projects participating in Pentopia and use the scope method I found, fully funded, to count the number.
00:08:43 Then, I’ll add that to our controller code.
00:08:48 So, if our constant includes the current project, we instantiate an instance of our event progress component.
00:08:56 This component will show what percent of funded projects exist for the event.
00:09:02 Let's render this view component conditionally in our show action.
00:09:08 We’ll also add a little stub for our constant so we can assert that the two projects we’ve added are returned by our Pentopia project stub.
00:09:14 Let's run our test.
00:09:22 All right, we got two greens! Amazing!
00:09:28 Now let’s examine the trade-offs we’ve introduced in our show action.
00:09:33 Firstly, we now have a test, which validates the functionality required by this feature.
00:09:40 Getting to a green test illuminated the unknowns and requirements we need.
00:09:47 Until we’re green, we can’t guarantee that we have accomplished the task.
00:09:54 Thus, refractoring prematurely is not an option.
00:10:02 With this green test, we know the requirements for this new domain concept.
00:10:08 We need the number of funded projects, a way to check if the current project is included in this event, and the denominators.
00:10:13 Consider that we have added significant code to this controller.
00:10:20 If this event does not occur again, this code will remain in this controller indefinitely for all future developers.
00:10:28 If this is early in the controller's lifecycle, the impact of this code may not be noticeable.
00:10:35 However, as the codebase grows, this added complexity can become an obstacle.
00:10:43 How can we instead utilize our PORO to support this new feature?
00:10:52 We understand that this new domain concept introduces the idea of multiple projects launching simultaneously.
00:11:00 What if we represent that with a new object? I will name it EventProject.
00:11:06 This will represent the fact that it's an event.
00:11:13 Let’s move our specs to a new spec for this new PORO.
00:11:19 We essentially already have the interface we need because we saw it in action in the controller code.
00:11:26 The funded projects method will return the funded projects and the projects method will return the projects.
00:11:34 Through these tests, we can determine that the name should simply return the string 'Pentopia' for our view component.
00:11:42 Now, let’s see what it's like to pass that test for this PORO.
00:11:50 Notice this exercise is largely moving controller code into a new file, a new object called EventProject.
00:11:56 We taking our constants and gave them a name that's hardcoded for now, while also relocating the projects method.
00:12:02 This method will iterate over the constant to grab the projects.
00:12:09 We also use that same scope method we talked about for funded projects.
00:12:17 One last essential aspect we outlined is a way to check if the current project we're displaying belongs to this event.
00:12:23 So, we will accept a project and add a method to return true or false if that project is participating in Pentopia.
00:12:30 By leveraging our previously written projects method, we can ensure the event's progress view component renders appropriately.
00:12:38 We can then simply return the component or nil based on the project’s status.
00:12:44 As I’m coding, I realize the only requirement for this feature is conditionally rendering this progress bar.
00:12:50 Additionally, I’ll move the other methods to the private scope and expose only the event progress view component.
00:12:57 Let's take a closer look at the PORO.
00:13:05 It’s still quite simple. It takes a project and has just one public method.
00:13:12 However, it offers a place for future changes to the EventProject to take form.
00:13:19 We can foresee future use cases for even those private methods, and some of this code may evolve.
00:13:26 Let’s see how the controller looks with this new object.
00:13:32 It’s now much simpler—essentially just one line where we set an instance variable to the return value of the event progress view component.
00:13:39 Our view code remains unchanged; our instance variable can either be nil or the event progress component we want.
00:13:47 This code now adheres better to the single responsibility principle.
00:13:54 Our show action stays focused on the components needed to display the project page.
00:14:00 In contrast, the EventProject is now responsible for knowing when to render this component and for supplying all the information needed.
00:14:06 This refactoring has also granted us much greater flexibility.
00:14:14 With this adjustment, we introduce a new seam into our code.
00:14:20 The term 'seam,' as I’m using it, refers to a place in which we can alter our program's behavior without modifying the actual code.
00:14:28 This aligns with the open-closed principle, which states that software should be open for extension but closed for modification.
00:14:35 In our case, team members should be able to add new functionalities to our system without altering the existing code.
00:14:42 We could modify the internals of EventProject without requiring any changes to the controller code.
00:14:49 Let’s say there's a new requirement regarding whether we show or don't show that component.
00:14:56 We won’t have to alter the controller; the EventProject can handle all new conditional logic.
00:15:03 Leveraging POROs yields seams that introduce simplicity to our design.
00:15:09 The controller remains straightforward, while the EventProject can continuously evolve.
00:15:16 This allows our code to remain inexpensive, easy to extend, and modify as needed.
00:15:24 POROs also provide a means to wrap our uncertainties, allowing our codebase to evolve.
00:15:31 In Greenfield code, POROs play a crucial role when wrapping business logic to represent new domain concepts.
00:15:39 This helps contain uncertainties and hard-coded bits in one place.
00:15:45 Those hard-coded strings and arrays can reside in our PORO, whereas outside the project, we don’t care how funded projects gets evaluated.
00:15:52 Within the PORO, we can develop the code we need as new requirements emerge.
00:15:59 Sometimes we need to tolerate uncertainties while waiting for the right abstractions to appear in our code.
00:16:06 Having a boundary around this code mitigates pain as we wait for future examples.
00:16:12 For instance, when we first received the feature requirement, we could save it as a model.
00:16:20 However, we don’t yet know the attributes that EventProject will require.
00:16:27 We expect to encounter unknowns during this initial phase, including changing table names and managing downtime with deployed changes.
00:16:33 None of these are concerns I want to deal with when adding a new feature.
00:16:41 This PORO helps us get the API we want from a model while alleviating the costs of waiting.
00:16:48 This PORO is designed with the current use case in mind, while allowing any interface changes as needed.
00:16:55 Moreover, our half-baked PORO, with its constants and hard-coded strings, signals to the reader where this object is in its development.
00:17:02 It’s not as set in stone as a model might imply.
00:17:08 Until we receive a requirement that can’t be fulfilled without a new table, I’m happy to wait.
00:17:15 Our POROs also foster faster and simpler testing.
00:17:22 When we refactored our tests around our PORO, we no longer needed to load our Rails app or its gems just to test our code.
00:17:30 Previously, while in controller specs, we would make requests to actions and assert on responses, which are expensive tests.
00:17:37 However, in our PORO, we can create better unit tests that don’t demand as many resources.
00:17:43 Our tests are now faster, as we transition integration tests from the controller into unit tests.
00:17:50 Furthermore, our controller specs will be more straightforward and won't require any test data setup.
00:17:57 They’ll simply expect to receive different responses from EventProject.
00:18:05 In addition, we have an ergonomic, easy-to-use API.
00:18:12 POROs can provide ergonomic features based on how we utilize and compose methods.
00:18:18 Even the public and private scopes we’ve devised contribute to this.
00:18:25 We might also add some syntactic sugar if calling certain methods frequently.
00:18:32 This keeps our code uncluttered and easy to understand.
00:18:37 If we need to wrap APIs, for example, we can use POROs with more semantic names and methods pertaining to our domain.
00:18:44 Using POROs allows us to design code in flexible, composable bits.
00:18:50 It was not as challenging to shift from fitting our code into the controller to developing a new object.
00:18:57 The most difficult part is simply deciding to form a new object during the quest for extendable code.
00:19:05 By continuing this practice long term, we will cultivate more flexible code.
00:19:12 Now, let’s talk about legacy code, which can have various definitions.
00:19:19 For instance, code that the original developer no longer maintains, or code that's no longer engineered but continually patched or untested.
00:19:26 The first bullet reflects how one developer hands the code off to another.
00:19:33 At one point, someone knew the purpose of this code, but they are no longer here for various reasons.
00:19:39 Show of hands: who has ever encountered a legacy code they had a hand in creating only to find it hard to understand?
00:19:47 I find it interesting how such code is never simple to patch without clear abstractions or design for extending.
00:19:53 This often puts us in a challenging position, especially if the code is untested.
00:20:02 It’s intimidating to change code we know has crucial functionality, yet we can’t quite comprehend.
00:20:08 How did we get here?
00:20:15 As new feature requests come in, we search our codebase for adjacent code and stick new code there.
00:20:21 We often add more code where code already exists.
00:20:28 If there’s a complex conditional, what's one more else-if statement?
00:20:36 Unfortunately, the natural trend for code is to grow larger and larger until it tips.
00:20:43 The complexity can become so overwhelming that imagining putting code somewhere else becomes impossible.
00:20:50 Poorly factored legacy code, which is harder to utilize than you might expect, is often criticized.
00:20:57 Yet, frequently this code persists because it continues to fulfill a purpose.
00:21:04 It reflects the constraints applicable during the time this code was written, when requirements were still emerging.
00:21:11 If we find ourselves extending this code, it is likely high-turn and worth an investment.
00:21:17 Refactoring this code can be intimidating due to the lack of tools available for untangling it.
00:21:25 This is where POROs shine.
00:21:32 They offer small, manageable steps within the feature delivery lifecycle, helping us begin disentangling legacy code.
00:21:39 Let’s look back at our app at BackerKit to explore how we can lasso our legacy code with our mighty POROs.
00:21:46 We will examine our pledge controller, as making a pledge is a crucial aspect of our app.
00:21:53 The complexity here is not surprising; in fact, this code does not fit on a single slide.
00:22:00 Prior to this code excerpt, we set some attributes on a pledge.
00:22:07 Pledge.confirm essentially saves the pledge, and, if successful, carries out some actions.
00:22:13 If unsuccessful, we’ll execute an alternate set of actions.
00:22:20 This presents a wall of text—so, I don’t expect you to read everything here.
00:22:27 We need to address numerous tasks once we've successfully saved our pledge.
00:22:35 As I scan this code, it becomes challenging to understand what's crucial for saving a pledge.
00:22:42 Putting on my archaeologist hat, I can speculate how we got here.
00:22:49 A feature request came in stating we want to show star backer status after someone pledges.
00:22:56 A reasonable developer looks for a seam after creating a pledge and sticks this additional functionality in.
00:23:03 I recognize a variety of service objects instantiated within this create method; design exists here.
00:23:09 While assessing this code, I notice two different activities occurring.
00:23:16 Setting instance variables for the next action or user view, and side effects or data modifications to support user flows.
00:23:23 I’m going to capture much of this procedural code that isn’t vital for the redirect logic and move it into a Plain Old Ruby Object.
00:23:30 As I review the method calls, I can see that the arguments for these methods often involve project, user, and pledge.
00:23:37 Let’s create a new service object called PledgeCreationService with user, project, and pledge as parameters.
00:23:44 I’m simply transferring much of the code into this new service object with a call method.
00:23:52 Looking back at the controller, I must retain some code within the conditional.
00:23:59 We've successfully resolved a substantial portion of the conditional branch.
00:24:06 I would argue that even the simple act of crafting a new service object that isn't fully factored produces a positive change in our code’s structure.
00:24:12 We can utilize POROs for small, incremental changes that enhance the code a bit better than how we found it.
00:24:20 We’ve provided a breadcrumb for the next developer to come in and enhance this further.
00:24:27 With this change in the controller, its primary purpose is now clearer.
00:24:35 It focuses on creating the pledge and redirecting the user to the pledge show.
00:24:42 By following the single responsibility principle, we can clarify the intended function of the action.
00:24:49 Hence, when someone extends the code, they can focus on side effects only if they need to.
00:24:56 We also gain a more usable API.
00:25:04 After examining my service object, I can categorize the operations into four distinct groups: creating followers, backfilling data, creating badges, and sending notifications.
00:25:12 Once we narrow the scope of the code we’re reviewing, it's much easier to reason about it.
00:25:20 We can gracefully name these tasks in a more usable manner, making the post-pledge service easy to understand.
00:25:28 Introducing POROs also presents us with opportunities for decoupling.
00:25:35 As I move the code into a service, I gain better insight into its dependencies.
00:25:41 Specifically, when I look at the mailer being sent, I notice it’s triggered when the created pledge funds the project.
00:25:48 This realization occurred as I attempted to decouple that worker into the service, which clarified its requirements.
00:25:57 This approach provides us a stronger foundation for refactoring complexity and decoupling objects.
00:26:04 I considered setting a variable called newly funded, which evaluates to true or false.
00:26:11 This would simplify that gnarly logic being passed into our service.
00:26:18 Decoupling that into a simple boolean will enhance usability.
00:26:25 While performing these refactors, we want our inner objects to be aware of as little as possible.
00:26:31 By saving this as just a true or false variable and passing that to the service, we can support separation of concerns.
00:26:38 Creating more reusable code will increase efficiency in the future.
00:26:45 If the manner we evaluate the last parameter alters, the service object will not require adjustments.
00:26:51 This speeds up our change process significantly.
00:26:58 We’re now going to accept 'newly funded' into the service object, assessing whether to send that email.
00:27:05 I also noticed we can base a lot off 'pledge,' so I’ll encapsulate 'pledge' within the service object.
00:27:12 We can instantiate the backer and project right inside the service.
00:27:19 By introducing this new object for future extensions or modifications, we create a more manageable landscape.
00:27:26 The next developer will appreciate having a clearer platform for extending this code.
00:27:32 They won't have to contribute to the unwieldy complexity already present in this controller action.
00:27:39 This technique is beneficial for untested code.
00:27:46 We can develop a new class or method from existing code and test just that within its block.
00:27:54 Once we separate dependencies from the existing code, we can introduce tests.
00:28:01 After breaking dependencies, we write tests and subsequently implement changes.
00:28:08 Yet, there are times we lack the time or context to break dependencies.
00:28:15 In such cases, leveraging POROs can help us escape a problematic dependency situation.
00:28:22 Let’s suppose we’ve received a request to enhance an existing feature, enabling backers to pay an installment to settle their remaining balance.
00:28:29 Underneath, we need to execute data updates managed by the scheduled payment manager.
00:28:38 The new feature incorporates logging to maintain a record of changes made to payment schedules.
00:28:46 The challenge here is that the manager is complex and untested, making it hard to uncover all its components.
00:28:53 Thus, let’s introduce a new class for this behavior, specifically the LoggingScheduledPaymentManager.
00:29:00 We will require it to respond to all the methods originally defined in the scheduled payment manager.
00:29:09 Therefore, I will extend the scheduled payment manager.
00:29:15 This will allow it to respond to all the same method calls.
00:29:22 Now we can incorporate the existing scheduledPaymentManager object into our newly created logging class.
00:29:29 We’ve successfully created a new class that we can use to maintain the behavior of the scheduled payment manager.
00:29:36 But in this new class, we can integrate that extra functionality and develop it using TDD.
00:29:43 Let’s add a logScheduledPaymentChange method and test drive this new feature.
00:29:50 We’ve now carved out a space to add this new method without adding to the former dependency complexities.
00:29:58 The next step is to call our new method in place of the function we’re trying to extend in the scheduled payment manager.
00:30:05 Ultimately, we would call the existing method, consolidateDueBalance.
00:30:12 Now our wrapper class, the LoggingScheduledPaymentManager, can behave like the scheduled payment manager while sharing its API.
00:30:18 This means it will execute the consolidateDueBalance method yet also incorporate the needed logging behavior.
00:30:25 Finally, we need to replace calls to our existing scheduled payment manager with this new wrapper class.
00:30:33 This transition should work seamlessly as it maintains the same API as the original object.
00:30:39 This method ensures that the code calling it won't recognize it as a different object.
00:30:47 What does this demonstrate?
00:30:54 This technique is called the wrap class technique, specifically aimed at addressing dependency situations.
00:31:02 We can’t decouple these dependencies due to a lack of time or context, making it easier to create a temporary solution.
00:31:10 This is helpful in situations where we can't afford to expand an existing class that already has independent behavior.
00:31:18 Or we’re faced with a large class that we want to keep from becoming even larger.
00:31:25 While this may seem like a significant change, it’s crucial to recognize the complexity already lurking in existing methods.
00:31:33 We could easily have added to that complexity instead of managing it.
00:31:40 This new procedure enables us to cultivate a narrow slice of tested code.
00:31:47 Often, the biggest hurdle to refactoring difficult code is the complexity we face.
00:31:55 Introducing incremental improvements empowers us to outline a roadmap for the rest of the team.
00:32:02 It provides initial tools to help tackle that complex, untested code.
00:32:09 Sometimes undertaking these changes inevitably adds complexity to our code.
00:32:16 While implementing incremental improvements, we must exercise patience with temporary challenges.
00:32:22 We are steadily charting a path toward breaking dependencies and refactoring legacy code.
00:32:30 This is a quote from a talk entitled 'All the Small Things' by Sandy Metz.
00:32:39 In this talk, Sandy walks through refactoring and simplifying specific code in interim steps.
00:32:46 Throughout these steps, it often seems the code grows more complex before it gets simpler.
00:32:54 However, if we’re committed to an object-oriented design, we should have faith that we’re moving towards greater simplicity.
00:33:02 We must recognize that the complexity faced today is worth the trade-off for that clearer end state.
00:33:10 The reality is we don’t always have time to accomplish everything in one ticket.
00:33:17 But we exist in a developer ecosystem, and sometimes taking one step will set the path for the next developer.
00:33:24 They may then continue to the next milestone on their journey of small steps.
00:33:31 POROs are our mightiest tool.
00:33:38 The beauty of POROs is that they create a clear delineation or seam in our code.
00:33:45 They offer strategies for growing Greenfield code in evolving classes.
00:33:52 They allow us to extract domain concepts that are easily mutable.
00:33:59 By leveraging POROs, we can break large complex classes into smaller objects with minimal coupling.
00:34:06 When dealing with legacy code, they let us refactor objects we can then use to build more flexible and faster-deployable code.
00:34:12 POROs grant new beginnings—whether literally in a new file or metaforically at a decision point, we create a new seam for modification.
00:34:19 The next time we grapple with new or existing code, I hope we turn to our simple, plain POROs.
00:34:26 Let’s strive to leave our codebase a little better than we found it, inviting seams of simplicity into our design.
00:34:32 Thank you so much for attending 'Plain Old But Mighty.' I don't have the time to answer questions here.
00:34:38 However, I will be around in the hallway if you have thoughts or inquiries.
00:34:44 Feel free to email or DM me; all complaints or critiques will be managed by Morty!
00:34:50 Here are some inspirations for my talk, and I highly recommend Michael Feathers’ book.
00:34:56 It outlines various strategies for tackling dependencies in legacy code.
00:35:03 I want to extend a special thanks to everyone who offered feedback on my talk, including my dad.
00:35:09 He’s attending his second RailsConf, so let’s give him a round of applause!
00:35:16 Thank you for coming!
Explore all talks recorded at RailsConf 2024
+33