Managing
Technical Debt

Don't let tech debt manage you.

school  Explore Training

Separation of Concerns

Building modularity.

assistant Learn More

Principles of Component Coupling

Promote flexibility while isolating
the effects of change.

assistant Learn More
assistant Development Tip

Acyclic Dependencies Principle

Design your software in such a way that the dependencies between your components do not form a cycle.  The resulting component dependency structure will form a Directed Acyclic Graph — such as a tree structure.  Draw your component graph as a tree and make sure all the arrows point downward.  Then rest assured that your dependencies do not form a cycle.

Learn More
assistant Development Tip

Stable Dependencies Principle

Design your software in such a way that any given component depends on other components that are more stable than it is.  In other words, the dependencies within a component architecture should be pointing in the direction of increasing stability.  This will ensure that components that need to change frequently will be easy to change and that the stable components that others depend on will not change nearly as often.

Learn More
assistant Development Tip

Stable Abstractions Principle

Design your software in such a way that components are as abstract as they are stable.  This supports the Stable Dependencies Principle and promotes flexibility by ensuring that stable components are easy to extend, even though they are hard to change.  This is achieved by applying the Open/Closed Principle, which enables us to easily extend functionality without modifying existing code.  This approach also works best when adhering to the Dependency Inversion Principle, which states that dependencies should point towards abstractions and not concretions.  This provides very effective dependency management for software components.

Learn More

Principles of Component Cohesion

Building modularity to facilitate
independent deployability.

assistant Learn More
assistant Development Tip

Release Reuse Equivalency Principle

Design components to be large enough to justify the cost of managing the release cycle.  These management activities include assigning release numbers, maintaining release notes with features and known issues, keeping track of dates, committing to grandfather old versions, etc.  Others cannot reuse your components without them.  Considering the time and effort these activities require, it is unlikely you will be able to manage a large number of components.  Better to plan a small number of strategic components that conform to the other Principles of Component Cohesion.

Learn More
assistant Development Tip

Common Closure Principle

Design components by grouping classes together that change for the same reason, while separating those that change for different reasons.  This will help minimize the number of components that change when requirements change.  Since the classes within a component all have the same Single Responsibility, they all serve the same actor.  Therefore, these classes are closed to all but that one responsibility and to the needs of every other actor.  So when requirements change, we will know which components are affected and which are not.

Learn More
assistant Development Tip

Common Reuse Principle

Design components by ensuring that if one class in the component is used, they’re all used.  This will prevent dependent components from having to be needlessly retested and redeployed because a class, that it does not use, was changed within that component.  This aligns with the Law of Demeter by promoting interactions only with closely related classes and by limiting knowledge of other parts of the system.  This also helps to deodorize the smell of Immobility.

Learn More

London vs Chicago

Adopting an integrated approach to TDD.

assistant Learn More

Isolate the SUT

Fostering Defect Localization.

assistant Learn More
assistant Development Tip

Testing in Production

What if I told you that you can test your software in production?  Well, you can with the right testing strategy and correct deployment methods.  A DevOps approach to delivery will enable your team to conduct limited manual testing of new functionality, extensions, and patches in a production environment before releasing to your customers.  Although it should never replace the unit, service, and system-level testing in your lower environments, it can help to ensure that your software will run as expected in production.

Learn More
assistant Development Tip

Always Releasable

Promote continuous delivery by always keeping your software in a releasable state.  Gain high confidence that your software works by integrating changes continuously, while leveraging a deployment pipeline to ensure that your software build is always releasable.  Do not isolate new development within feature branches.  A better way is to integrate those changes earlier by applying them directly on the trunk with a mature suite of tests to protect the build.  Employ DevOps patterns to always keep your software releasable in the midst of constant change.

Learn More
assistant Development Tip

Test Organization

Organize your xUnit test packages, classes, and methods in a way that makes it easy to find, understand, and maintain your tests.  While this is relatively easy with a small number of tests, effectively managing a lot of them takes some planning.  Although there is no single best practice, there are organizational strategies that will go a long way to help keep your tests and test fixtures well organized.  These strategies will also help to promote test code reuse, simplify running test subsets, manage the lifetime of test fixtures, and optimize test execution by managing resources that are expensive to allocate.

Learn More
assistant Development Tip

Feature Envy

Avoid this code smell by encapsulating functionality with the data it changes most often.  Feature Envy occurs when a function frequently interacts with data or functions in other modules.  This violates the Law of Demeter by extending knowledge of other parts of the system.  Apply the principle of Tell, Don't Ask to reduce query functions and encapsulate knowledge, while avoiding Anemic Domain Models.  Consider the Common Closure Principle for functions that query other components and consider the Single Responsibility Principle for functions that query other objects within the same component.  When needed, relocate whole functions by applying the Move Function pattern or split functions using the Extract Function pattern.

Learn More
assistant Development Tip

Commit Tests

Build a suite of tests that is optimized to run very quickly that serves as the entry gate for the deployment pipeline.  In less than ten minutes, the commit stage should either create deployable artifacts or eliminate builds that are unfit for production and notify the team that the application is broken.  This suite of tests should include all unit tests, along with a small selection of other types to ensure that the most common failures are caught early.  None of these should execute via the user interface to safeguard against Fragility and lengthy execution, while promoting Defect Localization.

