Functional Programming

Simple and Elegant Rails Code with Functional Style

Simple and Elegant Rails Code with Functional Style

by Attila Domokos

In the video titled 'Simple and Elegant Rails Code with Functional Style,' Attila Domokos presents insights on improving the quality of Rails applications by incorporating functional programming principles. He emphasizes the importance of reducing code complexity to enhance understandability and maintainability.

Key Points Discussed:
- Experience Background: Attila shares his transition from .NET to Ruby on Rails and mentions his recent job transition.
- Common Coding Issues: He highlights the challenges of working with overly complex codebases, exemplified by an Orders model with 2700 lines of code and methods exceeding 200 lines.
- Importance of Code Clarity: Attila stresses that poorly organized code can reflect negatively on developers and impact business operations.
- Code Complexity Analysis: He introduces 'Flog,' a tool for analyzing code complexity, discussing its usability in assessing Rails projects and how high Flog scores indicate problematic code.
- Refactoring Techniques: Attila demonstrates the significance of refactoring, such as method extraction, to simplify controllers and models, showcasing concrete changes in Flog points before and after refactoring.
- Service Objects: He encourages the use of service objects to encapsulate logic and maintain the single responsibility principle, which improves testability and clarity.
- Naming Conventions: A transformation in his naming strategy from nouns to verbs enhanced the clarity of service objects, promoting a better understanding of functionalities.
- Framework for Organizing Actions: He describes the development of LightService as a lightweight gem that facilitates good structure in service calls, emphasizing simplicity and organization in larger codebases.
- Warning on Complexity: Attila cautions against unnecessary complexity, suggesting that the use of Flog should guide developers in restructuring rather than just filling code with complexity.

Conclusion: The talk concludes with a call to action for developers to strive for elegance and simplicity in their code, paralleling it to the functional elegance of minimalist designs. Attila encourages participants to embrace structured approaches such as using the LightService gem to retain clarity and functional integrity in codebases, ensuring not just effective development but an enjoyable coding experience.

Attila leaves the audience with his social links for further engagement, inviting them to pursue more information on the discussed methods.

