Evaluate AI generated code: the two metrics
Matteo Migliore

Matteo Migliore is an entrepreneur and software architect with over 27 years of experience developing .NET-based solutions and evolving enterprise-grade application architectures.

He has led enterprise projects, trained hundreds of developers, and helped companies of all sizes simplify complexity by turning software into profit for their business.

The quarterly review was going beautifully, thanks to AI generated code.

The engineering lead was projecting a chart of the last three months of releases: the line was climbing.

The team had adopted a coding assistant in January, and since then the backlog had been emptying at a speed nobody could remember seeing before.

Four weeks after that review, the same system sat down on a Friday afternoon, with customers connected, and nobody in the company could explain why.

I have watched this scene repeat itself at companies with nothing in common, and it always ends the same way: someone opens the file responsible and realises that nobody wrote it.

AI generated code is not replacing programmers.

It is doing something more insidious and more expensive: it is filling companies with software that works and that nobody is able to judge.

And the problem is not technical, it is an accounting problem.

For the first time in the history of software there is a tool that improves every indicator management looks at (commits, tickets closed, features shipped) while degrading every indicator management does not look at.

Nobody notices anything until the bill arrives.

This article is about what that bill is made of: what unsupervised synthetic code actually produces, how you measure it with numbers instead of gut feeling, which technical choices AI cannot make for you, and who inside a company has to be able to say no.

If you are rolling these tools out across a team, the part that matters most to you is the one about the two measurements that get you listened to by the people who decide.

How many paths the code can take inside a single function, and how many classes you have to understand in order to change one of them.

AI generated code is not wrong: it has no future

Let us go back to that Friday afternoon.

The system did not give way because somebody had written bad code.

The code compiled.

The tests passed, the ones generated alongside the code.

The features did exactly what they were supposed to do in normal cases, and they had been tested for weeks without a single problem.

It fell over at the first case nobody had described: five hundred people editing the same records at the same moment, after a migration onto a production database twenty times the size of the test one.

There is only one reason, and it is the sentence worth taking away from this article.

AI answers your last prompt, not the life cycle of your system.

It generates the perfect solution to the problem you described this morning.

Your system, however, has to survive the mornings that come after, for years: requirements that change, a team that grows from three people to twenty, a load that triples because the business is doing well.

It is the difference between a cook and someone opening the fridge at eleven at night because they are hungry.

Both of them produce something edible.

Only one of them also has tomorrow's dinner in mind, the week's shopping, and the fact that somebody else will have to clean that worktop.

The practical consequence is that debt produced this way looks nothing like the debt we know:

Classic technical debtAI generated debt
Who decided to take it onsomeone, knowingly, under a deadlinenobody
Where it sitsin the places someone wrote down somewherespread evenly across the whole codebase
Who can explain itthe author, who knows why they chose that waynobody, because there is no author
Can it be paid backyes, it is a trade-off whose price is knownnobody knows where to start

And that is why it escapes every practice for governing technical debt that was invented when only people wrote code: those practices assume that somewhere there is someone who knew what they were doing.

The six recurring defects of AI generated code

What is striking, after watching these situations repeat, is that the symptoms all resemble each other.

The companies change, the sectors change, the teams change, and you keep finding the same six defects.

They are not random defects: they are the direct consequence of how the tool works.

Describe one operation, get one operation.

Everything you did not name in the prompt simply does not exist.

  • Methods that do too many things: the prompt describes a complex operation without breaking it down, and you get a single function that performs all of it. The result is code you cannot test in pieces, cannot reuse, and where changing one line requires understanding all the rest.
  • Architectural boundaries ignored: without an explicit instruction in the prompt, the AI reaches the data from wherever the request arrives, meaning an ASP.NET Core controller querying the DbContext directly, and mixes business rules with presentation. From that point on, any reorganisation means a rewrite.
  • Errors swallowed in silence: empty exception handling blocks, or blocks that log one generic line and carry on as if nothing happened. In production that means data quietly corrupting for hours before anyone notices, and the one who reports it will be a customer.
  • Tests that verify their own implementation: they check that a given method was called with given parameters, a Moq Verify on the repository call instead of an assertion on the result, rather than checking that the system produces the right outcome. They break at every reorganisation and hand you a false sense of safety, which is worse than no safety at all.
  • No handling of simultaneous access: almost no prompt mentions it, so nothing in Entity Framework Core ends up checking for row level concurrency. The code is correct with one user on a test database, and catastrophic when two SaveChanges calls start together and the last one erases the work of the first.
  • Third party libraries picked at random: NuGet packages suggested without anyone checking their maturity, their last update or their known vulnerabilities. You end up with a chain of dependencies nobody has ever looked in the eye and nobody is keeping up to date.

