Object-Oriented Programming

SOLID Principles Through Tests

SOLID Principles Through Tests

by Sebastian Sogamoso

The video titled "SOLID Principles Through Tests" presented by Sebastian Sogamoso at LA RubyConf 2014 discusses the importance of incorporating SOLID principles in software design, particularly through the practice of Test-Driven Development (TDD). Sogamoso emphasizes that while a significant number of developers might practice TDD, merely doing so does not ensure good design or code quality. He highlights the critical relationship between testing and design, asserting that tests serve as essential indicators of potential design flaws.

Key points discussed in the video include:

- Understanding SOLID Principles: The SOLID acronym encompasses five core principles:
- Single Responsibility Principle (SRP): A class should have a single reason to change to avoid unnecessary complexity.
- Open/Closed Principle (OCP): Classes should be open for extension but closed for modification, allowing for the addition of new features without altering existing code.
- Liskov Substitution Principle (LSP): Objects should be replaceable by instances of their subclasses without issues, ensuring proper polymorphism.
- Interface Segregation Principle (ISP): Favor smaller, specific interfaces over large, general ones to simplify maintenance.
- Dependency Inversion Principle (DIP): High-level modules should rely on abstractions instead of low-level details to promote flexibility.

  • Testing as a Design Tool: The speaker introduces the concept of utilizing tests to guide good design decisions. For example, a shipping method model is discussed, showcasing how tests can help identify responsibilities and promote decoupling.
  • Pragmatic Approach: Sogamoso urges that SOLID principles should not be applied rigidly. Instead, developers are encouraged to develop good habits and apply these principles contextually, adapting them to their specific scenarios.

The video effectively illustrates that adhering to SOLID principles can significantly enhance code maintainability, reduce dependencies, and promote developer satisfaction. Sogamoso wraps up with a quote from Kent Beck, reiterating the value of cultivating good programming habits over merely striving for a perfect code design. He concludes by welcoming questions, indicating a willingness to engage further with the audience on the discussed topics.

