Design Patterns

Write Small Things

Write Small Things

by Mark Menard

In the video titled "Write Small Things," Mark Menard discusses the importance of writing small, clean, well-designed code within the object-oriented programming paradigm, particularly in Ruby. Emphasizing that writing small classes is challenging yet essential, Menard focuses on methods and classes, advocating for their iterative development through refactoring and design.

He highlights key points such as:

  • The Risks of Large Code: Large, unwieldy code can become "zombie code" that is too difficult and risky to alter, leading to duplication of effort.
  • Concept of Small Code: Small code refers not to writing less but to having manageable methods and classes that can be composed effectively.
  • Design Discipline: Writing small code requires iterative design and refactoring, evolving towards clarity and modularity rather than being created from scratch.
  • Importance of Composition: Small, well-designed methods and classes facilitate a loosely coupled system, allowing for easier adjustments and code maintenance.
  • Future Adaptability: A cornerstone of Menard’s argument is that successful software will need to evolve; writing small, clear code lays the groundwork for this adaptability.
  • Key Tools: He discusses tools for achieving smallness in code like 'extract method,' 'extract class,' and 'composed method' as central practices for refactoring.
  • Method Principles: Menard stresses the importance of methods performing one task, minimizing arguments, and separating queries from commands, illustrating that such structuring leads to cleaner and more maintainable code.
  • Dependency Management: He emphasizes managing dependencies thoughtfully to avoid complex, tightly coupled structures, advocating for dependency injection where appropriate.
  • Real-World Example: The construction of a command line options library serves as a practical example throughout the talk, showcasing the application of these principles in a relatable context.

In conclusion, Menard urges developers to focus on clear abstractions and small method design as key strategies in crafting applications that are sustainable and adaptable for future changes. He invites fellow developers to engage with these practices to promote better coding standards.

