Once you realize you have a problem of technical debt, or if you want to investigate your code to see if the problem exists at all, there are objective ways of measuring this. Here are some metrics that might interest you.
Why Metrics
Before we list the metrics, let’s briefly discuss why they’re important.
Metrics can objectify a discussion about technical debt. Some team members may think the situation is extremely bad while others may say everything’s fine. These discussions can become emotional, especially if people take criticism on their code personally.
Metrics will give you objective, almost scientific insights into the state of the code. With many metrics, you’ll even be able to drill down to specific areas in the code that are problematic.
Cyclomatic Complexity
Cyclomatic complexity indicates how complex a piece of code is. For each possible path through the code, the cyclomatic complexity is increased with 1.
I won’t go into too much detail here. But a simple graphic will make it more clear:

The above diagram represents the possible flows in a piece of code. The top circle will be a conditional statement. Depending on the result we go to one of the next circles. And finally, we converge back to the last circle at the bottom.
This code has two possible paths (left and right in the diagram). So this code has a cyclomatic complexity of 2.
That’s simple and great. But if you calculate this metric on different pieces of code in your application, you might get numbers that frighten you!
Cognitive Complexity
Cyclomatic complexity isn’t perfect however. There are blocks of code that that have a higher score for cyclomatic complexity, but are still readable and maintainable.
Cognitive complexity tries to address this issue. It doesn’t just count the number of paths in a block of code. The people at SonarSource finetuned the math after cyclomatic complexity to achieve a better metric.
A high number for cognitive complexity means the code is harder to read, harder to understand and harder to maintain. It’s harder to work with this code, reducing developer productivity and increasing the chances of bugs being introduced.
Cognitive complexity is a better indicator for maintainable code than cyclomatic complexity. So if you can, use this instead of cyclomatic complexity.
Code Churn
Code Churn is a metric that requires source control. Once you have that, you can start measuring code churn. Code churn measures how much code is rewritten, changed or deleted a short time after it has been written.
It’s important to note that code churn isn’t a problem as such. Developers rarely get a piece of code right the first time. They find issues, better implementations or add missing features.
However, code churn can point to pieces of the code that change often and so are volatile. These should probably be tested more than pieces that don’t change often.
It gets even more interesting if you combine code churn with cyclomatic complexity. If a piece of code is very complex and changes often, this increases the chances of a bug being introduced. Again, automated tests will provide a safety net. They will then allow you to refactor so that you can reduce the complexity.
Keep an eye out for code that doesn’t change often because developers are just too afraid to touch it. That’s a bad situation too.
Code Coverage
Code that developers would mark as technical debt is often code that isn’t covered very well by tests. Code coverage is a metric to identify where there are pieces of code that aren’t covered by tests.
It’s useful to gain insights for which features you need to write (more) tests. When you run your tests with a code coverage tool, you’ll get back some numbers and you’ll be able to drill down to the individual code statements to see if they were executed in the test.
You shouldn’t focus on the numbers too much, but you can establish a minimum. For example, you could agree that the project needs a minimum of 80% of the code covered. If you’re not there just yet, you can set a minimum for now and let your CI build break if it drops below that minimum. After a period of adding more and more tests, you’ll be able to increase the number step by step, until you reach a minimum you’re happy with.

Don’t fall into the trap of aiming for 100% code coverage (unless you’re doing rocket automation or software for nuclear power plants!). Somewhere around 80-90% is a very good point to end up with.
Increasing the amount of tests will increase the stability of your product and the confidence you have in releasing new versions.
Know Your Numbers
Objective metrics can help guide the discussion on legacy code and technical debt. There are many more, and there are great tools out there (like NDepend, SonarSource, VeraCode, Code Climate, etc) that can help you get insights in the state and quality of your code.
I recommend you look at them, check your code, set targets and track your progress. Your team productivity and product stability will benefit.