The thread running through all of them is the same: they are all things an experienced person considers without thinking, because they have already paid the price of not considering them.

So the problem is not the tool.

It is using it without the foundations to judge what it produces.

A scalpel in the hands of someone who knows anatomy saves lives.

In the hands of someone who does not, it does damage with impressive precision.

Coding assistants work the same way, and the difference between the two scenarios is not the tool: it is who is holding it.

And this is where the market has moved the price.

The skill the market pays most for today is not knowing how to use coding assistants: it is knowing how to say no at the right moment, and how to explain why.

How to measure the quality of AI generated code: two numbers to take into the meeting

Sooner or later the moment comes when you have to convince someone who is not technical.

You go to your manager and tell them that code is fragile.

They look at the release chart climbing and ask you what you are basing that on.

"In my opinion" is not an answer that survives that meeting.

Which is why the two metrics that follow are worth more than any argument: they turn an impression into a number that fits on a slide.

The first is called cyclomatic complexity, it was defined by Thomas McCabe in the nineteen seventies, and it counts how many different paths execution can take inside a single function.

It is a way of measuring how many ways that piece of code can behave.

Synthetic code produced without supervision typically sits between 15 and 30.

The recommended value for maintainable code is under 10, and the still acceptable value does not go above 15.

Translated for the person who decides: every extra point is one more test case to write, one more way to break in production, and one more day of work for the next person who has to touch that function without having written it.

That next person is often the same person, eight months later, remembering nothing.

The second metric measures how many other classes a class has to know about in order to work.

Code generated from unstructured prompts typically sits between 8 and 15, where the healthy value is under 5.

The practical meaning is immediate: to change one thing you have to understand ten others.

It is the domino effect that turns a half hour change into a week of work done with fear in your stomach, and in codebases with years of unchecked automatic generation these values reach a point where starting again costs less than fixing.

Taken into a meeting, the two numbers fit on two lines:

MetricWhat it countsCode generated without supervisionHealthy value
Cyclomatic complexity (McCabe)how many different paths execution can take inside a single functionbetween 15 and 30under 10, still acceptable up to 15
Class couplinghow many other classes a class has to know about in order to workbetween 8 and 15under 5

These two numbers, read together, are the most effective tool you have for getting listened to.

They do not ask anyone to trust your instinct: they show a curve climbing month after month, over the same period when the release curve was climbing too.

It is also the reason some companies have stopped waiting for this role to arrive from the market and are building it in house.

They call it the AI Quality Gate Engineer: it sits between the output of the assistants and the code that goes into production, and the companies that have built the role internally pay it more than a senior developer.

Demand outstrips supply, because that combination of architectural skill and understanding of the tools is still extremely rare.

Extremely rare means two things: companies are looking for it and not finding it, and nobody is teaching it.

The AI Software Architect Course exists to fill exactly that gap, with reviews of your code and your numbers.

You do not learn to use AI better: you learn to judge its output before it becomes somebody else's problem.

The silent security debt: the flaws no test catches

AI generated code needs its own security checks

There is a category of problem that shows up in no metric and that companies underestimate more consistently than any other.

It brings nothing down, it slows nothing down, it never surfaces during testing.

It sits there and waits.

Generated code works, but it is not secure by default, and the flaws it produces have a recurring shape.

Checks on the data entering the system are missing, because the tool assumes it arrives already clean from whoever is calling.

That is the most reasonable assumption in the world inside an example, and the most dangerous one in an exposed application.

Objects are rebuilt from external data without an explicit list of permitted types: it is the classic way in for anyone who wants to run arbitrary code on your server.