00:00:25.439 Good morning everyone, my name is Sebastian Sogamoso from Medellin, Colombia in South America. I work for a software development company called Volvo Inc, where we build custom solutions for institutions in the higher education sector. Today, we're going to talk about SOLID principles through tests.
00:00:56.399 I want to start with a question: Can you please raise your hand if you are practicing Test-Driven Development (TDD) in your daily work? That's great to see! It's more than half of you, which is encouraging.
00:01:29.280 I've observed that as more people adopt TDD in their day-to-day jobs, they sometimes come to believe that just practicing TDD means they're doing it correctly. While it's true that TDD leads to better practices, it doesn't automatically guarantee good application design or code quality. As Kent Beck wisely said, "TDD doesn't drive good design; it gives you immediate feedback about what might be bad design." This means you should listen to your tests because they are the first clients of your system. If testing is painful, there's likely a design issue.
00:02:24.319 There are ways to make code more testable, but making code more testable doesn't necessarily mean that the code quality will improve. For example, you might choose to make a private method public just to facilitate easier testing, but that doesn't inherently improve your code's design. Michael Feathers once noted that good design enhances testability; thus, by addressing your design issues, you may also resolve your testing challenges.
00:02:51.680 SOLID principles aim to help us write well-designed object-oriented software. For those who aren't familiar, SOLID is an acronym representing five design principles identified by Uncle Bob Martin: the Single Responsibility Principle, the Open/Closed Principle, the Liskov Substitution Principle, the Interface Segregation Principle, and the Dependency Inversion Principle.
00:03:58.239 The intent behind following these principles is to avoid code that is rigid, fragile, immobile, and viscous. In other words, they help us prevent tightly coupled code, which is difficult to change and not reusable. When we consider these principles in the context of software development, we realize that they help us write code that saves time and money.
00:04:44.240 Let's consider an example to see SOLID principles in action. Suppose we have a requirement to model a shipping method for an e-commerce system. This shipping method should calculate the cost of shipping a given order based on three factors: (1) the number of items in the order, excluding digital items, (2) a fixed rate that depends on the amount of items, and (3) the distance between the origin and destination. We will use RSpec to illustrate these principles.
00:05:36.800 Let's begin by modeling a test to implement the requirements. We will consider various cases, such as when all items are digital, when only one item is digital, and when multiple items are digital. Additionally, we should include sub-cases based on the distance shipped, such as when the origin and destination are in the same state, the same country, or are in different countries.
00:06:10.320 If we were to write the entire test, it would be quite extensive, which serves as a warning sign. A long test is generally a symptom of a design issue, but let’s use this test to guide our coding. The calculation for shipping cost should operate as a straightforward formula: number of items multiplied by the rate multiplied by distance. Let's explore how we can achieve this.
00:06:50.920 To calculate the items, we will retrieve item data from the order and exclude digital items before counting them. Additionally, our class for calculating the shipping rate and distance should be able to handle changing circumstances without tightly coupling.
00:07:25.920 A class with many private methods containing key business logic is often referred to as an "iceberg class." This occurs when the public API appears simplistic, but substantial complexity lies beneath the surface. In design, we should strive to minimize these iceberg classes as much as possible.
00:08:17.760 Now let's examine the Single Responsibility Principle (SRP), which states that a class should have one and only one reason to change. With this principle in mind, we might consider extracting certain responsibilities from our shipping method class. However, instead of jumping to conclusions and implementing changes right away, it's critical that we let the tests guide our decisions.
00:10:00.480 Using a technique I refer to as "wishful testing," we can write tests assuming that additional classes we need already exist. For instance, we might write a test for a method called `non_digital_items` that presumably calculates how many items are not digitized, returning zero if all items are digital. This manner of delegating responsibility redirects our design from being tightly coupled to allowing each class to have focused responsibilities.
00:10:49.920 With each passing example in our testing, we will see how our tests naturally guide good design decisions, pushing us to create smaller, more cohesive classes that have clear and maintainable interfaces.
00:12:00.000 Now, let's transition to the Liskov Substitution Principle (LSP), which asserts that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. This means if class B is a subtype of class A, we should be able to replace any instance of class A with an instance of class B without causing any issues.
00:12:35.680 To reinforce our LSP understanding, we can utilize contract tests that allow us to verify that a class adheres to a predefined contract, giving us the confidence to use duck typing in Ruby. With these proactive tests, should we need to add new shipping methods, we can do so without altering the existing design.
00:13:57.760 Next, let's address the Dependency Inversion Principle (DIP), which emphasizes that high-level modules should not depend on low-level modules but rather on abstractions. This principle becomes particularly relevant when designing systems for proper extensibility and maintainability; it encourages developers to create systems using abstractions.
00:15:30.960 We then observe the Open/Closed Principle (OCP), which states that classes should be open for extension but closed for modification. A practical application of this principle means we can introduce new behaviors without altering existing code, enhancing our software without introducing errors.
00:16:11.680 Moving forward, we should talk about the Interface Segregation Principle (ISP), which advocates for smaller and more specific interfaces. Though Ruby doesn't implement interfaces like Java or C++, it’s vital to apply the essence of this principle by ensuring that we don’t expose large public APIs that complicate maintenance.
00:17:04.640 In summary, applying SOLID principles helps reduce dependencies and promotes a decoupled architecture, thereby making our code easier to maintain. Understanding and applying these principles allows for more harmonious collaboration among developers while promoting better software quality and developer satisfaction.
00:17:44.000 It’s crucial to remember that SOLID principles are not rules to be applied dogmatically. Context is key; blindly adhering to these principles can lead to poorly designed systems. The essence lies in adopting good programming habits while considering these principles thoughtfully.
00:18:29.920 To close, I'd like to share a quote from Kent Beck: "I’m not a great programmer; I’m just a good programmer with great habits.” This statement encapsulates the heart of our discussion on SOLID principles. Integrating these principles into our coding practices can help cultivate these invaluable programming habits, ultimately leading us to create better-designed, maintainable software.
00:20:34.400 Thank you for your attention! Now, I'd like to open the floor for questions.
00:24:58.480 I’m curious if you've encountered any examples of wrapping Active Record in a way that limits the methods accessible to applications, possibly for better test design and performance.
00:25:17.760 That's a great question! Many developers have taken to keeping their Active Record models lean by only using them for database interactions. This is often done by confining business logic to other classes, thus promoting both maintainability and faster test execution.