Learn More
assistant Development Tip

Acceptance Testing

Build automated tests that verify that the acceptance criteria of a user story have been met and demonstrate that the system delivers the value expected by the customer.  These tests are best implemented using the domain language without reference to the application’s user interface (UI).  This will greatly simplify the test fixtures, access to test results, and obscure GUI components.  This, of course, requires a well-factored architecture that decouples all business rules and application logic from the UI.

Learn More
assistant Development Tip

Immobility

Avoid this design smell by building software from which it is easy to extract and reuse internal components in new environments.  This smell is often caused by dependencies that are tightly integrated with other parts of the system.  Promote mobility by decoupling components from low-level implementations, such as data persistence, logging, user interfaces, etc.  For example, business rules should be encapsulated within components to enable reuse across multiple systems.

Learn More
assistant Development Tip

Rigidity

Avoid this design smell by building software that is flexible and easy to change.  Rigidity is often observed when a small change forces a complete rebuild and redeploy.  Small changes should be able to be built, tested, and deployed very quickly and independently of each other.  Long build times are a symptom of high coupling.  To promote flexibility, manage the dependencies between modules to ensure when one module changes, the others remain unaffected.

Learn More
assistant Development Tip

Fragility

Avoid this design smell by building software that is highly modular, highly cohesive, and loosely coupled.  A change to one part of your system should never break another part that is completely unrelated.  High level policies (i.e. business rules) should never be impacted by changes to low level implementations (i.e. data persistence).  Even related functionality should be decoupled enough to extend functionality without affecting related components.  There are many engineering patterns and practices that facilitate flexibility, extensibility, adaptability, along with many other qualities that inhibit fragility.

Learn More
assistant Development Tip

Two Hats

As software is developed by making behavioral and structural changes, do not attempt to do both at the same time.  Wear one hat to add new capabilities (including tests) without changing existing code, then wear the other hat to restructure the code without changing behavior.  Build software by swapping hats frequently to develop and test very small capabilities, then refactor to improve the design and the overall quality of the code.

Learn More
assistant Development Tip

Software Evolution

Build your initial features quickly, then evolve your software based on customer feedback.  This demands having a good suite of tests to prevent cruft and promote refactoring without the fear of breaking those features.  This also requires being able to recognize design smells.  These smells will permeate your software due to bad decisions caused by carelessness and false expedience.  Manage the dependencies within your software to ensure a loosely coupled architecture and...

Learn More
assistant Development Tip

Slow Tests

Build your software and your tests in ways that minimize test execution time.  Common causes for long running tests include over-engineered test fixtures, asynchronous code, components with high latency, Test Overlap, and too many tests due to a tightly coupled architecture.  This causes bottlenecks in Continuous Integration, inhibits rapid feedback facilitated by automated testing, and constrains frequent code merges that are required for Trunk-Based Development.

Learn More
assistant Development Tip

I’ll Fix It Later

Do not fool yourself into thinking you will ever go back and actually rewrite your code the right way.  Any experienced developer will tell you it rarely happens (even with the best of intentions).  And every time you save that cruft for later, you’re introducing Technical Debt, which will do nothing but slow you down.  To go fast, you must go well.  Trading quality for velocity will catch up to you very quickly and slow you down much more than if you had written good code to start.  This does not mean you should...

Learn More
assistant Development Tip

Trunk-Based Development

Embrace continuous delivery by frequently integrating small batches of work directly into the source control trunk (at least once daily).  Short-lived branches can be used to implement pull requests or to isolate release candidates, but should never persist longer than a day.  Utilize Feature Toggles to turn off features that are not yet ready for release.  Pull the Andon Cord and fix regression errors immediately to keep the trunk in a healthy and deployable state.

Learn More
assistant Development Tip

Repeatable Tests

Write tests such that each produces the same result from a given initial state without any manual intervention between runs.  Unit tests must verify Single Test Conditions by executing a single code path through the System Under Test (SUT) and executing the same, exact code path each time it runs.  Verifying one condition for each test helps to minimize Test Overlap and ensures we have fewer tests to maintain if we later modify the SUT.  Isolating the SUT ensures that we only have to focus on code paths through a single object. 

Learn More
school Training

Principles & Practices of Test-Driven Development

This course teaches the principles and practices of Test-Driven Development (TDD) and demonstrates how proper software design evolves through application of the eXtreme Programming principle of Test First.  Unit testing principles are introduced, along with a thorough discussion on the benefits of TDD.  An application is developed (from start to finish) during this course to explain step-by-step and demonstrate first-hand how a high quality, testable design evolves by applying the three laws of Test-Driven Development.

Learn More schedule 3 Hours
computer Code Kata

Prime Transformation

A good TDD developer is one who can apply transformations well.  What are transformations?  They are the opposite of refactoring.  While refactoring is changing the structure of code without changing its behavior, transformations are changes to code that generalize behavior without changing its structure.  In this kata, you will create a function to calculate the prime factors of a given integer using TDD while applying transformations to pass the tests.

Get Started

Doug Klugh

Software Craftsman

Software Development and DevOps Leader, Microsoft developer, and Agile Coach.