Authentication tokens are handled without strict checks on expiry and revocation, which means access taken away from someone can keep working for hours.

And every so often an obsolete hashing algorithm such as MD5 or SHA1 turns up applied to sensitive data, simply because it was there in the examples the model was trained on.

That last point deserves a moment, because it explains all the others.

These tools learned from the code that existed, and the code that existed contained twenty years of superseded practice.

They are not getting it wrong: they are faithfully reproducing a historical average in which yesterday's mistakes weigh as much as today's solutions.

None of these problems makes a test fail.

All of them become an incident the day someone competent goes looking for them on your production system, and at that point the conversation is no longer with your engineering lead: it is with a customer, a legal team and, if personal data is involved, a regulator.

What AI generated code costs after six months: how it shows up in the two metrics

The bill does not arrive as a catastrophe.

It arrives as days that stretch, which is why almost nobody connects it to the cause.

In the teams I have worked with after an incident the sequence is always the same, and it reads exactly in the two numbers above.

Median complexity in the functions touched by automation starts from the project's healthy values and, over six months, settles firmly above the warning threshold.

Coupling follows a quarter later, because it takes time for the new classes to hook into everything else.

By the time those two curves have finished climbing, the team notices somewhere else entirely.

Estimates start being systematically too low: a change budgeted at half a day takes two, and not because anyone worked badly, but because touching one thing means understanding ten others.

Incidents that require an emergency rollback to the previous version grow exponentially, and the share of time the team spends fixing rather than building overtakes what it was before the tools were adopted.

These numbers do not come from a theoretical study: they come from post incident reviews at teams that come back asking for architectural support after months spent accumulating technical debt without noticing.

The cruel part is the timing.

The speed gain is immediate and visible to everyone. The cost arrives six months later, and it presents itself as "the team is less productive than it used to be".

At that point the most natural conclusion, and the most wrong one, is that what is needed is more automation.

It is the same mechanism as taking out one loan to pay off another: in the moment the numbers add up, and every month the problem gets a little larger than it looks.

What thresholds to set on generated code before it reaches production

Giving up the tools achieves nothing, and it is a position as comfortable as it is wrong and doomed to fail.

What you need is a competent human checkpoint before the code enters the shared codebase, equipped with explicit criteria rather than the common sense of whoever happens to be reviewing that day.

It is worth being practical here, because this is the point where most teams stop.

They know a check is needed, they do not know what to put in it, and they end up adopting a generic rule along the lines of "generated code must be reviewed", which nobody manages to apply consistently and which disappears after three weeks.

The rules that survive three weeks are three, and none of them is about the tool:

  • A human owner for every change: the name of whoever accepted that code, not of whoever ran the prompt. If that piece breaks tomorrow, there has to be someone who can explain why it is built the way it is. It is the rule that on its own removes half the problem, because nobody puts their name on something they have not read.
  • Numeric thresholds declared in advance: above 15 cyclomatic complexity the change does not pass and has to be broken down, above 5 known classes the boundary gets discussed before anything else is written. Not "we will judge case by case": two written numbers, the same for everyone, which take the discussion off the ground of opinions and personal sympathies.
  • The constraints inside the request, not after it: the architecture, the boundaries between modules and the project's conventions have to be in the prompt before the code exists. Correcting downstream costs ten times as much and always produces the same result: a patch consistent with nothing.

They are three unremarkable rules, and the reason almost nobody has them is not technical difficulty.

They require someone with the authority to block a release, and that authority has to be granted in advance, not in the moment it is needed.

Everything that comes after, meaning turning them into a written company policy with risk zones and automated checks in the release pipeline, is a different job and you will find it explained in governing generated code inside a company.

The question is not "how much code can we produce", but "who in this company is able to judge it".

If the answer is one single person, that person is your breaking point.

If the answer is nobody, the bill is already on its way, even if you cannot see it yet.

You have read the three rules and they are unremarkable.

The reason they do not exist at your company is not technical: it is that nobody has the authority to stop a release, and that authority does not arrive with the title on the business card.

It is built by demonstrating, one decision at a time, and it is the work we do in the AI Software Architect Course.

You learn to write the thresholds, to get them accepted, and to defend them when somebody tries to skip them.

