I'm sure every developer wants to be able to change code with confidence and without fear. Readable and self-explanatory code is one aspect of that. Too much coupling is another major source of problems that prevent you from changing one part of the system without causing side-effects. In this talk, I'd like you to show you common sources of unnecessary coupling and offer you options to help prevent and/or break those. I'll talk about how principles like Don't Repeat Yourself and Dependency Injection can be a double-edge sword, how to detect too unnecessary dependencies and how to use the Dependency Inversion Principle to unravel some of those. And yes, I will also talk about controlling dependencies on the package level. Challenge 1. How to prevent too much coupling? 2. We are tought to decrease coupling and increase cohesion 3. How do you increase cohesion? By applying DRY! 4. But DRY creates more coupling 5. And too much decoupling results in a forest of abstractions 6. The solution is… find a balance (or "it depends") On the architecture level 1. Use architecture style that embraces DIP (e.g. Onion, Hexagon, Clean Architecture) 2. Organize your code along functional boundaries -> this will make it easier to break off that boundary for rebuilding/strangler/microservices. 3. Apply DRY within those boundaries only, but allow shared services for complicated capabilities 4. Align your test scope with those internal boundaries… On the package level • Classes and interfaces that are almost always used together should be packaged together. Those that don’t meet that criteria don’t have a place in that package • Packages should not require changes (and thus a new release) for unrelated changes. • There must be no cycles in the dependency structure • A package should only depend upon packages that are more stable than it is or abstract packages On the code level • Avoid technical folders and organize them by functionalities or capabilities • Things in adjacent folders usually mean they are separate boundaries • Use the Tell, Don't Ask principle • Use the Law of Demeter to detect unnecessary coupling • Encapsulate primitive types and collections in specific types • Dependency injection is great, but try to avoid a global container • It's fine to inject concrete classes inside boundaries Strategy for legacy code 1. Ensure you have a safety net using characterisation tests 2. Find existing seams and decouple them better a. Identify modules or functional slices b. Identify groups of classes that are supposed to be used together and are designed to be reusable c. Identify interfaces or abstractions in DI registration code and see if they are truly designed for reusability d. Assume that code in adjacent folders is supposed to be independent e. Move code from different technical folders that belong together in their own (functionally named) folderPublic 3. Use that to define (and visualize) the target architecture