Code Smells

Summarized using AI

Fields & Fences

Sarah Mei • February 04, 2015 • Earth

Fields & Fences: Understanding Service-Oriented Architecture and Object-Oriented Design

In her talk at RubyConf AU 2015, Sarah Mei delves into the interconnected themes of service-oriented architecture (SOA) and object-oriented design (OOD). In a concise 20-minute format, she leverages her experience as the chief consultant at Dev Mine Software to highlight critical insights about maintaining clean code and effective design in the face of growth.

Key Points Discussed:

  • Importance of Defined Focus: Mei emphasizes the challenge of delivering meaningful content in a short talk, ultimately identifying the single most important point to communicate effectively.
  • Nature of Service-Oriented Architecture: SOA is described as an increasingly popular approach driven by the need to manage scaling issues in successful applications, both regarding traffic and team size.
  • Introduction to Object-Oriented Design: OOD is presented as a method for organizing code that focuses on the responsibilities of classes and encourages iterative design rather than rigid upfront planning.
  • Emerging Design Principles: Mei explains the SOLID principles that characterize ideal object-oriented design, noting their limited utility without actionable guidance on implementation.
  • Code Smells: The concept of code smells, which represent signs of deeper issues in a codebase, is outlined as a critical approach to identifying areas for improvement. For example, duplicated code serves as an indicator that a new class is needed.
  • Service Smells: Mei introduces service smells, analogous to code smells, which highlight issues with poorly defined service boundaries, such as shared data or overly complex query parameters.
  • Anecdotes and Practical Examples: Through engaging storytelling, she shares her experiences in handling legacy code and emphasizes the importance of recognizing the evolving responsibilities in code and object definitions.
  • Call to Action: The talk culminates in the message to