Websites and web applications: the choice AI cannot make for you

Websites and web applications call for different choices

Imagine you have to construct two buildings.

The first is a shop window on a street: it has to look good, it has to be visible from a distance, and anyone walking past has to understand in three seconds what you sell.

The second is an industrial plant: nobody looks at it as they pass, but three hundred people work inside it at the same time and it must never stop.

Nobody would use the same design for both.

In web development it happens every day, and it is the decision no automated assistant will ever make for you, because it requires knowing what that system will be in five years.

The real divide is not between two technologies.

It is between people who build websites and people who build web applications.

They look like the same thing and they are not: they have different business models, different value curves and radically different pay for the people who build them.

A website, however sophisticated, exists to communicate: to deliver content, convert visitors, rank.

It is measured in organic traffic, load time, conversion rate.

Server side rendering is decisive, because the content has to be readable by search engines without them having to execute code to see it.

A web application exists to run processes: coordinating operations across several people, keeping data consistent while everyone writes, responding to events in real time.

Here search engine ranking is irrelevant.

What counts is responsiveness, robust business rules, and the ability to grow without losing consistency.

Everything else follows from that distinction, and anyone who skips it gets the choice wrong in eighty per cent of cases, however good the code they write afterwards.

It is also the point where an automated assistant is most dangerous, because it will give you an answer regardless.

You ask it how to implement a particular feature and it implements it, never once stopping to ask you whether that system is going to be a shop window or a plant.

Someone has to ask you that question, and if nobody at your company asks it, the answer gets chosen by chance.

ASP.NET MVC, Blazor and Minimal API: which to use and when

Building websites with ASP.NET Core MVC remains the professional choice for high traffic sites where ranking matters.

The server returns pages ready made: search engines read them without friction and the initial response time is optimal, because the browser does not have to execute anything before showing something.

For a high traffic blog, a content portal or an online shop with thousands of products, in 2026 it is still the professional market's reference point.

Blazor for interactive web applications in C# answers a different need: building rich, responsive interfaces without writing JavaScript, using the same language as the rest of the system.

Blazor Server keeps the processing on the server and synchronises the interface over a persistent connection.

It is the right choice for internal line of business applications, where the network is reliable and under your control.

Blazor WebAssembly runs everything inside the browser, removes the continuous dependency on the server, and allows applications that keep working even when the connection goes away.

The most underrated advantage, though, is neither of those two: with a single language from the database to the interface, the data models exist only once.

An entire category of bugs disappears, the ones where the server side definition and the browser side definition drifted apart and nobody noticed until release.

Validation rules are written once and hold on both sides.

It is also why automated assistants make fewer mistakes on this kind of project: there is one single truth to respect, not two that have to agree with each other.

There is then a detail that decides far more bad days than its reputation suggests, and it concerns how long the services the system builds for you stay alive.

In ASP.NET Core MVC every request opens and closes its own scope: a service is born with the request and dies with the request.

All predictable.

In Blazor Server it is not.

Components live as long as the user's connection lasts, which for a long session means hours, sometimes days.

That same service now lives as long as the session, not as long as the operation.

The practical consequence concerns the database access context, which is not designed to be used by several operations at once.

Sharing it for a whole session is a time bomb: two simultaneous actions on the same connection, and you end up with errors you cannot reproduce locally.

Along with them come the memory build ups that grow quietly as more users connect.

It is exactly the kind of problem an automated assistant will never flag for you, because the code it wrote is correct: it is the context it runs in that nobody told it about.

There is also a third road, often treated as a shortcut for the lazy and in fact perfectly legitimate: the Minimal API of ASP.NET Core.

Introduced in .NET 6 and improved in .NET 8, they let you define endpoints with a minimal syntax, cutting away the structure that in simple cases serves nobody.

They are the correct choice for specialised services, internal communication between parts of the same system, notifications arriving from external services, and every scenario where the full structure would add complexity without adding value.

The downside is equally clear: that same absence of structure makes them unsuitable as the main approach for interfaces with hundreds of endpoints.

There, the rigidity becomes the reason why, two years later, someone can still find their way around the project.

Stripped to the bone, the three roads are chosen like this:

