Refactoring

Build to Last: How to design rails apps to avoid a rewrite in 5 years

Build to Last: How to design rails apps to avoid a rewrite in 5 years

by Mike Moore

In his talk at LA RubyConf 2015, Mike Moore addresses the common issue of maintaining legacy Rails applications and shares strategies to design Rails apps that can avoid the need for a rewrite in just five years. He emphasizes that legacy apps often arise from poor expressions of intellectual complexity rather than outdated technology. The presentation covers the following key points:

  • The Concept of Legacy Apps: Moore begins by recognizing the prevalence of legacy Rails applications and the frustrations that come with them, such as dealing with technical debt and unexpected code breaks due to gem updates.
  • Avoiding Rewrites: He argues against the prevalent notion that rewriting an app is the only solution to maintaining software, suggesting that maintaining understanding and expressiveness in code is fundamental.
  • Understanding Software: Moore challenges the conventional view of software simply as instructions for machines, advocating instead that it should serve as a way to manage complexity and communicate ideas among developers.
  • Technical Debt: He explains the metaphor of technical debt and its implications for software development, stressing that it’s not just about poor code quality, but about how well the code reflects the understanding of the problem it solves.
  • Designing for Maintainability: He introduces concepts like 'Code Neighborhoods' to group related objects in a way that facilitates easier communication within an application model, leading to clearer design and maintainable apps.
  • Testing Practices: Moore encourages thoughtful testing practices that revolve around domain interactions and maintaining code clarity, asserting that well-structured tests are integral for longevity as applications evolve.

Moore concludes by urging developers to rethink their approach to application design and maintenance, emphasizing that ownership of the software's success lies with its creators, not just the underlying framework or language. He encourages attendees to engage with his ideas and evaluate their own practices to enhance their coding effectiveness and application lifespans.