00:00:00.480 Thank you so much for hanging in here with the gorgeous weather. I know it's nice outside, and I really appreciate you coming back from the break. I was picturing essentially an empty room with everyone outside having a beer, so thank you very much. I'm really excited to be here and present a new talk. You're the first ones to see it, and probably the only ones to see it in a 20-minute format. At least, I have discovered that a 20-minute time slot is really awkward. By awkward, I mean excruciatingly difficult. I've never done a 20-minute talk before and because this is new, I didn't have a 35 minute one that I'm cutting down. This talk is from scratch.
00:00:15.120 As I was looking through some of my older talks, I realized that no matter how long they were, it took me about 20 minutes to get to the point where I said the first thing I wanted people to remember. So, what that told me, and what my experience writing this talk has shown me, is that in 20 minutes I can really only say one thing. Therefore, when I was writing this talk, I had to decide which of all the points I wanted to make was the most important. I feel like I could talk about this for several hours, but which one is the most important? I guess it remains to be seen whether I picked the right one. We will find out.
00:00:52.600 I am the chief consultant at Dev Mine Software, based in San Francisco. You may have heard the tech industry is killing the soul of the city, and of course, I'm part of the problem since I’m the one looking up there. I do a lot of pair programming, and I help people figure out how to refactor large codebases. I was probably the only person who raised my hand non-ironically when Keith asked who liked legacy code yesterday. I actually really love working with codebases that have been around for a while, because you can see all the scars and the battles that have been fought. It's like a tapestry with hidden stories woven into the Git log.
00:01:16.119 I have some side projects, all of which are non-profits. I think that's a totally legitimate way to spend side project time. I'm one of the directors of Ruby Central, which means I help run RubyConf and RailsConf. This year, I’m the program director for RailsConf. The Call for Proposals is opening for another 28 hours, so it’s not too late to submit your talk. Atlanta is really nice in April. In addition to Ruby Central, I founded and am on the board of RailsBridge. We’re working to get more underrepresented groups into the community. I also do Bridge Foundry stuff; it's pretty cool! We now have Closure Bridge and Mobile Bridge, which are really neat.
00:02:02.480 Despite all the slides I've shown you so far, this is not a talk about farming. It’s a talk about service-oriented architecture. Are you scared? We've had so many talks about it already. In fact, you may have noticed that Rachel did a great talk about service-oriented architecture before lunch. You may also have noticed that she and I have many of the same side projects, and that we both live in San Francisco. It’s not like we don't talk, but it wasn't until I got here yesterday that I thought to ask her what she was going to talk about.
00:02:34.320 I had a moment of panic when I thought we might end up doing the same talk, especially since I'll be the one speaking after lunch. But actually, Rachel set me up pretty well because this is also a talk about object-oriented design. These are two bits of jargon that I think can be pretty intimidating. Many people have very different ideas about what they mean, so I'm going to give you my definitions for each one and then explore why they are interesting together.
00:03:07.040 Let’s start with service-oriented architecture (SOA). SOA is quite trendy at the moment, as I'm sure you can tell from the talks we've had that have touched on it. I have a friend who’s trying to raise money for her startup right now. She has a pitch deck that she takes to meetings. During one meeting, she was about three minutes in and had just gotten to the slide about monetization when the investor interrupted her and asked if she was going to do SOA. She was like, ‘Uh...’ but in a very nice way because she’s really good at talking to VCs.
00:03:41.000 SOA is one of those terms that people think they know, even non-technical people. It’s like ‘the cloud’—a term that doesn't have a clear meaning anymore. What it actually is, and I’ll explain how I think about it, is that everyone starts with one Rails app and one repository. Initially, that app does everything: it accesses the database, manages the tables, routes requests, renders views, talks to third-party services, and runs background jobs. For the majority of Rails apps, this pattern is totally fine. But then there are those that become successful.
00:04:20.000 SOA is often prompted by growth—either growth in traffic that requires one component of your app to be scaled differently from the others or growth in team size that makes it awkward for everyone to work on the same codebase. Often, these issues happen simultaneously. SOA, as it is most commonly practiced, involves identifying separate responsibilities in your codebase and isolating them into their own codebases with their own deployment setups. This leads to an app that does fewer things than it did before.
00:04:56.000 Remember, this is all driven by growth. When you start moving towards a service-oriented architecture, you're separating responsibilities in your codebase not because some moral imperative is urging you to, or because your VC told you to, but because you're genuinely feeling the pain that service-oriented architecture is meant to address. Now, let’s talk about object-oriented design (OOD). OOD is often abbreviated as OOD; it’s a much older idea than service-oriented architecture, at least among our generation of programmers. As a result, OOD is sometimes perceived by some as very academic—less useful in the everyday creation of software.
00:06:51.360 There are very academic definitions of OOD floating around, but at its core, it’s really a simple idea: object-oriented design is how you decide where to put code when you need to write it. That’s really it. Does it go in a class you already have or a new class? A method you already have or a new method? People often think about object-oriented design as academic largely because they picture it happening like this: you have an app you want to build, so you decide up front what all the class names should be and how they will interact with each other. Then the rest of it is just filling in the details.
00:07:40.000 You fill in all the methods you think you need and you’re working towards this grand plan in your head. At some point, when your code matches the original design, you feel accomplished. However, that’s not what object-oriented design actually is. This ideal scenario rarely happens. People think it should, but it never does. This expectation is frequently influenced by the SOLID principles—an acronym that encapsulates five principles of object-oriented design. If you thought the phrase object-oriented design was jargon, just wait till you see these principles.
00:08:57.840 First, we have the single responsibility principle, which means each class should have a single reason to change. Then, there’s the open-closed principle, the Liskov substitution principle, the interface segregation principle, and the dependency inversion principle. These are great and very useful to know, but in my opinion, they provide limited utility. The SOLID principles describe what your code should look like when it’s done but do not offer guidance on how to achieve that.
00:09:31.320 In the absence of concrete guidance, many developers assume that object-oriented design entails coming up with a design a priori that incorporates all of these principles. However, the reality of object design in practice is quite different. At the beginning, you have rough ideas of concepts you want to model. You create a few classes, fill in some code, and then notice at some point that a model is getting large and is hard to manage—it has too many responsibilities. So, you examine it and separate one of those responsibilities into its own class. Again, this isn't done out of some moral imperative or because a thought leader told you to; it’s done because you’re feeling the pain that object-oriented design aims to resolve.
00:10:45.920 The design of your application emerges from the multitude of decisions made at various scales concerning where code goes. Your design isn’t a pre-specified framework making those decisions for you; it is an emergent property of those decisions. Therefore, it’s a valid question to ask: why not just define what we want from the beginning and adhere to it? The straightforward answer is that we don’t know what responsibilities our code will have initially. The only way to discover that is to write the code, place it accordingly, and notice when something feels out of place to relocate it. Failure to do this is a common issue in Rails apps, resulting in the emergence of monoliths.
00:12:53.880 Consider the largest Active Record model in the app you are currently working on. For example, let's look at the order model from Spree, which is an open-source e-commerce Rails application. The order model file has around 650 lines, but it also includes various modules, bringing the total to just about 1100 lines. Who here has a model with more than 1100 lines? More than 5000 lines? It exists! The largest model in your codebase likely revolves around the application; it's the one that must change with every new story and the one needing object-oriented design. Other models, if their code fits on one screen, aren't as critical. Code Climate offers a scatterplot where one axis displays complexity and the other churn. Files placed in the top right section of the plot change frequently and are complex, making them the ones that require focused design.
00:14:10.120 So, that’s my definition of service-oriented architecture and object-oriented design. When examining diagrams of boxes and lines, it all seems clear, right? You just identify the boundaries of your responsibilities and formalize them. However, delineating the boundaries of a single responsibility is surprisingly difficult. Object-oriented design can be similarly daunting; you may have read a book outlining the single responsibility principle for classes, felt prepared to do the right thing, and then sat before your Rails app, not knowing what to do next. Design is simpler when starting from zero; it’s exceedingly challenging when refactoring existing code. These two challenges are difficult for the same reason: defining the boundaries of a single responsibility is inherently complex, whether you’re extracting a service or reducing the size of a class.
00:15:23.440 Now, what do we do? SOLID presents us with an idealistic vision of what our code could look like, yet it offers no roadmap on how to reach that ideal. So, how can we discern which elements to separate? I refer to this as the ‘phone booth in the garden’ problem. Perhaps there was a time when the phone booth was appropriately placed—maybe there used to be a sidewalk or a building in that area. However, the landscape has since transformed around the booth. We become so accustomed to its presence that we no longer notice its incongruity. Our challenge lies in identifying these inconsistencies once again.
00:16:37.040 To help ourselves identify our phone booths, we need to focus on code smells. The term ‘code smells’ originates from Kent Beck but gained popularity through Martin Fowler’s book on refactoring. Code smells are often described as superficial indicators in code signifying deeper issues. Some liken them to icebergs; only about 10% of an iceberg is visible above water. For me, however, code smells carry a more specific meaning: they signify a mixture of responsibilities and provide hints on which objects I need to create and where to establish boundaries in my code. They highlight the phone booths in the garden.
00:18:02.480 Let’s examine a concrete example: duplicated code is the most recognizable and addressed type of code smell. Suppose you have two Active Record models containing similar methods that generate user-readable IDs but employ slightly different algorithms. It can be challenging to spot the differences in their algorithms. Fortunately, each code smell has a corresponding refactoring strategy that resolves it. When encountering duplicated logic, it suggests there is another class waiting to emerge. The appropriate refactoring strategy is referred to as ‘extract class,’ wherein you create a class that encapsulates the duplicated idea. You can then utilize this class in place of the previous implementations, allowing the differences in the algorithms to become clearer and facilitating easier testing than the older number generator methods.
00:19:36.040 Throughout the conference, we have discussed DRY (Don’t Repeat Yourself), with many emphasizing that it is the duplicated ideas that matter, not just the duplicated syntax. However, often, discerning the difference can be quite challenging. I think about it this way: if I can transform the duplicated logic into a new class, then I should. If not, I’ll leave it as is.
00:19:46.000 In summary, if you cannot reduce duplicated logic into a new class, keeping it together is acceptable, as doing so indicates that it's a coherent concept worth reusing. Code smells guide us in recognizing opportunities to clarify the boundaries of our objects, steering us toward optimal solid code and informing where to place class boundaries.
00:19:57.760 The question is, can they also guide our decisions about where to establish service boundaries? I believe they can, but the forms they take in the SOA space differ.
00:20:08.360 The first of these is shared data. This can manifest in various ways, the most apparent of which is shared databases. Two services that share a database are not independent. You cannot circumvent this by having separate databases that sync; while tempting, it's ultimately ineffective. This is a sign that these services are not logically independent.
00:20:30.480 Another instance of shared structure occurs when you share business logic. If you find yourself creating a gem containing your models, your services are not sufficiently independent. Services that must always be deployed together are a critical issue. These situations might arise from shared data or business logic.
00:21:01.760 Just like code smells, service smells are not always clear-cut and do not appear in isolation. They might be challenging to identify, but having a name for them can significantly facilitate understanding.
00:22:16.240 An additional warning sign is the number of query parameters; if you find yourself always passing more than one or two parameters to ascertain what to do next, your service is likely handling too much.
00:22:36.360 Nested routes can also cloud understanding. Finally, a parameter clump indicates you're consistently passing multiple pieces of data together, suggesting that a distinct behavior should probably exist in its own service.
00:22:58.560 We possess these corresponding service and code smells. Addressing most of these issues generally involves creating smaller classes when dealing with code smells—making fewer things than they previously did. The same principle applies when addressing service issues; often, if there's tension between two services, it suggests that another service needs to be created to alleviate that awkwardness.
00:23:59.440 I would like to leave you with a couple of thoughts. Service-oriented architecture is simply a contemporary term for a very ancient skill: identifying responsibilities. This is a skill you can practice and improve upon. It’s much easier to refine this skill within your code than it is in services because class boundaries are significantly easier to change compared to service boundaries.
00:24:56.000 Refactoring your code into smaller objects is actually what makes implementing a service-oriented architecture possible. Without the ability to identify a responsibility to create a service from, which infiltrates multiple extensive objects, you must address your God objects first before proceeding with service-oriented architecture.
00:25:41.920 Then, once you reach that point, you can leverage code smells to inform your architecture towards single responsibility services. If I could convey one takeaway from this talk, it’s simply this: make smaller things. Always.
00:25:54.560 That's all I have. Thank you very much!
Explore all talks recorded at RubyConf AU 2015
+13