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.)