Welcome to the course path for Refactoring Techniques in Scala 3. Before we begin the first course, let's discuss the themes of this course path, what we will cover, and how we will approach our practice sessions.
Our journey will focus on equipping us with the skills to confidently modify existing codebases while minimizing the risk of introducing bugs. By the end of this course, we'll be well-versed in various strategies to make our code more testable and reliable using Scala 3 and its powerful features.
Working with established codebases often presents unique challenges. These include dealing with tightly coupled components, a lack of documentation, and the fear of breaking existing functionality when making changes. Without the use of proper tools and techniques, even minor modifications can lead to unexpected issues.
As we progress through the path, we will be using a few Scala libraries to help us along the way. While they are going to be used extensively within this course, keep in mind that they are just some of the options available out there, and that other projects might use different tools. The point is to understand why we use them, not the particular tool.
We'll be using ScalaTest
, a popular testing framework, to write and run tests effectively. Refactoring will be a significant focus, as it helps increase testability by creating traits and breaking down dependencies. We'll also explore the use of Mockito
, a mocking library, to isolate and test components without external dependencies.
All courses will work with an existing codebase. This means that we will always start with some existing state and will use it to demonstrate a concept, technique, or strategy.
The first course will revolve around writing tests and enhancing testability. The second course will go deeper into breaking dependencies and how to use dependency injection and inversion of control in a practical way using Scala's features. The third course will cover how to discover and use seams to enable testability and expand capabilities, while the last course will talk about sprout and wrap techniques for refactoring.
To make these concepts tangible, we'll use an order processor as our central example throughout the vast majority of our lesson and practice sessions. This practical application will serve as a playground for applying the techniques learned. By working through real-world scenarios, we'll gain a deeper understanding of how to implement these strategies in our own projects.
This is one example of how the order processor might look:
Depending on the topic at hand, the order processor will be contrived to best demonstrate said topic, hopefully helping us grasp the concepts discussed.
In this course path, you’ll notice that many examples—including those involving dependency injection, test coverage, seams, sprout, and wrap techniques—show transitional or “in-between” states rather than perfect end results. This is intentional. In real-world projects, refactoring is almost always a gradual process. Whether you’re introducing new patterns or increasing test coverage, changes are made step by step to avoid breaking existing functionality and to maintain backwards compatibility.
These transitional patterns are not meant to be best practices for production code. Instead, they illustrate how you can safely improve legacy codebases over time, even if that means temporarily mixing old and new approaches. In your own projects, you should aim to complete these migrations when possible, moving toward a more maintainable and consistent architecture.
The goal of this course is to show the realities of incremental improvement, not to suggest that transitional states are ideal. Use these techniques as stepping stones, and always strive for clear and maintainable code as your end goal.
Now that we have covered the basics, let's look at an intro practice session aimed at getting us familiar with our practice playground.
Let's move on to see the first version of the order processor using Scala
and ScalaTest
.
