Max VelDink

Refactoring: The ASMR of Programming Talks

Refactoring: The ASMR of Programming Talks

by Max VelDink

The video presents a talk by Max VelDink at the Blue Ridge Ruby 2024 conference, focusing on the theme of programming refactoring, likened to the ASMR experience due to its satisfying nature. The talk emphasizes the importance of refactoring code for clarity, maintainability, and overall satisfaction within software development.

Key points discussed include:

- Max's Background: He is a staff software engineer with over a decade of experience, focusing on Ruby applications and education for new Rubyists.

- Refactoring Defined: The session aims to demystify the process of refactoring, drawing inspiration from legendary figures in Ruby programming, like Sandi Metz and Katrina Owen, who have significantly influenced modern refactoring practices.

- Audience Interaction: Max engages the audience by asking for suggestions on refactoring methods, emphasizing the collaborative nature of coding improvements.

- Identifying Issues: He presents a code example, highlighting common pitfalls such as the use of ternary operators and lack of test coverage, asserting their impact on code readability and maintainability.

- Refactoring Process: The process is methodical, beginning with adding test coverage to ensure that refactoring does not break existing functionalities. Then, Max elaborates on renaming classes and reducing complexity by simplifying method interfaces.

- Improving Readability: By breaking down complex operations into simpler, more understandable methods, Max illustrates how clarity can enhance code maintenance and the developer's experience.

- Testing Updates: The talk emphasizes keeping tests updated alongside code changes, demonstrating how expressive test cases can improve understanding of the system's behavior.

- Final Thoughts on Refactoring: Concludes with a reflection on the satisfaction derived from clean, well-organized code, encouraging engineers to continually engage with code revisions and legacy systems as part of their growth journey.

In conclusion, Max encourages developers to actively participate in their projects, reflect on their coding practices, and embrace the challenges presented by legacy code, all of which foster personal and professional growth in software engineering.