00:00:16.320 Hello everyone. The title of my talk is 'Simple and Elegant Rails Code with Functional Style.'
00:00:22.560 I’ve been working professionally for three years now. Prior to this, I spent eight years in the .NET space.
00:00:28.960 Someone mentioned there’s a bit of feedback. Can you all hear me?
00:00:34.239 Great! So, I spent eight years in the .NET space, and I was fortunate to switch over to Ruby and Ruby on Rails.
00:00:41.280 I was laid off about two months ago from my previous job.
00:00:46.640 I recently found a new job in Chicago, so I'm moving from Northeast Ohio to Chicago.
00:00:52.559 Is everyone okay? Anyone here from Chicago? I hope to see you guys there soon.
00:00:59.199 Currently, I work for a company where I’m the only developer. We are looking to hire additional team members.
00:01:05.680 Our company specializes in the franchise space, and we provide numerical results for hiring decisions.
00:01:11.360 Instead of relying on gut feelings, we make decisions based on numerical results that can help identify the best candidates.
00:01:17.759 I’m not going to teach you functional programming in this talk, nor am I going to try to overly bend Ruby into a functional programming language.
00:01:23.360 My goal is to show you how to write simple and elegant Rails code.
00:01:29.119 In my previous job, I had a 25-minute commute to work. I used to listen to the radio, but I grew tired of the commercials.
00:01:34.560 I decided to start listening to audiobooks, and a friend recommended 'The E-Myth Revisited.' I remember a particular sentence from the book: 'The work you do is a reflection of who you are.'
00:01:42.079 When I see someone who does a lousy job, I don’t just think about the job itself; I reflect on what kind of person they are. This can be applied to our profession as well.
00:01:49.680 The quality of the code you write can be a reflection of your own character.
00:01:55.600 Recently, I went bowling with my son and drove on a road that looked terrible.
00:02:00.719 I was worried I would lose one of my car's wheels. As I stepped out, I could see cars passing by recklessly.
00:02:05.759 I even took a photo to document how hazardous the road looked. It seemed like it took over a year to reach that state.
00:02:12.480 They attempted a fix with tar, which may last through winter, but come spring, the pothole will likely return, if not worse than before.
00:02:18.239 Last year, I stumbled upon a news story from China where the government needed to build a highway but a person refused to sell his apartment.
00:02:24.480 What they did was build the highway around the apartment instead.
00:02:29.680 A couple of weeks later, the owner finally agreed on a price, and the highway was completed.
00:02:35.200 I thought that was an unbelievable story, but it was soon overshadowed by another news item.
00:02:41.200 A family in a similar bind refused to remove the remains of their relatives, and the builders ended up constructing the foundation around the graves.
00:02:46.560 They literally built around the gravesite.
00:02:51.920 Do you remember what your room looked like when you were 15? Mine looked like a mess.
00:02:57.280 I would often spend hours looking for my textbooks or jeans.
00:03:02.800 I'm not proud of it, but that was my reality.
00:03:09.040 This here is a model from a Rails application I once worked on. It's an e-commerce application, and this is the Orders model.
00:03:14.239 It contains 2700 lines of code. I’m not joking. The 'Calculate Shipping' method alone has around 206 lines of code.
00:03:20.239 Right underneath that is the 'Recalculate Shipping' method, which has 197 lines of untested code.
00:03:25.760 We wanted to tackle this issue, but it seemed like too overwhelming a task.
00:03:31.840 When you encounter code like this, think about real companies that have similar size codebases. I was laid off from that company due to financial reasons.
00:03:37.200 Really, this type of complicated code can bring down a company.
00:03:44.080 To add insult to injury, this code was not written by contractors, but by employees before I arrived.
00:03:50.400 I wanted to find a good tool to analyze code complexity and stumbled upon 'Flog.' Does anyone here know about Flog?
00:03:56.480 Awesome! I spoke with Jim Weirich at another conference, and he said he used 'Flog' against a project he had never seen before.
00:04:03.599 It quantifies the code without needing to open up the classes, providing a score based on code complexity.
00:04:08.799 The higher the score, the more challenging it is to test that code.
00:04:13.600 This is a central concept in my talk today.
00:04:19.199 Installing Flog is straightforward; add it to your Gemfile, preferably in the development or test group.
00:04:24.880 Installing through RubyGems is a breeze. You can run it against a class or a group of classes.
00:04:30.000 I typically run it against controllers and models. With 95% accuracy, I can assess what kind of code I'm dealing with.
00:04:37.120 The video you saw earlier mentioned a staggering 25,902 Flog points.
00:04:44.000 The ideas I'll discuss stem from my work on various Rails applications.
00:04:50.480 To illustrate my points, I wanted to share a tracking application where users provide a category, amount, and description.
00:04:55.680 It's important that this app doesn’t just save values into the database; it should also do some parsing.
00:05:01.039 For instance, when you provide a value like 'running 3.2.12', it should save it to a category table with the corresponding details.
00:05:05.200 This data can then be filed under today’s date unless overridden by the user.
00:05:11.040 Now, let me ask you: Where do you put your domain logic? How many of you place it in the controller?
00:05:17.040 Raise your hands. Now, how many put it in the models?
00:05:22.000 That’s about a third of the room. How about the remaining two-thirds? Where do you place it?
00:05:27.440 Interesting! I thought this was a common history, but the codebase I inherited was quite heavy on controllers.
00:05:34.159 I came across controllers with around 1750 lines of code, which is not an uncommon practice.
00:05:42.079 When I interviewed contractors, one of my questions was, 'How many units do you have?' Their answer was zero.
00:05:48.240 Yes, I joined them because despite this, I believed their business idea was strong and had potential.
00:05:54.480 We definitely need to clean that application, and I hope to return one day and share our success story.
00:06:01.920 What I attempted to do was integrate parsing logic right into the controller action, and this is how the codebase appears now.
00:06:09.040 Don't worry about understanding the details; the point is, this code isn't giving us clarity.
00:06:15.360 There are 48 lines of code here, and it's not performing well.
00:06:22.160 The Flog value comparison shows how the controller's complexity overshadows the model’s.
00:06:28.160 Thanks to the extraction method, I managed to reduce the line count from 48 to 25.
00:06:34.720 Following the refactoring exercise, we noticed Flog values changed as well.
00:06:41.920 Now, instead of a hefty 40-plus Flog point action, we have reduced it to a manageable 20.6.
00:06:47.239 This reflects that refactoring via method extraction is beneficial.
00:06:54.080 Through this process, we realized that using logic from the controller can be tricky.
00:07:01.920 I moved the parsing logic from the controller into the model, which improved clarity.
00:07:07.200 Now, instead of complex controllers, we have simplified our approach.
00:07:12.240 The model's Flog value shot up to 84 while the controller's value dropped to about 25.6.
00:07:18.399 That’s a significant improvement! Classes with a Flog point around 20 to 30 are manageable and understandable.
00:07:25.440 I want you to remember that enormous room clutter I showed you earlier—it had 2500 lines of code.
00:07:31.920 Three years ago, when I started working on a Rails application, the code was already four years old.
00:07:37.200 I asked other developers how many unit tests we had, and their response was zero.
00:07:42.560 They explained it takes too long to run the existing tests, with one spec taking 17 seconds.
00:07:48.000 This led to my early decision to move business logic out into services.
00:07:54.560 I even wrote a blog post on it that received quite a bit of attention.
00:08:00.640 Initially, I stubbed everything out of Rails extensively, but with these techniques, I managed to run extra specs under one second.
00:08:06.880 The code didn’t change much after this shift in approach.
00:08:12.160 I began calling a service instead of the model directly, which is just a plain Ruby object.
00:08:19.040 Are there any of you following this method of using plain Ruby objects for services?
00:08:25.759 Now, I’m changing focus to illustrate how I represented Flog values.
00:08:31.440 I moved from comparing controllers and models to comparing models and services.
00:08:36.000 As you can see, the Flog score has readjusted. The model had fallen back to 38.4 while services scored 71 points.
00:08:42.000 Here’s how my initial service class looked.
00:08:50.960 Each entry method calls other methods on the service. By the end of the call sequence, the instantiated track object is returned.
00:08:56.560 This structure allowed me to test every method, but it got complicated.
00:09:03.440 When I modified method signatures, unit tests began to fail, resulting in significant headache.
00:09:08.480 I opted to only have one public method in the service class while keeping everything else private.
00:09:15.679 This allowed me to modify method signatures more effectively. However, setting up tests became burdensome.
00:09:23.040 These service objects were violating the single responsibility principle.
00:09:29.439 Consider what happens when your customer comes back with additional requirements.
00:09:36.000 You have to think about where to put that new functionality and how to scale.
00:09:42.240 A year and a half ago, in January, I was in a coffee shop helping a colleague with a testing issue.
00:09:48.240 I spent 10 minutes trying to identify a failing spec due to dependencies I wasn’t recognizing.
00:09:54.239 Before continuing, I want to emphasize naming.
00:10:00.560 Instead of naming services after physical objects, I started using verbs that describe actions.
00:10:06.400 For example, ‘parse_feed’ clearly indicates its functionality.
00:10:13.440 I attended a coding retreat and was advised to read a piece titled 'Execution in the Kingdom of Nouns,' which changed my perspective on naming.
00:10:20.000 As the complexity increased, so did the size of my methods.
00:10:27.040 Instead of allowing them to grow lengthy, I created subclasses as needed.
00:10:34.000 This approach made testing more accessible since I could stub and mock the dependencies effectively.
00:10:40.040 Yet, I still didn’t feel completely satisfied with the clarity.
00:10:46.720 My goal was to achieve a uniform interface with a method that took context as an argument.
00:10:53.760 The reasoning was to simplify testing by requiring a single argument.
00:11:00.000 As I mentioned, I spent eight years in the .NET space learning design patterns.
00:11:07.200 I loved the Chain of Responsibility design pattern. It made sense to handle multiple steps in a process.
00:11:13.760 Although I attempted to implement this in Ruby, it felt cumbersome.
00:11:20.000 As I navigated through coding exercises, I realized two object types emerged.
00:11:29.600 The organizer object helped narrate the process flow.
00:11:35.760 It starts with splitting the feed into parts and validating the information.
00:11:42.320 By situating code around these processes, you can read the flow.
00:11:50.000 The actions represent the core logic of this process.
00:11:57.480 Each action encapsulates a method that does its job and returns a context.
00:12:03.920 After refactoring that bulky service class, I was pleased to see Flog points reduced.
00:12:12.080 My Flog average for the service class fell from around 70 to 13.9.
00:12:19.040 Classes with lower Flog values are much easier to grasp.
00:12:25.520 Moreover, my actions are not stateful, meaning they are cleaner and simpler.
00:12:31.760 The context serves as a conveyor belt in an assembly line, passing from one action to another.
00:12:38.560 The context object defines a well-structured format for the data.
00:12:44.480 It’s instantiated by the organizer object, which pulls data as needed.
00:12:51.360 Actions process this data in sequence, and if an action fails, the subsequent actions won’t execute.
00:12:58.720 As I transitioned from one project to another, I found a pattern.
00:13:05.120 I developed a lightweight gem called LightService.
00:13:12.320 It’s simple, featuring only two classes.
00:13:18.720 The LightService organizer handles data feeding and action processing.
00:13:25.520 The action class contains the method to be executed if no failure occurs.
00:13:32.160 A friend pointed out that it’s difficult to remember the guard condition and execute methods.
00:13:39.200 In response, we created a macro to simplify this.
00:13:46.080 This macro eliminates the need to memorize specific guard conditions.
00:13:52.480 So, if you want to simplify your approach, check out the macro.
00:14:00.160 However, as software grows, so does its complexity.
00:14:06.720 Every project begins smoothly, but after two years, even minor changes require extensive effort.
00:14:13.040 To study growth, I experimented with a controller action containing an extracted method.
00:14:20.160 On the left, you’ll see the code complexity of the controller, while the right shows a series of actions.
00:14:26.400 In its original form, the coding complexity was at 71 for the actions.
00:14:33.040 After duplicating method and class names, the controller complexity skyrocketed to 124.2.
00:14:39.679 But the actions maintained their lower complexity, remaining around 13.
00:14:46.240 If I had continued to extend them, that Flog value would’ve reached unsustainable heights.
00:14:52.720 Remember, if you add complexity to a codebase, it doesn't just disappear.
00:14:59.680 Use Flog as a guide for when to split objects or functionality.
00:15:07.040 A warning: I won't use this pattern inappropriately.
00:15:13.440 We should recognize when the complexity of business logic doesn't necessitate it.
00:15:19.440 I still take advantage of Rails for simple updates.
00:15:26.320 As soon as conditionals or iterators creep in, I extract that logic to a service.
00:15:35.440 If that service does multiple tasks, it’s split into actions and an organizer.
00:15:42.000 This has become my guiding principle.
00:15:48.960 When you're faced with a requirement change, encapsulate it in a new class.
00:15:55.360 This keeps your codebase tidy and manageable.
00:16:02.080 Think back to that cluttered 2500 lines of code I mentioned.
00:16:09.680 Conversely, consider the simplicity of a minimalist hotel room in Japan.
00:16:15.360 Something so simple yet functional.
00:16:20.440 Let this hotel room symbolize clear, concise action.
00:16:26.880 Now, here are my social links. I’ll leave this up for you.
00:16:33.840 Thank you for your attention.
00:16:38.560 You can find my slides at this link: bitly/ad_at_railsconf. I've created and shared this link this morning.
00:16:44.159 Once again, thanks for your attention!
00:33:30.960 (End of talk.)