EuRuKo 2016

Rules, Laws, and Gentle Guidelines

EuRuKo 2016

00:00:03.800 Our next speaker is Andrew Radev. He's been working with Rails for a while and has experience in both back-end and front-end development, as well as most of the places in between. He's also a Vim wizard and a maintainer of Vim Ruby and Vim Rails. Additionally, he's quite humble about it, so please welcome Andrew, who will be discussing 'Rules, Laws, and Gentle Guidelines.'
00:00:35.170 Hello everybody! Can you hear me? Is everything okay? Cool! So, hi, my name is Andrew. You can find me on the internet as Andrew Radev. If you're having trouble with my last name, just remember it starts with 'Radev' and ends with 'dev', which is basically what I am. Today, I'm going to talk to you about rules, laws, and gentle guidelines. The latter part of this comes from the Law of Demeter, which inspired this talk.
00:01:05.750 Now, if you don't know what the Law of Demeter is, it essentially states that if you have this disorganized Ruby code, you'll see a lot of method chaining with dots. That's bad—you shouldn't have that many dots. What you should do is replace them with underscores, resulting in more meaningful method names like 'user_profile_address', 'user_first_name', or 'created_at'. These are all methods I've actually seen used in the wild.
00:01:23.000 Okay, this isn't exactly what the Law of Demeter is about; I'm going to go back to the source. It was originally defined in a paper called 'Object-Oriented Programming: An Objective Sense of Style.' I'll talk a bit more about the objective part later, but it was published in 1988. The formal definition, which I actually kind of borrowed from Wikipedia, goes like this: if you have a method M, the class of the object O can only call methods on a limited number of objects.
00:01:36.840 Specifically, you are allowed to call methods on: 1. 'self' (the object itself), 2. any parameters passed to method M, 3. any objects that are direct components of the object (i.e., instance attributes), and 4. global variables, although that has its own problems. In essence, you can pick your friends, and you can pick your nose, but you can't pick your friend's house unless they've been provided to you in a method argument.
00:02:06.110 The best interpretation and explanation of the Law of Demeter I've ever seen comes from an article in a book called 'The Paperboy Wallet and the Law of Demeter.' In it, the scenario describes a paperboy delivering a paper to a customer who demands payment. To pay the paperboy, the customer retrieves their wallet, and then we call 'subtract_money' on that wallet object. It sounds pretty reasonable, right? The problem here is that this setup exposes the paperboy to more information than necessary. The paperboy now knows all about the wallet, including whether the customer even has one.
00:02:50.480 We’d like to decouple this code from the wallet concept altogether because it doesn’t matter if the payment is done with a wallet, a credit card, or bitcoin. David Bock suggests a method called 'get_payment', which encapsulates the payment logic, whatever that may be. This could make the code look something like this. You might notice that the method is named 'get_payment' and not 'get_wallet_subtract_amount'. We aren't just chaining two methods together, even if one was a bit more sensible.
00:03:26.700 Even naming it 'subtract_amount' wouldn't be ideal because it still mentions the wallet, implying some level of implicit coupling. We want to avoid tightly coupling our code further. The words on paper suggest a slightly different example, which also deals with the Law of Demeter, but leads into similar problems. For instance, if we have methods that do something like 'user_address' and we find a way to have a shopping cart, we start to couple our methods to the user.
00:04:01.110 The point I want to make here is that naming matters. You can't simply delegate responsibilities and call it a day. Implicit coupling could still exist between your methods. Secondly, we don’t need to delegate in some cases. We can resolve issues highlighted by the Law of Demeter by removing unnecessary parameters altogether.
00:04:39.310 For example, take a user that has many email-related attributes. Suppose we have a method that asks the user if we should send newsletters. Instead of pulling attributes directly from the user, we could extract all of these email attributes into a separate object called 'email_settings'. The issue is that we now have a Law of Demeter violation if we call 'user.email_settings.weekly_summary'.
00:05:02.170 One solution is to delegate, but that can feel like a step back. A better alternative could be injecting 'email_settings' as a method argument. We would then call 'weekly_summary' directly on 'email_settings'. This way, we could avoid coupling the user with the email settings entirely. In fact, we can define the recipient in a more generic way and not tied specifically to a user.
00:05:37.310 This way, most of the code we currently have could make sense with the proper names. We can change the first argument whenever necessary. Moreover, the code does not need to know about the relationship between the user and the email settings—and that relationship may not even exist. I find that one of the best parts about decoupling code and leveraging appropriate abstractions.
00:06:11.420 If I were to refactor and anticipate email settings extraction, I might just declare 'email_settings' first in the method, then use them in the bottom half of the method. This would establish that the email settings are indeed useful domain parts of our logic without the method requiring the user context. However, I would still argue we would be in violation of the Law of Demeter because of the original user's knowledge being necessary.
00:06:43.120 While this coupling isn't a bad thing in itself, having it necessitates understanding its broader implications on code maintainability and openness for future modifications. It's pivotal to note that the nature of this coupling may warrant a change in how we approach our design. The Law of Demeter is not an absolute; it’s an idea you could follow loosely depending on your context.
00:07:21.540 Point three: the law is not a law in the sense that if you break it, you're going to jail. You're also not going to win a Nobel Prize for following it strictly. This nuance is accentuated in the original paper that indicates a need for large-scale experiments to prove the effectiveness of what they refer to as the Law of Demeter. Still, we know that approximately 14,000 lines of code around its principles often work in practical settings.
00:08:02.750 The original paper itself mentions trade-offs, illustrating that while following the Law of Demeter could work, it might increase the number of methods you need to maintain, leading to less comprehensible implementations depending on how far you take these concepts. There’s a risk of developing several arguments or methods that could heighten complexity within interactions. Therefore, while it’s useful, it’s not to declare it as a rigid principle with adverse consequences upon deviations.
00:08:36.450 Having said that, I personally dislike the term 'Law of Demeter.' Instead, you should consider calling it a 'Guideline of Demeter' or similar, as it serves as a tool to understand your code more effectively—guiding you where you may find coupling within it and help you establish whether it’s worth fixing at a given moment.
00:09:16.070 Moreover, this principle shouldn’t be weaponized in arguments on the internet. I’ve seen it used more often than I’d like to win arguments in coding discussions, similar to the Single Responsibility Principle (SRP), which I think is a very popular principle but perhaps a bit misleading at times.
00:09:56.560 The phrase 'Single Responsibility' states that each class should only have one reason to change. This idea, however, can be susceptible to misinterpretation if applied without context. Let's say we have a class that updates comments. It saves the comment, performs validations, and also handles some dependencies of the comment. Sounds reasonable until you see another class, say 'DestroyComment', calling the exact same methods but doing a different action by destroying the comment.
00:10:24.470 We could say these two classes are not single responsibility unless you consider they're sharing a responsibility. The responsibilities are not in the right place, which indicates a need for refactoring. We could encapsulate this shared functionality within a new class—perhaps call it a 'CommentManager' or 'CommentLifecycle' to better represent its overarching responsibilities.
00:11:04.490 Refactoring involves looking over how we define responsibilities. One option is to examine origins of supporting patterns that bind our methods. Single Responsibility itself was expanded by Uncle Bob (Robert Martin) who originally described it in the context of separating concerns. His direction is about having a singular focus or reason to change.
00:11:43.570 This presents questions: What truly signifies one 'responsibility'? Moreover, how we determine that dividing responsibilities adheres to the tenet of SRP? Consider an operation that registers a user—it could encompass filling fields, validating the input, and submitting the form—a multitude of functionalities rolled into one method.
00:12:22.980 However, naming is paramount. We might be inadvertently describing multi-faceted methods with names that hint at broader responsibilities than they hold. We could break this problem down further and look how to derive a concept for our functions that doesn't confuse their real action. Difficulty arises when deciding on clarity and separation of concerns within methods.
00:13:05.890 Separating methods does not always mean cutting them into smaller pieces just for the sake of it—sometimes it’s about redefining how responsibilities are shared and where they lie. There could be practical reasons to consolidate code instead of splitting it aside unless you’re already cognizant of repeated patterns. Being adaptable can help maximize that efficiency.
00:13:44.030 On that note, if we consider a connection more broadly known—like coupling in our methods—many abstractions center around these principles. For example, Uncle Bob described the interaction of an interface involving both 'send' and 'receive' methods that relate to messaging vs. 'hang up' altering connections. Those two responsibilities can stand separate. The significance is in the degree of change expected with each term.
00:14:24.470 The challenge lies in determining if those components could evolve independently. Trajectory for change itself may reduce the importance of strict adherence to SRP or similar concepts that further prescribe specific actions—to enforce a better semantic interpretation into your code.
00:15:02.950 In some sense, reasoning about why you’re making these changes necessitates exploring future environments. The gist of establishing what your project requires will rely on subjective estimations you derive from your experiences, along with your team's capabilities. Remember: our implementation realities will vary based on circumstances happening simultaneously in various teams.'
00:15:41.960 I don’t want to oversell anything nor categorize principles as ironclad dogmas. Each one of these methods provides direction to discuss comprehensively looking at our code without turning them into rigid guidelines. Which leads to my last point: I'll always believe it’s easier to ask for forgiveness than permission. That’s my guiding principle, just naturally through my experiences. Some people may perceive this approach as pretentious or dire when it might only be reflecting the lack of available discussions around those moments.
00:16:21.840 I find this practice tends to generate its share of unintentional chaos. You know, early mornings in the office, people commit ideas without much discussions simply to apply unproven ‘better’ techniques. I do have my share of stories about situations when I first joined my current team; I pulled new code changes only to discover someone pushed an idea that had wrapped trading in unnecessary methods that we weren't even consulted on.
00:17:01.220 I thought I could keep the importance of ongoing dialogue for early decisions and let everyone else see how we can troubleshoot as we grow, but we still find people perform system operations that create complexity long before the why about examining Change. Improvisation isn’t always the way things should develop, but they sure could grow, proportionate to the magnitude of changes we had gotten for it.
00:17:42.380 Unstructured yet somewhat tangible relationships in production also come back to bite us later. As a final note, please monitor if your design theories have spread over multiple rotations or surfaced as heuristics across libraries. Outlining ideated responsibilities need verification for you and your systems; only then should lessons translate outcome accurately to future defaults or options.
00:18:22.390 Don't push in the middle of the night unless you’ve received secondary analysis about your decisions! I would appreciate your understanding if you could allow me to postpone little decisions earlier in terms of lessons learned, putting faith into our methods effectively.
00:19:02.510 Thank you for your time!
00:19:28.950 Thank you, Andrew! I really loved these wartime stories; it's everyone has their heads full of them. If you have any questions for Andrew, find him in the hallway and talk with him. I have a lot more stories.
00:19:35.530 [End]