Talks

Into the Heart of Darkness: Rails Anti-Patterns

Help us caption & translate this video!

http://amara.org/v/GUQN/

GoRuCo 2009

00:00:19.279 Um, welcome back to everyone from lunch! My name is Jake Howerton, and this is my presentation about things you shouldn't do in Rails. At lunch, Ben from Pivotal was saying to me, 'You know, where are these wide-eyed optimistic presentations? How come nobody's super excited?' I replied, 'Ben, I'm sorry to disappoint you, but my presentation is on next. So if anybody else was hoping for that, this is probably not going to be an optimistic presentation.' So according to the title of my talk, I guess it falls on me to expose the dirty underbelly of the Ruby and Rails community. Hopefully, Francis screened out all the Pythonistas from the attendance list, and nothing’s going to get trolled on Reddit that I put up here.
00:00:50.480 I've been working with Ruby on Rails for the past four years, which is quite a long time since Rails is only five years old. When I hear myself say that out loud, it feels strange to think it's been that long. It seems like just yesterday that I went to Vancouver for the first RailsConf, and all these ideas started formulating in my head. The amount of code that I've written, that the community has written, and that all of you have written over that time is huge—millions of lines of code and probably millions of bugs, right? And now we're at a point where we have to deal with these sorts of issues, specifically around applications that have been in the wild for four or five years. What happens when a Rails application has lived that long? How do you deal with the problems that we've introduced into the code?
00:01:38.400 I know in the first six months of using Rails, I can only pretend that I wrote bad code back then. I can remember specific examples of horrific code I wrote in those early months, and I constantly encounter applications with code that still resembles that. Let's not pretend that nobody does this. I do it; I've been doing it for four years. Even the Ruby and Rails gods in our community, people that we all look up to, write terrible code sometimes. It's a fact of life that no matter how good you get, you'll write bad code in the future. The one thing we can do about it is to recognize when we're making mistakes; we can look at common errors, teach each other about the consequences, and just try to do it less in the future, fixing issues as soon as we start to veer off course.
00:02:56.160 Some people might say you need to work for a certain number of hours to become an expert. I've heard something about ten thousand hours—something to do with a paper that claims you can be an expert after doing something for that long. You get your expert passport stamped, and poof! You're an expert, right? I don't know how true that is, but we have ways of thinking that help people who aren’t at that ten thousand-hour level. If anyone is familiar with fine arts, there’s a book called 'Drawing on the Right Side of the Brain.' It's a system of thinking that guides people through practice; even a pure beginner can use it, and at the end, they can draw better than they could before. In martial arts, you have kata, which are pre-choreographed sets of moves, practiced repeatedly by both beginners and experts. These aren’t the only methods of fighting or drawing; it means they're proven ways to help beginners and intermediates learn effectively.
00:03:50.720 In programming, we have the concept of patterns, and there are some pivotal works in patterns that everybody hopefully knows, like the Gang of Four book and Martin Fowler's 'Patterns of Enterprise Application Architecture.' Software patterns are frameworks of thinking; they aren't always the right way to do things. In fact, some of the worst code is written when people read these books, see the title of a pattern, and think, 'This is exactly what I need right now,' when it might not actually be right for them. They do somersaults in order to fit their specific problem into the pattern model.
00:04:50.320 So, now that we've established that we have these four-year-old Rails applications, what do we do about it? To define anti-pattern, we should probably start with the definition of a pattern. According to Wikipedia, a pattern is a general reusable solution to a commonly occurring problem in software design. I want to add that typically, we've discovered patterns through emergent practices. You might have one developer in Los Angeles and one in China, while they’re working on disparate systems, but they discover similar ways to solve the same issue. Once enough developers converge on a solution, it becomes recognized as a pattern.
00:05:40.160 In contrast, anti-patterns are emergent practices that are actually harmful and prevent us from solving problems. In layman's terms, anti-patterns refer to mistakes that people repeatedly make, even after being made aware of their potential consequences. In spite of blog posts from three years ago warning about certain practices, they still occur frequently. The most crucial takeaway I want to emphasize is to know your APIs. Ninety-nine percent of problems arise because people aren't familiar with their APIs; they end up re-implementing things or getting chaotic simply due to lack of knowledge.
00:06:48.000 The APIs for Ruby and Rails really aren’t that huge, so there’s no excuse not to familiarize yourself with them. Read the documentation and become well-acquainted with it. When you look at your code or your teammates' code, you should evaluate it through the lens of the API and reflect on how the code could be improved by leveraging more of the Ruby API. Lastly, GitHub is an invaluable resource. Almost all the code is accessible for free. You can follow projects like Rails, monitor change sets, and observe whether bug fixes are going into stable branches or if you’re looking at what Yehuda Katz is doing on the cutting edge.
00:08:09.600 As you work on your projects, if you believe part of your project might be generic, I often spend a couple of hours each month searching GitHub to see if someone else has thought of a more generalized approach that could be extracted into a plugin. But that's a bit tangential; I want to segue now into the first anti-pattern I want to discuss: script plug-in and gem install-itis. Does anyone here know for a fact that they have plugins in their application where no code ever gets executed? Yes? At least one person admits it! I'm sure more of you encounter this issue, especially when working with outside contractors or transient developers, who tend to install anything, thinking, 'Oh, cool! This plugin solves all my problems!' and then they install it without vetting its necessity.
00:09:10.720 If you’re involved with any production application, and you're committing code into it, it’s your responsibility to understand what's included in that code and why you need it. If you’re working on a team, everyone must come to a consensus about using plugins. No one should just throw code into an application without discussion. Always communicate; say, 'Hey, I want to use this plugin because...' and come to an agreement. Ask whether there's a method in the core API that can achieve the same results instead of blindly implementing a plugin.
00:10:14.080 I also want to discuss Ruby's style. I don't consider myself the definitive source on Ruby style—there are many others much more qualified for that, like David Black, Eric Cotta, or Ryan Davis. They would likely be more militant about it. However, when you open an application that's foreign to you, one of the most frustrating things is encountering code that fails to follow well-known Ruby conventions. I want to share a few anecdotes, some of which aren't strictly style-related, starting with some pseudocode derived from actual production applications.
00:10:56.000 Let me outline a few problems. First, I always use the bang operator, so I strongly recommend consistency throughout your application. You must discuss with your team and establish whether to use 'not' or the 'bang' operator. Furthermore, regarding the keyword 'self,' it's unnecessary to use it everywhere; we only need it for assignments. Then, there's the matter of case statements, which people often write incorrectly. Almost every time someone attempts to write one, they get it wrong. I’ll confess that I’ve done that too, but it doesn't mean I stop there once it works.
00:13:16.080 We've seen countless examples of incorrect conditional statements as well. Sometimes people transitioning from other languages aren't fully aware of Ruby operators. As we learn more about Ruby's operators and API, we should prioritize making our code succinct while remaining comprehensible. Here's a closer example of a method that might have hidden problems—it works because it can run, but without context, it’s meaningless, even though it was running in production without anyone noticing. So, my point is how can this be in production code without any relevant bug tickets? It raises questions about whether it's truly actually being used.
00:14:54.720 That leads me to IRB-driven development; IRB is a fantastic tool packaged with Ruby. I frequently use it for experimentation and learning. This method ends up being messy because developers take something that works in IRB, copy it, and paste it directly into the application. In one production instance, there was code responsible for retrieving the IP address of the server it ran on, which functioned beautifully in IRB. Yet, what would happen if that external service became unavailable? It's essential to consider that the practice will only work as long as the original service remains active. Then there's the bang-bang issue, where a developer forces a value into true or false, typically discouraged in predicate methods. It is permissible for truthy values, but using this within case statements or conditionals is bad design.
00:17:25.680 Planning applications over the span of many years is quite challenging, particularly when you're not just working on pure tech applications. Business applications become exponentially more difficult to forecast. I’ve noticed many creative business folks inadvertently create a scenario where they verbalize an incredible idea, but they also assume that the software must already accommodate it. They don’t consider historical context or future work necessary to implement such an idea effectively. Thus, two years hence, they might remember contradictory directives they previously provided, leading to confusion about what they really want in their project plans.
00:19:35.840 One way I deal with this is through Cucumber and Webrat. Cucumber allows you to transform business requests into a storyline format, which you can then run as integration tests using Webrat against Rails. This information lives on in your app, allowing you to revisit it in two years and understand the context when a client requests a new feature or an adjustment in authentication. You can always return and add a feature or adjust it without disrupting any existing functionality.
00:20:48.960 A lot of stagnant applications arise from issues where the teams don't know what parts of the code they can safely change. This is crucial when dealing with unit testing, which can also help clarify the current state of the application. A gem called MetricFu is a wonderful collection of analytics tools, including a specific gem called Reek. This gem focuses on identifying code smells and presenting analyses of them within your application, which helps you catch and address these 'smells' proactively. You can run Reek against your application, and it highlights long methods or other problematic patterns.
00:22:05.760 With Ruby on Rails, you might find yourself needing to change certain things as you test the controllers since the response blocks are often longer than five lines. When you run Reek, it provides feedback, so you’re made aware if methods are beyond recommended lines and can investigate why they are too long. I can confirm that almost every application I work on has at least one method exceeding five lines, and I’ve recently reduced a method of about 80 lines down significantly. When I moved from PHP and got introduced to such tools in Ruby, I barely even remember certain methods I wrote that were 100 lines long. We’ve come to embrace shorter method calls—like user.new and user.authenticate—as a much more efficient approach to coding.
00:24:21.440 I also want to discuss orphans, akin to the plugin issue. You likely have routes or plugins within your application that are utterly unused. Identify these routes lacking execution paths to validate if any elements rely on them while completing testing and integration processes. The more you add or remove features, the harder it becomes to manage abandoned or dead code. Cucumber claims to have support for archiving features; if you successfully establish this adherence, you can visualize which stories are actively utilized and accordingly manage unused features.
00:26:29.920 Concerning disorganization, I’ve seen several Rails applications where the folder structure is a complete mess. Although we are shifting to a more modular framework, if someone feels the need to create a 'concerns' folder in the app's root directory, that’s usually a red flag about underlying issues. You can perform most functions within a Rails application utilizing its standard structure, greatly benefiting junior developers as they get acclimated to Rails conventions. If your team fails to maintain eye contact, disorganization creates problems down the line.
00:29:06.720 Everyone should already be conducting tests, but while testing is praised and positively spotlighted, issues can arise with how tests are structured. I call this the 'valueless test.' From the application owner's standpoint, some tests provide no value—they only confirm that Rails can load certain fixtures and select fixtures from the database. Tests that merely verify database counts without any deeper objective may bulk up your codebase. Additionally, slow tests can be detrimental; if a test suite is so slow due to thorough checks, people will simply stop running them. Coupled tests can also be problematic if they depend on conditions established from previous tests.
00:31:22.560 One common issue is coupled tests connected to external services. For instance, if a test references an external API for something like IP address verification, this may introduce complications into test execution, especially if you don’t have the necessary dependencies installed. In a previous application, we received code from a contractor for generating images—these tests could involve substantial overhead by running against image processing libraries, leading to unnecessary complexity. Ideally, we want to mock any external calls and verify that the commands executed are structured correctly without testing against live, external services.
00:34:08.480 These issues underscore the overall principle that complex tests can indicate complex code. If you encounter significant difficulties in figuring out how to test something or find yourself building excessive mocks, it’s often a signal to revisit your design approach. Recognize when you're behaving in a cumbersome way and fix whatever the root problem is—there is often room for greater simplicity by reevaluating the design.
00:36:13.040 To prevent recurring pitfalls, implement the following: establish clear team decisions and document them accordingly. If the team decides to use certain operators or methodologies consistently, they should collaborate in writing these guidelines into a style guide stored in a place easily accessible to everyone. Make onboarding teams aware of these decisions so they can reference them when needed. Meanwhile, making sure you are writing comprehensive tests, utilizing tools like Cucumber, and setting minimum metrics with tools like MetricFu or Reek will significantly enhance your output. It's essential to employ a build server for establishing rules compliance among team members.
00:39:02.400 Specific tools, such as Integrity built on Sinatra, should be considered, which can handle postbacks from GitHub. This way, your team remains continuously aligned on project standards. Lastly, the idea behind maintaining build artifacts—like documentation, style guides, and rdoc—lies in ensuring they reflect any amendments you make throughout coding changes. A well-structured comprehensive manual is paramount for tracking current states of metrics like Reek or MetricFu. This setup ensures developers can review production matters on a granular level and adequately maintain the quality of their work. I appreciate your attention, and I welcome any questions.