00:00:00.100 Good afternoon! My name is Mark Menard, and I run a company called Enable Labs in Troy, New York. A little bit about me: I'm married to my wife, Silva, and we have two boys who are about the same age, as they reside in Troy with me. I am the owner of Enable Labs, a boutique consultancy that specializes in web and mobile development. I have been working with Ruby on Rails for about five years, but I have been involved in software development for a long time.
00:00:27.310 In preparation for this talk, someone on my Twitter feed shared a quote that I really love, as it sums up why writing small, clean, well-designed code is important. The gist of it is that the problem with writing code that ‘just works’ is that it’s too risky and expensive to change, so it lives indefinitely as 'zombie code.' This often leads to code that you don’t even want to open. If you do open that file, you just think to yourself, 'I’m not going to touch that,' and end up writing your own version of the functionality, duplicating what’s already there.
00:01:18.909 This talk is going to focus on code at both the class and method level because having small methods is fundamental under the object-oriented programming paradigm. The ability to compose small, clear classes allows us to create loosely coupled systems. First off, I want to dispel some common misconceptions about what clean code or small code actually is. It’s not about writing less code; rather, it’s about writing smaller methods, smaller classes, and having a smaller interface that you need to understand to get your work done.
00:02:02.109 Fundamentally, writing small code is a design discipline. I was discussing this with some colleagues yesterday, and it's a classic bait-and-switch scenario: you can't write small code from scratch. At least, I can't. I’ve been trying for a long time, and I'm still not great at it. It’s an iterative process of refactoring and tactical design at the class and method levels. So, this topic is really about both design and refactoring as much as it is about writing small code because design and refactoring are the keys to writing small code. We don't just sit down and write small code from scratch; we evolve to it, iteratively.
00:02:53.020 Writing small code involves understanding that while refactoring may lead to more lines of code, more methods, and more classes, it's the ability to compose them that is essential. It's about small methods and small classes that can be composed together. The class is the fundamental building block of object-oriented programming. We take concepts, separate them, and compartmentalize these into manageable pieces to create systems that can sustain a long life.
00:03:07.660 So, why should we aim for this? The most important reason is that we do not know what the future will bring. If your software succeeds, it needs to adapt and change over time. For example, I started my business ten years ago, and we still have our first client using the original piece of code we wrote for them at that time. If your software is successful, it will evolve, and you will want to raise its level of abstraction, which is easier when you have small code, small methods, and small classes.
00:03:50.310 You also want to create composable components to avoid the risk of having functionality buried within a large codebase, leading to code duplication. The challenge we face with small components is dealing with dependency management; improper management can turn into a nightmare. It is crucial to get this right because if the dependencies are in the wrong direction, it becomes chaotic. You'll need to manage the context thoroughly to avoid these pitfalls.
00:04:19.710 The goal is to have small units of understandable code that are amenable to change. Our three main tools will be 'extract method,' 'extract class,' and 'composed method.' Longer methods are harder to grasp than shorter ones, while longer classes are harder to understand than short ones as well. When methods take many arguments, they become troublesome to extract due to the context surrounding them. We will utilize the composed method as a way to write self-documenting code, allowing the code to explain itself.
00:05:03.350 Let's discuss methods. I highly recommend the book ‘Refactoring’ by Martin Fowler. If you don’t have it, I suggest you get it. The fundamental takeaway is that the best and longest-living object-oriented programs feature short methods. The operative word here is 'best.' It’s crucial to remember that although poorly written code may also endure, it's not quality code. Our aim is to have short, composable methods and classes.
00:06:01.880 The first rule of methods is: do one thing, do it well, and do only that thing. However, we don’t always achieve this goal. This is the target we should strive for every time we write code. One important aspect of doing only one thing is to avoid side effects, which is essential for good object-oriented programming. Additionally, naming is one of the most challenging tasks we face as developers. It is worth the effort to improve.
00:06:43.010 My goal when writing methods is to limit arguments as much as possible, ideally to zero. If I must have arguments, I prefer to have one or, at most, two. If three arguments are necessary, it should be a rare exception. I am strict with this unless it involves a constructor, wherein it’s acceptable to accept a few arguments to set up the context for the code to work.
00:07:31.050 Separating queries from commands is another critical principle. If you want to ask your object something, it shouldn't be altering the object's internal state. Commands should manipulate the internal state. And of course, the golden rule: don’t repeat yourself. Recognize that if you have to change something, you’ll end up hunting for the instances of it throughout the code, which can lead to divergent behavior.
00:08:07.440 So, let's build a command line option library. A significant challenge in preparing this talk was finding a relatable example that fits into the 21 minutes we have. My friend asked if I could help with a command line options library he was creating for a project. I suggested a command line options structure that allows for various command line flags.
00:08:50.180 For instance, I could traverse the command line options and check for flags like '-c', '-b', and '-e' to execute specific conditions. Next, I'll write some tests to ensure they function as intended. Once the spec is complete, I expect to encounter a few failures given that I won't have any code implemented yet.
00:09:29.040 The code I created involves storing the options in an array and checking if individuals exist among the allowed flags. The setup merely checks presence without any validations at this stage. The goal is to extract the options defined by the user throughout the input to adapt to possible future requests while ensuring a smooth interaction with the command line.
00:10:18.460 Now that we've set up our command line options, the next challenge was adjusting the structure to accommodate more complex user input scenarios. My friend requested modifications to validate these options further. This modification led to the creation of a more flexible structure capable of handling various argument types, while ensuring the demands of the command line were met.
00:11:03.190 To elaborate on the validations made to the command line options, I tackled issues regarding effective parsing and retaining specific command-line behavior. I needed to ensure flags correctly returned data without introducing unnecessary complexity into the structure. Upon running the new tests, I would regularly monitor them for failures or errors that indicated something was amiss.
00:11:43.380 As I developed the validations further into strings and integer recognition, rapid checks for conditions highlighted how easily logic could diverge. The focus was on continually streamlining methods to ensure class responsibilities were limited while keeping functionalities modular.
00:12:25.500 Eventually, I recognized the importance of clear abstraction within my command line options to further separate concerns based on type. For example, as I isolated those responsibilities into their respective classes, the code base became cleaner and more maintainable. As you dissected each aspect of the functionality, it progressively reinforced the principles of object-oriented design.
00:13:10.750 Maintaining small classes and methods fosters a clear and focused approach, thus minimizing the overhead associated with modifying older functionalities. This became evident as I analyzed existing methods for duplicity or excessive complexity. There was an ongoing effort to ensure that each method had a single responsibility, further promoting reusability across various sections of the code base.
00:13:55.150 To provide a visual on how improved design promotes maintainable code, I started incorporating class hierarchies that accurately depict object types and their behaviors. This encapsulation helps maintain clarity while allowing for class evolution. The difference between straightforward implementations versus abstract ones becomes clearer as you establish conventions.
00:14:42.870 As we moved forward with this design evolution, further iterations of logic and validation became instrumental in ensuring sustainability. The objective remained to reduce code complexity while adhering to sound object-oriented practices that establish the foundation of our systems. Throughout this exploration, I underscored the principles of clarity and separation, insisting on keeping implementations succinct.
00:15:30.220 While constructing these classes, I also kept an eye on dependency management, using the strategies of dependency injection and avoiding tightly coupled structures wherever possible. The discussions stressed the importance of remaining adaptable over time, as future requests invariably change the landscape of the code architecture.
00:16:14.740 As I finalized the command option structure, I saw how effective implementation would enhance future engagements with command-line operations. Each method is set against clear behaviors and expected interactions, allowing the core to remain undisturbed by external influences. From here, transitioning between versions would maintain integrity while fostering expansion.
00:17:05.790 To summarize, clear abstractions and small method design can be game-changers in effectively crafting dynamic applications capable of evolving alongside user needs. By prioritizing small classes and coherent interactions, we can ensure consistent performance while minimizing overhead.
00:17:46.390 In conclusion, I would like to extend my gratitude for your time. My name again is Mark Menard, and should you be interested in delving deeper into these concepts or associated practices, I would love to engage with you. Thank you very much for your attention, and I am open to any questions you may have.
00:18:31.050 Thank you!