00:00:12.000 Max is a staff software engineer who has created full-stack web applications for more than a decade. He is a man of a certain age, and I dare say it sounds cooler when we say 'decade' instead of 'nine years.' His focus is on combining the best of object-oriented and functional philosophies while exploring novel architectural approaches, especially for Ruby applications. Teaching the next generation of Rubyists is incredibly important to him, and he is currently developing modern Ruby courses to replace the Ruby boot camps that have gone out of favor.
00:00:29.640 With that introduction, Max is going to talk to us about the ASMR of programming talks. All right! Good afternoon. I will try to be as entertaining as possible during this last talk, but it doesn’t bother me if you need to take a nap while getting ready for the happy hour. Today, we are here to discuss refactoring.
00:01:37.560 Cool. I'm Max VelDink, and I work on payroll and tax software at Justworks. Here’s my plug: we're hiring in New York City and Central Florida if anyone is interested. If not, that's okay too—just don’t tell me about it! I also love open source; I am currently working on the Ruby SDK for the Open Feature Project and am really into Sorbet and its extension gems if you like type systems.
00:02:06.479 So let’s dive in. This talk will be divided into two parts, heavily inspired by some incredible refactoring talks from nearly a decade ago. In the first part, we will refactor some code together and try to capture that ASMR feeling while doing so. I want you to notice how the refactoring makes you feel because great programmers love discussing their feelings.
00:02:25.040 If you don't already know, Sandi Metz has been influential in my career; I read 'Practical Object Oriented Design' at least once a year and teach it every other year. Katrina Owen has also given foundational talks that I consider important. Seeing their thought processes and approaches to refactoring has been invaluable for me. It's a genre we seem to be missing lately; I haven't seen a good refactoring talk in a while. We even did an internal one at our company, inspired by these talks. I highly recommend checking out their work, and I’ll provide resources at the end of this talk.
00:03:29.360 Now, let's park here for a moment and take a look at some entirely original code, definitely not based on anything I’ve encountered in the wild. We’ll move into the audience participation part of this discussion. I want you to shout out some refactoring ideas while taking a moment to digest this beautiful code. If we were to touch this part of the codebase, what are some things you would approach first?
00:04:03.000 Some common suggestions I hear include shortening methods, ensuring meaningful names, eliminating ternary operators, and using early returns. You all have great ideas. Here’s a compilation of those suggestions. We won’t implement all of them, of course, but this talk focuses on a specific class whose public interface is accessed via a class-level method. We have all done this for the sake of expedience, yet now we have an opportunity to refactor and discover more about the interface of this class.
00:05:41.319 We’ll also address the fact that the second argument is a symbol that users must know in order to call this method. While we have documentation (which is nice), the domain of symbols is almost limitless. To improve clarity, we should be more explicit about what that operation is. Duplicate ternary operators performing the same check can become difficult to read, especially as they compound in complexity. Lastly, there's no test coverage for this file, which is a bit of a surprise!
00:06:43.200 So let’s address these issues. First, we should add some tests to increase our coverage. This way, we can begin the refactoring process without breaking the existing code excessively. There’s a push-pull dynamic here; while I could spend a lot of time examining the code to document all possible pathways, I chose to focus on the most common paths by testing the happy paths first using simple RSpec.
00:06:55.559 Here's how we do this: we describe the current method, testing it for a vanilla order by passing in a symbol with a form object, and then we have a return for an ice cream order. I can definitely tell I work with payroll software, right? Once we have some basic test coverage, we can clean up the class method. There’s no need to change the module; instead of using a generic utility class that invites undesirable behavior, let’s be specific about what this code does. We'll call it the 'Recipient ID Resolver.' When naming things, if you can't think of something, simply add 'ER' to any noun, and voila, you have your name!
00:07:45.800 Now, let's examine the supporting code around this. I've moved over large chunks from the original code. Since we are no longer using a class-level method, we can pass in state. Our form will pull out the values we actually care about. After looking at the big file, you might have missed that we only care about two data points: who the recipient is, and whether they are using a bakery ID. We expose these values with bare words, allowing access throughout the rest of our class. Now, our resolve method becomes the public interface.
00:11:47.360 It’s worth noting we haven’t yet messed with those ternary operators, but at least now we can read this more clearly and understand its workings. If we're using the bakery ID, we will determine whether to choose between the prettier ID or the more basic ID. If the baker ID is a placeholder, we retrieve something from there and multiply it by an empty space; if the baker ID is blank, we will revert to the existing behavior. It might still be unclear what this is doing, but at least it’s running within an instance method!
00:12:04.760 Next, we need to update our tests to reflect the changes made to the call sites throughout the app. Now that we have a more RSpec-oriented test, we'll introduce the Baker ID Resolver, taking in the cake ordering form. We’ll set up our world here, using Factory Bot and memoization. We will keep our two existing tests but now pass in vanilla so as not to have to run the cake ordering form every single time.
00:12:18.560 For our implementation, nothing groundbreaking occurs, but at least we've cleaned the file up. The second thing we want to focus on now is that symbol used for the second argument. Often, we want to give our users options, but we’re uncertain how to provide them, so we revert to a symbol anticipating it will go on to hold more values over time. In reality, we often have only two or three options, making this additional complexity unnecessary.
00:12:46.000 Instead of requiring knowledge of the symbol, let's break it down into two methods that users can intuitively call. Rather than crowding everything into a single public API, we’ll expose two separate options—this makes things easier for users. We can then remove that constant as well. Focusing on the resolve for vanilla method, we have retained the same flow but broken out all the ternary operations. Now, each step is significantly clearer.
00:13:10.040 If we're using the bakery ID and it’s a placeholder, we’ll return nine empty strings. If it’s blank, we’ll do the same. Eventually, we’ll return the baker ID, ensuring readability and clarity throughout the code. The satisfaction from seeing a neat, organized code piece is truly a wonderful feeling. Now let's extend this to the ice cream method, where the same principles apply. We can clearly observe which components are changing as we update.
00:14:14.100 Now, our tests are more expressive, effectively documenting the new behavior. We will maintain the two describe blocks for vanilla and ice cream, which ensures we cover all essential public API use cases. The issue of multiple ternary operations that made things complicated in the past can now be disregarded. With the second argument cleaned up, we have less to worry about, allowing for a more efficient system.
00:14:27.779 With these two methods, constants for default values are now defined, eliminating unnecessary operations with empty strings. As we examine the current state of the code, we see it's much cleaner and straightforward, yet that nagging feeling still persists a bit, asking if these methods shouldn't be combined. It’s important to know when we've done enough in refactoring; this structure is set to be more modifiable in the future.
00:14:55.720 In conclusion, our test suite has improved dramatically. Each describe block providing context for the corresponding method makes testing more effective and understandable. In the past, the bakery ID was causing redundant use of constants, but with the new structure, that confusion is resolved. We feel relieved now that we've completed the system cleanup and have clearly mapped out the pathways. Refactoring can provide a sense of satisfaction, much like watching someone perform a clean and precise task.
00:16:01.320 Is it better, you ask? Yes and no; it's somewhat relative. Perhaps that code didn’t even need to be changed, yet my instincts pushed for improvement as soon as I laid eyes on it. If it hadn’t been modified, we might still have needed to stay adaptable, confirming that changes wouldn’t affect existing workflows. This ongoing discussions around code refinements are typical in our environments, leading towards more maintainable solutions.
00:16:50.919 Visibility has been enhanced, allowing easier bug detection and flow comprehension. The code now lays open, revealing potential improvements or needs for adjustments whenever future forms arise. As our engineering teams expand, understanding each block's utility in shared code becomes critical. This increased awareness leads to an abundance of opportunities for collaboration instead of a budding sense of fear or hesitation to modify original scripts.
00:17:08.989 Now, switching gears, how are we feeling? Hopefully better, perhaps akin to an unexpected crunch of fresh produce! It’s okay if the thought of chalk on a chalkboard sends shivers down your spine. Watching a favorite show or strumming along to a game can certainly lift our spirits. It’s important to realize the more active our engagement, the more substantial growth we can achieve.
00:17:36.800 As a call to action, let's avoid simply seeking pleasure from watching others. Transitioning from passive observation to actively participating in your projects will build stronger instincts and enhance your own craft. Pursue opportunities to refactor, examine, and dissect your own code to identify potential smells before they grow into larger issues. Engage with useful resources that can help inform your instincts about code behavior.
00:18:06.560 Finally, don’t shy away from assessing legacy applications; they offer profound learning experiences that propel your evolution as an engineer. Supporting code that has been around for a decade can teach invaluable lessons much more than Greenfield projects. Every project evolves into legacy code with time; thus, familiarizing oneself with existing structures is essential. Your growth relies on your engagement with these challenges, identifying what feels right versus wrong during evaluations.
00:19:12.279 I know I have thrown a high number of suggestions and resources at you, but immerse yourself in gaining knowledge. Therapeutic refactoring is a fantastic resource that I frequently revisit to discover fascinating insights into refactoring best practices. 'Practical Object Oriented Design in Ruby' is a must-read, along with '99 Bottles of OOP' for light learning experiences. Lastly, take the time to peruse 'Refactoring: Improving the Design of Existing Code’ to equip yourself with a deeper understanding of refactoring nuances.