Refactoring Code

A use case that might not immediately sprint to mind when you think of diagramming is refactoring. I definitely don't do all of my code refactoring using diagrams, but for particularly gnarly code, or as an aid to explain code to other engineers, I find it really valuable.

In this article, we're going to look at the primary diagram I use for refactoring code: a class diagram. It comes from the family of Unified Modelling Language diagrams, and does exactly what it sounds like, it creates a diagram to help understand how your classes are structured. To help understand how refactoring code using class diagrams, there will be example class diagrams included too.

Refactoring Code

What Is A Class Diagram?

When considering changes to code, and in particular refactoring code, one diagram I regularly reach for to get all of this information in one place is a class diagram. It gives you a different view to a sequence diagram, and a different view to the one you get in your code editor. When looking at code in an editor, you see it somewhat in a vacuum. You can see a single class and its dependencies, maybe a couple of classes if you split your editor windows, but visually it can be hard to clearly see the dependencies for an area of the codebase. That means you might be refactoring your code without the full picture.

That's where I reach for a class diagram. I don't do it every time I write code - far from it. If I'm doing a small refactor, extracting a method for example, it's wasteful to create a class diagram. However, if I am doing a larger refactor, particularly if I'm adding a new feature and want to refactor before, I often want to see the dependencies and methods at play first. You can certainly do it in your editor, but I guarantee you'll spend a lot of time jumping back and forth between classes, forgetting what class depended on another. A class diagram can be created in no time, so I find in the long run, it pays dividends to take the time to draw one.

In lots of programming languages, in particular statically typed languages such as Java and C#, it's possible to export class diagrams automatically. This is definitely quicker than drawing it, and in some cases might be all you need. However, it won't be editable, as it will just be an image, so won't fit all use cases, as we'll soon see.

Creating A Class Diagram

Hopefully you now have a clear idea of the use cases for refactoring code using class diagrams, so let's take a look at a quick class diagram example. There'll be a couple of examples, showing the before-and-after.

          classDiagram
            class UserController {
                -CreateUserService _createUserService
                +UserController(CreateUserService createUserService)
                +Create(string email, string username) User
            }

            class CreateUserService {
                -UserModel _userModel
                -SendWelcomeEmailService _sendWelcomeEmailService
                -Kafka _kafka
                +CreateUserService(UserModel um, SendWelcomeEmailService es, Kafka k)
                +Call(string email, string username) User
                -CheckActiveUsers(List~User~ users) bool
            }

            class UserModel {
                +FindUsersByEmail(string email) List~User~
                +CreateUser(string email, string username) User
            }

            class User {
                +int UserId
                +string Username
                +string Email
            }

            class SendWelcomeEmailService {
                +Call(string email, string username) bool
            }

            class Kafka {
                +PublishUserCreatedEvent(string email, string username) bool
            }

            UserController ..> CreateUserService: depends on
            CreateUserService ..> UserModel: depends on
            UserModel ..> User: depends on
            CreateUserService ..> SendWelcomeEmailService: depends on
            CreateUserService ..> Kafka: depends on
        

This diagram was created using diagrams as code.

In this fictional class diagram example, we have six classses. A class diagram shows the dependencies between classes, as well as showing things like interfaces, that we'll see shortly.

A class diagram also details the public and private methods and properties for a given class. We can see that in the class diagram example above, with the first box in each class containing the properties, and the second box containing the methods. Before each line there is either a hyphen or a plus sign, a hyphen means it's private, and a plus sign means it's public.

Now that we've detailed the code's current state, what does it look like if we try refactoring the code?

Refactoring Using A Class Diagram

You may have noticed that all of the classes depend on concrete implementations. This isn't an article on writing clean code, so I don't want to go into too much detail, but relying on concrete classes leads to tight coupling between classes. This ultimately makes the code harder to work with, as a change in one class often directly impacts the class it depends on.

A common approach to breaking coupling between classes is to use and rely on interfaces, instead of concrete implementations. For those not familiar, interfaces can be seen as defining a contract. They contain no logic, they just define the methods that a class must implement in order to fulfil that contract. At a code level, that means classes can depend on an interface, rather than a concrete class. This in turn breaks the coupling between them, as class A no longer knows about class B, it just knows it will receive an instance of a class that adheres to the interface it depends on.

This is a really interesting topic, but for now, let's take a look at what part of the class diagram looks like if we add interfaces to the class diagram example:

          classDiagram
            class UserController {
                <<Class>>
                -ICreateUserService _createUserService
                +UserController(ICreateUserService createUserService)
                +Create(string email, string username) User
            }
            
            class ICreateUserService {
                <<Interface>>
                +Call(CreateUserRequest createUserRequest) User
            }
            
            class CreateUserService {
                <<Class>>
                -UserModel _userModel
                -SendWelcomeEmailService _sendWelcomeEmailService
                -Kafka _kafka
                +CreateUserService(UserModel um, SendWelcomeEmailService es, Kafka k)
                +Call(CreateUserRequest createUserRequest) User
                -CheckActiveUsers(List~User~ users) bool
            }
            
            UserController ..> ICreateUserService: depends on
            CreateUserService ..|> ICreateUserService: implements
        

I've trimmed down the class diagram example to make it easier to see the change, but we can now see that the concrete classes depend on a common interface, rather than the service being a direct dependency of the controller.

In terms of refactoring code, this is a big improvement, because the two classes are no longer coupled together. However, there is another benefit of using a class diagram for refactoring, and that's for visually demonstrating the improvement to the code. This concept isn't an easy one to grasp, but I find visualising it makes it much easier for others to understand when I explain the concept. In particular, when I start with the class diagram lacking intefaces, and then a colleague can visually see the arrows change, breaking that coupling that existed before, it's often like a light bulb moment.

That isn't the only change to the code we could make, perhaps you can spot some more, but the one changed served the purpose of giving a good class diagram example.

Learn To Create Class Diagrams

Creating Diagrams With Modern Diagramming Techniques Book

Refactoring code using class diagrams can be really intuitive, hopefully you can see that in the class diagram example.

The class diagrams were created using a tool called MermaidJS. MermaidJS allows you to effortlessly create a variety of diagrams, including class diagrams..

Want to learn how? I cover how to create class diagrams, along with a number of other use cases and diagram types, in my book: Creating Software Using Modern Diagramming Techniques, published via The Pragmatic Programmers.