Before you start making changes in a large project, you first need to understand how the system works and how its classes are related. This chapter will help you visualize this using a tool called dependency maps. You’ll learn:
- What is a dependency map?
- How can you use it to understand complex systems?
- How can you use it to identify problematic relationships?
- How can you use it to break up a complex system into modules?
Feel free to continue using your project from the last chapter, or start fresh from this chapter’s starter project. For the best hands-on experience, you’ll need a pencil, red marker, green marker and paper. This is going to get… analog!
Alternatively, a drawing program — or even Keynote — will work too.
You may be wondering, “What exactly is a dependency map?” Great question!
Dependency maps are a way to illustrate dependencies between types. Its primary purpose is to help you understand how a change will affect an entire system. You can use dependency maps to identify change points, test points and places where you can pull out types to make your app more modular.
Before making a code change, your first step is to identify what the new behavior should be. In this case, your job is to move MyBiz’s login functionality into a separate module. Long-term, the plan is to use the login module in multiple apps.
Moving login into a separate module also has side benefits: Faster incremental compile times, separation of unit tests and more.
Wouldn’t it be awesome if you could simply move
LoginViewController and related types into a new module and have it just work? Unfortunately, real-world apps aren’t usually so well architected…!
Consequently, you’ll need to break up dependencies to make this possible. This is the perfect problem a dependency map can help you solve.
Choosing where to begin
Choosing the “right” place to begin can be a daunting task in a large app. Fortunately, creating a dependency map is a journey of discovery and you can iteratively refine it. An educated guess for a starting point is good enough.
Finding direct dependencies
The next step is to identify the type’s direct dependencies.
Finding secondary dependencies
Your dependency map looks nice right now with all of the arrows pointing away from
LoginViewController. However, this is because you’ve only inspected
LoginViewController and not any other classes yet.
Deciding when to stop
You could iteratively walk all files and create a diagram for the entire app. While this might be interesting, it’d likely be too busy to be useful. The further you get from the type you’re trying to modify, the less likely you’ll find relevant dependencies. Should you find yourself making changes in files that aren’t on your diagram, of course, you can always include them later.
What are problematic dependencies?
A type is coupled to another when it directly depends on it. However, this may or may not be problematic. For example, if a type is coupled to a delegate protocol (e.g.
APIDelegate), this is better than being coupled to a concrete type directly (e.g.
Finding problematic dependencies
You can evaluate the relationships in the dependency map using these questions to find problematic dependencies.
Completing the map
If you find that a direct dependency is problematic, you don’t need to evaluate whether its secondary dependencies are problematic. Rather, you’ll need to refactor or fix this in some way first. Depending on what you do in this regard, however, you may later consider the secondary dependencies or may never do this.
Breaking up complex systems
You can use your dependency map as a blueprint to break up complex systems. It tells you exactly how types are related and which relationships are problematic!
You learned about dependency maps in this chapter. Here are their key points:
Where to go from here?
In the next chapter, you’ll use this dependency map to actually pull out the login functionality into a new module! Of course, you’ll do this in a TDD fashion and learn tricks along the way for handling problematic relationships.