When it is the right choiceThe constraint to know first
ASP.NET Core MVChigh traffic sites where ranking matters: blogs, content portals, online shops with thousands of productsthe server returns pages ready made, so rich responsive interfaces need something else
Blazor Serverinternal line of business applications, where the network is reliable and under controlcomponents live as long as the user's connection: a service lasts as long as the session, not as long as the operation
Blazor WebAssemblyapplications that have to keep working even when the connection goes awayeverything runs inside the browser, with no continuous dependency on the server
Minimal APIspecialised services, internal communication between parts of the same system, notifications arriving from external servicesunsuitable as the main approach for interfaces with hundreds of endpoints

The right choice is always made by looking at the expected life of the application.

A system meant to evolve for ten years with changing teams needs a structure that guides the decisions of whoever arrives later.

A prototype to be delivered in four weeks to validate a hypothesis can be born lean, and it will be rewritten properly if the hypothesis holds.

Confusing the two contexts is one of the most expensive mistakes a team can make in its first weeks.

And it is the mistake generative tools systematically encourage, because they answer the last request and have no idea where that code will end up.

Your assistant answers the last request.

Your system has to hold for the next ten years.

No tool closes that gap: it is closed by someone who knows what every choice costs two years out, and in the AI Software Architect Course it is the thing we work on every week, on the real projects the participants bring.

You stop choosing your stack on instinct and start choosing it against the expected life of the system.

I run the training in person, so the number of places is what it is.

How to measure cyclomatic complexity and coupling in a .NET project

Measuring .NET code makes technical debt visible

Thresholds achieve very little as long as they stay a conversation.

They become a checkpoint the day anyone on the team can produce those two numbers in two minutes, without asking permission from anybody and without installing anything.

In Visual Studio the path is Analyze, then Calculate Code Metrics, and you can run it on the whole solution or on a single project.

The result is a table with one row per assembly, per type and per method, and the two columns that interest us are cyclomatic complexity and class coupling.

Alongside them you also get a maintainability index from 0 to 100 that summarises the rest: handy for a snapshot, useless for a discussion, because it never tells you which piece is the problem.

The right way to read that table is not from the top.

You sort by complexity in descending order and look at the first twenty rows: they are your concentrated debt, and they almost always sit in three or four files.

A single measurement, though, proves nothing, because no project was ever born clean.

What proves something is the same measurement repeated.

You save today's result alongside the code, with the date, and you do it again every time you close a significant release.

After three measurements you have a curve, and a curve is the only thing that beats a rising release chart in a meeting.

Anyone who wants to take that measurement out of people's hands moves it into the build.

The analysis tools already included in .NET can check both complexity and coupling on their own: they are usually set to do nothing more than warn, but they can be configured to treat crossing the threshold as a real error, on a par with a forgotten bracket.

From that moment the threshold stops being a recommendation that some people remember and others do not: it is a build that stops.

And a build that stops does not need authority to be respected, which moves the problem to the level where it is easiest to solve, meaning before the code exists rather than afterwards, in a meeting where somebody has to be proved right.

One word of warning, because this is the point where teams hurt themselves.

Switching that check on across an existing codebase produces hundreds of warnings on the first day, the team turns it off within a week, and that is the end of it.

You switch it on for new code, you freeze the old code as an accepted starting point, and you look only at the difference.

Visual Studio 2026: the tools that turn a suspicion into a measurement

Everything you have read so far rests on one condition: someone has to be able to see what is happening inside the system.

Without that condition, quality control is one opinion against another opinion.

And between two opinions, in a meeting, the one belonging to the more senior person wins.

Which is why the development environment has stopped being a matter of taste.

Five years ago it was the place where you wrote code.

Today it is the place where you verify what somebody else wrote.

Visual Studio 2026 opens a window while the application is running, and inside it you see three things you will never see in a code review.

The first is every question your program is asking the database, at the exact moment it asks.

That sounds like very little.

Then this happens to you: you load a hundred orders and for each one you read the customer name, an innocuous line, a property like any other.

A hundred database queries appear on screen, one per customer, when one would have been enough.

In the code there is nothing suspicious about that line.

