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.