00:00:24.080 I love LA! I'm happy to be back. I think that, you know, Kobe and I kind of started in Ruby around the same time.
00:00:30.240 One of the things I'm proudest of is Confreaks. I've heard from developers literally all over the world who have been helped by having conference presentations online.
00:00:44.960 So I have a big love for conferences. I love coming here, meeting with everybody, and I love that magic happens at conferences and we get to share that online. Like I said, one of the things I'm proudest of is that Confreaks is a thing, and I had a little part in starting that.
00:01:06.720 That said, I made a promise that I would talk about how to design Rails apps to avoid a rewrite in five years. I think that this might be something that you are interested in.
00:01:20.320 So hello! I'm super excited to be here. I love going first because then I can enjoy the rest of the conference.
00:01:27.479 Kobe already raised their hands; could we do that again real quick? How many people here are at LA Ruby for the first time? I mean, that's like the vast majority, I think at least three quarters.
00:01:38.759 And then, who's here for their very first Ruby conference? I think it was like half, yeah, about half. Now, who has been using Ruby or Rails for less than two years? That's about a third to a half.
00:01:54.159 And of those folks, who has been programming for less than two years? Okay, so not too many. So about a third have been doing Ruby for less than two years.
00:02:01.799 This is the first conference for the vast majority, so awesome! On behalf of the organizers, speakers, and everybody who's been here before, welcome! We're happy to have you.
00:02:12.680 Okay, I want to talk about a problem. The problem I have seen in the Ruby world, specifically in the Rails community, for about eight years now is that legacy apps exist.
00:02:31.160 It's a bizarre thing for me to think about because just last year we were all on Slashdot reading about the ten times productivity increase of Rails and how that was to be taken with a grain of salt.
00:02:45.840 But all of a sudden, it's like, oh no, that wasn't last year; it was ten years ago. People have been writing Rails apps for ten years and putting them into production.
00:03:02.680 That's been fantastic because Rails is such a wonderfully productive web framework that you can build many great applications.
00:03:14.280 However, that also means that there are literally many thousands of applications out there in the wild that need to be maintained.
00:03:27.840 So, I guess another show of hands: who considers themselves to be working on a legacy Rails app? About a third of the room. And who would prefer to work on a Greenfield app than a legacy Rails app? Almost everybody.
00:03:41.000 So why is that? I have some thoughts. In my mind, the problem is that we have legacy Rails apps or that concept of a legacy Rails app exists. Let's talk a little bit about the pain caused by dealing with legacy Rails apps.
00:04:00.360 What exactly is the pain of dealing with legacy Rails apps? If you have an app that you consider to be legacy, what pain do you feel from it? Why would you consider it legacy and not an active app?
00:04:12.439 Change is hard. Right? Technical debt is a big issue. Yeah, just trying to figure out what's going on can be complex.
00:04:32.759 The thing that kills me is when I have apps that are working fine, and I don't have any world reason to change them, yet suddenly some gem breaks, causing a chain reaction of things that have to be upgraded.
00:04:52.880 Old routing doesn't work anymore, and everything blows up when I just want it to work perfectly, leaving it alone.
00:05:07.639 The gem ecosystem changes underneath you, or there's a hotfix for some vulnerability that gets patched, and all of a sudden, you can't use what you were using before.
00:05:20.440 You end up having to upgrade through multiple generations of Rails. I hate that as well.
00:05:39.960 Alright, so we've got this issue with legacy apps and the corresponding pain we feel.
00:05:53.720 My first reaction is often to just rewrite it. I don't want to deal with it. That reflects something I think about how we view the permanence of the apps we build.
00:06:13.319 For example, if you have an app that's working fine, built on jQuery, using sprinkled JavaScript, and you want to rewrite it in React, you don't really need to rebuild your app to do that.
00:06:38.440 However, a lot of people do think they have to since the presentation concerns are buried so deep into the app that it's tough for it to change.
00:06:45.160 So the question in my mind is: if we have a problem with legacy apps being unmaintainable, how do we build maintainable apps? How do we avoid legacy decisions?
00:07:02.560 I think the real question is: is rewriting to microservices the only viable answer at this point? I don't believe it is. But the pain from legacy apps still exists because they are difficult to change and understand.
00:07:25.560 How can we solve that pain without rewriting to Node and microservices? Can we still have fun with legacy apps? Can we still find that Greenfield excitement in legacy apps? I think we can.
00:07:38.680 Well, the simple answer is: you're doing it wrong. That’s not entirely true; I think you're thinking about it wrong.
00:07:52.320 I've seen this pattern repeat over the past ten years in the Ruby and Rails world and about ten years before that as well.
00:08:10.080 I have this concept to share with you regarding what I think leads to building maintainable apps, and it’s not entirely what you might think it is.
00:08:28.920 To start down this path, let's back up a bit and talk about the basics. What is software?
00:08:42.160 If someone came up to you and asked, 'Oh, you do software, what is that?', what do you say? It's often described as dark magic, science experiments, or even data transformation.
00:09:11.160 However, what I hear most often is that software is generating instructions for a computer to follow. Is that a fair assessment?
00:09:31.120 I think that perspective is a bit wrong. The emphasis on software meaning instructions for machines is misplaced.
00:09:53.200 Has anybody seen the book 'The Structure and Interpretation of Computer Programs'? It's a consequential book, authored in the 1980s, for MIT's introduction to computer science program.
00:10:14.720 This book reflects two major concerns. First, it stresses that a computer language is not just a way for getting a computer to perform operations.
00:10:24.240 Instead, it serves as a novel formal medium for expressing ideas about methodology. Thus, programs must be written for people to read and only incidentally for machines to execute.
00:10:41.360 Second, the essential material to be addressed at this level is not the syntax of a particular programming language, clever algorithms for computing functions efficiently, nor the mathematical analysis of algorithms.
00:10:57.680 Instead, it revolves around techniques for controlling the intellectual complexity of large software systems.
00:11:06.720 When I first read this, it jumped out at me. I had never viewed software as a means of controlling intellectual complexity, which fundamentally changed my perception of my job.
00:11:29.880 When we craft software, we are crafting ideas, managing complexity to communicate with others. So our task is not merely to write instructions for machines.
00:11:46.960 It is to craft and express ideas, making us workers of knowledge. Through this, we create from nothing but ideas, which is fascinating and noble.
00:12:04.560 This endeavor is incredibly challenging, but not many can get paid to work solely with ideas.
00:12:12.560 Later, it mentions how computer science is not a science, and its significance isn’t grounded in computers. The computer revolution alters our thinking and the way we express it.
00:12:28.360 Let's reflect on how we approach software. We often rewrite apps not because they're too old, but because they're not effectively conveying their intellectual complexity.
00:12:47.560 The computer doesn't have difficulty running the app; it just continues to execute unless there are dependency changes. The problem that prompts a rewrite boils down to understanding it.
00:13:06.560 Ultimately, other programmers hold more importance than the computer regarding how we write code. We need to better express our thoughts about software.
00:13:17.840 Has anyone here heard the term 'big ball of mud'? This term describes a system that is casually structured, dictated more by expediency than design.
00:13:34.440 This likely rings true for many legacy Rails apps. Here’s a link to the paper about it, which I encourage you to read.
00:13:55.720 If you find yourself with a big ball of mud application, it tells you that the person in charge did a poor job of expressing the app’s intellectual complexity.
00:14:12.240 That's disheartening, and unfair, especially since you have the intelligence to understand it. Embrace that frustration and remember how that feels.
00:14:28.960 We frequently feel this in our software, so let's work to improve our expressiveness.
00:14:38.320 Alright, I'm almost done setting up the problem. Does anyone here know about technical debt?
00:14:45.160 About half the room. Technical debt is a metaphor coined by Ward Cunningham. This evening, I learned that I have more time than I thought.
00:15:07.680 I'm going to play a video where Ward explains the metaphor of technical debt. It’s essential to understand how we discuss this concept.
00:15:26.440 I became interested in the way metaphors influence our thinking after reading George Lakoff and Mark Johnson's 'Metaphors We Live By'. An essential idea is that we reason by analogy with metaphors.
00:15:54.760 I coined the debt metaphor to explain the refactoring for the UYCash product. It was important to me that we accumulated the learnings about the application over time.
00:16:09.680 This means we should strive to modify the program to reflect our understanding as we learn. I told my boss that if we failed to align the program with our understanding of financial objects, we'd be slowed down. This is like paying interest on a loan.
00:16:25.960 You can rush software out to gain experience but neglect to repay that loan when implementing upgrades.
00:16:43.560 By continually adding features without reorganizing to reflect your understanding, you'll eventually struggle with program efficiency, akin to zero purchasing power.
00:17:01.960 Many bloggers confuse the debt metaphor, thinking poorly written code is its primary source. While writing code poorly isn’t advisable, it's essential to express your understanding even if incomplete.
00:17:16.440 Writing code that reflects your comprehension allows easier refactoring to improve and clarify your code’s purpose.
00:17:27.320 Understanding the debt metaphor is crucial to your advantage in writing clear refactoring. It embodies one approach to explain why methodologies like Extreme Programming work.
00:17:48.640 Now, I found an interesting video on the debt metaphor, and it's available on YouTube if you’d like to check it out.
00:17:59.440 When I first heard about the concept of technical debt, I misinterpreted it, associating it with a big ball of mud. However, the reality is that your app desires to be the best representation of the problem.
00:18:13.520 If it fails to express understanding over five years' time, you either have a big ball of mud, or you've taken on intentional technical debt.
00:18:26.440 To navigate this, we must focus on intent. We need to ask ourselves about the system's intent rather than only writing instructions to achieve it.
00:18:39.240 Let’s think about design. One key advantage we have in Ruby is the language's suitability for domain modeling, which is crucial to our design.
00:18:54.440 I could discuss Ruby's virtues for an entire day, but instead, let’s focus on a central term: anthropomorphization.
00:19:05.360 Half the room is familiar with this term. Anthropomorphization means treating non-human entities with human qualities. Assigning human traits to something non-human helps in design.
00:19:36.440 For example, saying, 'I want this code to be lazy' reflects human qualities. Achieving this in your app consistently is counterintuitive and quite challenging.
00:19:54.320 Historically, our programming education focused heavily on instructions, not design. Ward's viewpoint on software as metaphors offers us significant insight.
00:20:11.080 We must think about what it means to design great software. Rebecca Wirfs-Brock provides a metaphor worth discussing: 'Code Neighborhood'.
00:20:28.920 Imagine opening your Rails app and examining the code within the models directory. It becomes clear there are natural groupings where related objects cluster into neighborhoods.
00:20:48.160 The objects within those neighborhoods communicate with a high bandwidth. Conversely, communication between neighborhoods becomes more formal and easier to document.
00:21:09.280 Recognizing these boundaries within your application is vital for writing maintainable software. To illustrate this, consider a typical Rails blog app.
00:21:27.120 You have a user who is logged in, likely different posts and comments existing within your application, alongside tags and categories for organization.
00:21:46.160 You can organize these entities into three different neighborhoods. Posts and comments need to be aware of users since they are authored by the user.
00:22:01.920 Yet tags and categories come into play with posts and comments while having little relevance to users. Drawing these dependencies helps illustrate the connection.
00:22:20.920 This resonance allows us to visualize a running application without a web interface — purely through this domain model.
00:22:44.440 Next, let’s examine the Rails application itself and its interaction with other neighborhoods, such as the public user interface that handles web requests.
00:23:03.440 Your administrative UI, which serves additional functions, also involves blog posts, comments, and tags, yet alters how you work with those entities.
00:23:22.920 Consider permission checks: if I want to update a post, I would need the author's permission for the public web UI.
00:23:42.080 You want admins to also have this power to take down harmful content like cross-site scripting out of the administrative UI.
00:24:00.800 The question now is, where does that responsibility belong? Should the core domain model handle these permissions? Or should we delegate the responsibility elsewhere?
00:24:17.720 Let’s anthropomorphize the 'post.' Let's ask if the post would like to check permissions for publishing. Likely, it wouldn’t want that responsibility because it isn't the appropriate party.
00:24:37.040 Instead, let’s create a separate entity that checks permissions, keeping the complexities neatly organized while all parties are happy.
00:24:57.760 With various neighborhoods like domain, administrative, and web boundaries established, we should recognize some neighborhoods contain more specific concerns like permissions.
00:25:18.880 Tests are paramount and should be the first clients of your domain. As you make modifications, update the tests and ensure they’re driving those changes.
00:25:35.880 Have you ever worked on a Rails app with so many tests that any minor change leads to broken tests or even broken mocks and stubs everywhere?
00:25:55.480 I once worked on an app with a three-and-a-half-hour test suite that had to be largely discarded due to extensive mocking causing tests to fail endlessly without changing any fundamental behavior.
00:26:12.400 In this context, your tests should be around the domain, focusing on High-Level behavior while encapsulating implementation details.
00:26:27.880 This focus means you are testing the significant interactions between neighborhoods, allowing you to test less while achieving valuable results.
00:26:39.560 I wanted to share code examples, but if you come up later, I can demonstrate what I mean.
00:26:48.840 A good rule of thumb for Rails apps is that if you're calling Active Record methods outside your domain, you may be violating encapsulation, as Active Record should be a private API.
00:27:09.800 Instead, encapsulate these calls with meaningful methods that expose intended behavior. Write tests for it in your domain so that the controller calls those methods.
00:27:32.560 By doing this, you are specifying your domain with the tests, fulfilling their role.
00:27:46.560 I've worked across many teams and come to realize that perceptions surrounding testing vary widely based on experiences.
00:28:03.560 Through this approach, I firmly believe testing can indeed be fun — and I promise it becomes super easy over time!
00:28:14.560 Of course, even though it takes effort to get the hang of it, it's worth the investment in discipline.
00:28:25.560 In terms of your domain tests, ensure they aren’t scattered. Organize your tests in a manner that they represent the appropriate features of your application.
00:28:50.560 This makes your tests self-documenting; they tell you exactly what your application does.
00:29:01.560 If you look at your tests while building a new web UI, you could leverage existing test code, making it easier for you while ensuring consistency.
00:29:14.560 As a departing thought, I love to share quotes, and one of my favorites stresses how important it is that we emphasize how our work intersects with humans, not machines.
00:29:38.560 Human-centered programming is crucial. As Yukihiro Matsumoto stated, focusing on how humans care about programming and operating software is paramount.
00:30:01.560 As we embark on this fantastic LA Ruby conference, remember to be wise masters of your software design. It reflects ultimately on us.
00:30:35.560 If an app fails, that failure is ours to own; it’s not a flaw in Rails or Ruby. Let's deliberate on improving how we think and operate.
00:30:51.560 Thank you for the opportunity to speak today. Listen to the talks, and if my ideas differ from your own beliefs, don't reject them outright.
00:31:16.560 Try them on and see if they inspire any shifts in your thinking. I hope by the end of the day, you'll grow and become better too. Thank you very much.
00:31:40.560 My name is Mike Moore, also known as Bloomage or Blage online.
00:31:44.000 I have stickers! I work remotely in a basement, so I don't interact much with people in real life.
00:31:54.000 It’s been months for me, so please come and talk to me; I need that social interaction.
00:32:05.560 So, is there a question? Oh, can you provide another example regarding testing around your domain?
00:32:36.000 The more crucial question is defining what belongs in your domain and what doesn’t. One mental exercise is to judge a feature by its relevance to the public API, administrative API, or background job.
00:32:51.560 If it’s specific to one area without relevance to others, it isn’t part of the domain and should be tested separately.
00:33:05.600 Ultimately, your domain tests should represent your application's essential features, making them self-documenting and clear.
00:33:21.680 Regarding integration tests, they should primarily serve as sanity checks. The intricacies of different neighborhoods present challenges in coverage, making absolute testing infeasible.
00:33:37.920 While integration tests are useful for identifying breakdowns, they shouldn’t be the only place for failure. We need those failures present in both model and integration tests.
00:34:02.160 The truth is testing each permutation in integration tests isn’t feasible. Instead, validate core functionality through your other tests continuously.
00:34:29.560 Thank you for your engagement today. Let’s focus on improving our apps’ longevity while we navigate the exciting aspects of coding.