There, in a row, with the timing written next to each one, it is impossible not to see it.

The second thing you see is where the time goes.

Not "the system is slow": which function, for how many milliseconds, called by whom.

The third is two snapshots of memory taken minutes apart and compared, so you discover what the system keeps accumulating and never throws away.

These are three problems tests do not see, and not because the tests are badly written.

They run on tiny datasets with a single user: in there, a hundred database queries cost nothing and the memory does not have time to fill up.

They show up when real data and real users arrive.

Which is to say in production, on a Friday afternoon.

In the same environment there is also the side that writes the code, and it is worth understanding what changes.

An assistant disconnected from the project only knows the file you have open.

An agent that knows the architecture you chose and the conventions the team adopted can flag when you are breaking a rule you set yourself.

It does not remove the need for someone to judge.

It only changes what gets discussed: no longer "it compiles", but "it sits where we decided".

One last advantage remains, and it is the one that weighs most in an article about debt.

Code written years ago on this stack keeps working, and few ecosystems can promise that.

If your codebase does not have to be rewritten every eighteen months because somebody changed their mind about conventions, you at least have the time to pay back the debt you accumulate.

The Software Architect: the role AI makes indispensable

The Software Architect guides the choices that go beyond AI code

In the end, everything converges on a single question: who, inside the company, has the authority and the competence to stop something.

Not whoever writes the most code.

Whoever looks at a change and understands what it will do to the system in two years, and is listened to when they say so.

The Software Architect is not a senior developer with a few more years behind them.

It is the person who takes an ambiguous, often contradictory requirement and translates it into a structure that can carry the company's growth: a structure where twenty people can work in parallel without treading on each other, and flexible enough to absorb requirements that do not exist yet.

With generative tools, this role has stopped being a luxury for large enterprises.

While code is produced at a speed no team can read line by line any more, someone has to look at the whole.

To measure the debt accumulating week after week.

To decide when to stop new features and restore order, and when instead you can live with an imperfect solution, which is an equally professional decision.

The more code automation produces, the more valuable the person who knows how not to write any.

There is a simple way to tell whether this role really exists at a company or is just a title on a business card.

Watch what happens when somebody proposes stopping a release.

If the answer is "let us look at it after we ship", that person is not an Architect: they are a senior developer with a nicer title.

The authority to say no is the operational definition of the role, and either it exists on the org chart or it exists nowhere at all.

Which also explains why this role does not emerge inside the teams that need it most.

Someone who has spent five years shipping fast has learned extremely well how to ship fast, and the moment you ask them to assess the impact of a choice over the next three years they find themselves without the mental tools to do it, not out of laziness but because nobody ever asked them before.

This role cannot be improvised and cannot be self certified.

It does not emerge spontaneously from ten years of unguided work, because ten unguided years produce the same habits ten times over.

It takes prolonged exposure to complex systems in production, with real problems to solve while users wait and somebody asks for an estimate.

It takes someone who has already made the architectural mistakes you are about to make, so that you learn the consequences without you or your client paying for them.

And it takes the daily habit of judging code you did not write, including generated code, until you recognise the problems at a glance rather than after the incident.

The rest is study, and the full path with the skills and the order in which to tackle them is set out in how to become a Software Architect.

There is one skill on that path, though, that almost nobody really trains, and it is the one that decides careers: being able to explain an architectural decision to someone who is not technical, translating it into cost, risk and time.

I have seen projects die from a communication problem, not a technical one: choices every developer had understood perfectly, and that nobody managed to explain in time to the person signing off.

Anyone who develops only technical depth stays an excellent executor with a ceiling they will discover late.

Anyone who develops only the ability to talk becomes a manager who does not survive the first difficult meeting.

You need both, and the second is built by writing: a document that sets out in black and white the alternatives considered, the criteria for choosing, and the expected consequences, in a form the decision maker can read and use.

In the programmes this progression is structured deliberately.

The work is not only about how to write better code: it is about how to demonstrate, in cost and in risk, why that code is better.

Because it is the one thing no reorganisation can take away from you: it does not sit in the company, it sits in what other people know they can take for granted when you are there.

Automation has changed one single thing, but it has changed it for good: writing code has stopped being the scarce part of the job.

What remains scarce is judgement, and judgement is built in one way only, with someone correcting you on your own mistakes, your own code, your own project.

I run the programmes in person, so the number of people I can take on is limited and the selection is real: if during the first call I realise your problem is solved another way, I will tell you and save you the money.

If it is the right road, we start from the system in front of you today, not from an exercise.

You have finished this article with something most of your colleagues do not have: you know how the damage is measured, you know where to look, and you know who should be able to say no.

All that is missing is the part no article can give you, which is someone who looks at your system and tells you where you are going wrong right now.

That is exactly what the AI Software Architect Course is, and I do not take on more people than I can genuinely follow.

Anyone who executes specifications written by others will always find work.

Whoever decides how the system is built, on the other hand, gets to choose it.

Frequently asked questions

By the architectural boundaries, not by the bugs. The most common signal is the entry point querying the data context directly, with business rules blended into presentation. The code compiles, tests pass and features do what they should in normal cases. The problem surfaces at the first case nobody described, because the tool answered the last prompt rather than the life cycle of the system.

With two metrics that turn an impression into a number. Cyclomatic complexity, defined by Thomas McCabe, counts how many paths execution can take inside a single function: the recommended value for maintainable code is under 10, still acceptable up to 15. Class coupling counts how many other classes a class must know to work, and the healthy value stays under 5. Read together, they tell you what changing that code will cost in two years.

In Visual Studio through Analyze, then Calculate Code Metrics, on the whole solution or a single project. The result is a table by assembly, type and method: sort by complexity descending and look at the top twenty rows, which almost always sit in three or four files. A single measurement proves nothing. What proves something is the same measurement repeated at every significant release.

Above 15 cyclomatic complexity the change does not pass and must be decomposed, above 5 known classes the boundary gets discussed before anything else is written. The difference between a threshold that works and one that disappears after three weeks is that the first is written down and equal for everyone, while the second says we will judge case by case. The analysers included in .NET can treat crossing it as a build error.

Flaws no test catches because nothing breaks. Input validation is missing, because the tool assumes data arrives already clean from the caller. Objects are rebuilt from external data without an explicit allow list of accepted types. Authentication tokens run without strict expiry and revocation checks. And obsolete algorithms such as MD5 or SHA1 appear on sensitive data, inherited from the code the model was trained on.

The bill does not arrive as a catastrophe, it arrives as days that stretch. Median complexity in the functions touched by automation settles above the warning threshold, coupling follows a quarter later, and the team notices somewhere else entirely: estimates start being systematically too low. The speed gain is immediate and visible, the cost arrives later and looks like a generic drop in productivity.

A website exists to communicate: it is measured in organic traffic, load time and conversion rate, and server-side rendering is decisive because content must be readable by search engines without executing code. A web application exists to run processes: ranking is irrelevant, what matters is responsiveness, robust business rules and growing without losing consistency. It is the choice no automated tool will make for you.

ASP.NET Core MVC for high-traffic sites where ranking matters. Blazor Server for internal line-of-business apps, with one constraint to know first: components live as long as the user connection, so sharing the data context for the whole session produces errors that never reproduce locally. Blazor WebAssembly when the app must work without a connection. Minimal API for specialised services, never as the main approach across hundreds of endpoints.

Because while code is produced faster than any team can read line by line, someone has to look at the whole: measure the debt accumulating and decide when to stop new features to restore order. The operational definition of the role is the authority to say no. If the answer to stopping a release is let us see after shipping, that figure does not exist in your company.

Leave your details in the form below

Matteo Migliore

Matteo Migliore is an entrepreneur and software architect with over 27 years of experience developing .NET-based solutions and evolving enterprise-grade application architectures.

Throughout his career, he has worked with organizations such as Cotonella, Il Sole 24 Ore, FIAT and NATO, leading teams in developing scalable platforms and modernizing complex legacy ecosystems.

He has trained hundreds of developers and supported companies of all sizes in turning software into a competitive advantage, reducing technical debt and achieving measurable business results.

Stai leggendo perché vuoi smettere di rattoppare software fragile.Scopri il metodo per progettare sistemi che reggono nel tempo.