Fintech News

The Question Every Engineering Lead Should Ask: Is Your Codebase Still Safe to Change? — Menahil Tanveer

Menahil Tanveer is a Senior Software Engineer with experience spanning LegalTech, HealthTech and FinTech, where she has worked on products in environments where maintainability, data integrity and architectural decisions have consequences beyond the code itself. Her work has increasingly focused on a problem that often remains invisible until it becomes expensive: how technical debt and architectural complexity accumulate silently as software systems grow.

The rapid adoption of AI-assisted development is making that question increasingly relevant. Engineering teams can now generate and modify software at a pace that puts greater pressure on existing approaches to review, validation and architectural governance. For Menahil, however, the underlying problem predates AI. A codebase can pass tests, satisfy requirements and survive code review while becoming progressively harder to understand and safely change.

Her perspective is shaped by seeing this pattern from different angles. At Portaris, a LegalTech platform, she encountered a roughly 5,000-line Vue component carrying multiple responsibilities, alongside 39 redundant API calls on another page. At Floortje, a HealthTech platform, she applied those lessons earlier in the development lifecycle by establishing a shared component system and building architectural constraints into the product from the outset. She now works in FinTech at Lloyds Banking Group, where questions of architecture, data integrity and risk take on additional significance.

TechBullion spoke with Menahil Tanveer about why code review alone cannot prevent architectural decay, how engineering teams can automate structural safeguards, where human judgment remains essential, and what it takes to build software that remains understandable, predictable and safe to change as it evolves.

Let’s start with your journey, Menahil. How did you get into software engineering, and how has your approach to building products changed over time?

My real interest in software engineering began in the summer of 2018. I was in the middle of my summer break and wanted to use the time to learn something creative. My first thought was graphic design because I liked the idea of combining intuition for good design with something people could actually use. Unfortunately, I missed the enrollment deadline.

My brother suggested web development instead. I wasn’t convinced. I had never enjoyed coding – not when I first learned a programming language at university, and certainly not when I was learning Java. I could never understand why I needed to implement a bubble sort or print a pyramid of asterisks with a for-loop. What I was missing was context. Nobody had shown me how software connected to real problems or how something I built could actually make a difference.

That changed when my brother persuaded me to take a two-month web development course. We started with real-world problems and then used software to solve them. I learned how to build an interface, make it interactive, connect it to a backend and eventually deploy it. For the first time, everything connected. I became obsessed with it. Instead of spending my free time watching TV, I was building small applications and trying to solve one problem after another.

After my final exams, I joined my first role as an Associate Software Engineer, and that experience fundamentally changed how I thought about building software. Early on, my definition of success was simple: does it work? If it worked, I considered the job essentially done.

As I progressed towards senior engineering, I realised that working software is only the starting point. Sustainable software has to remain understandable, maintainable and safe to change as the product and the team grow. That’s when I became increasingly interested in complexity, architectural boundaries and technical debt; not simply how to build software that works today, but how to prevent the structure underneath it from quietly deteriorating tomorrow.

That is where I started considering things like complexity, readability, and maintainability of the software I was building. 

You’ve described sustainable software as software that remains understandable, maintainable and safe to change as a product grows. But those qualities can sound subjective. How do you actually recognise and measure architectural complexity and maintainability in a codebase? What signals tell you that a system is becoming harder to change safely?

Complexity isn’t just the number of lines in a file, it’s how much cognitive load and coordination is required to make a safe change in the file or component. I also look at the cost of change itself as a signal.

If a relatively small product requirement requires changes across multiple components, repositories or services, that’s usually a sign that the architecture has accumulated coupling.

Then I look at things like dependency depth, duplication, component responsibilities, state interactions, API or request fan-out, and cognitive complexity. These are signals that tell me where the architecture is becoming harder to reason about.

Readability is slightly different. I think about it as cognitive load: how much context does an engineer have to hold in their head before they can safely modify something?

For me, maintainability ultimately comes down to three things. Can we understand the system? Can we change it safely? And can we continue changing it without the cost becoming disproportionate to the value of the feature?

That’s what I mean by sustainable software. It doesn’t mean that nothing ever breaks or that every component has to follow the same pattern. It means the system remains predictable enough for engineers to understand its behaviour, isolate problems and make changes without constantly fighting the architecture.

What is a recurring architectural problem you’ve seen in your experience? And why does code review alone fail to prevent it?

The recurring problem I’ve seen is architectural complexity accumulating through individually reasonable decisions.

That’s important because technical debt isn’t normally created by one obvious bad decision. Instead it builds like death by a thousand cuts: you integrate one extra API request for a new feature, append a conditional for a specific edge case, or nest another responsibility within a component because it feels convenient at the moment. You pull in a dependency to bypass an immediate hurdle.

A codebase can remain functionally correct while becoming progressively harder to understand, change and reason about. The problem is that code review is primarily a human judgment process, and reviewers naturally concentrate on whether the logic works, whether the requirements are met, and whether there are obvious defects. They are not consistently measuring structural complexity across an entire codebase.

I’ve seen this across LegalTech, HealthTech and FinTech. At Portaris, for example, I came across a Vue component of roughly 5,000 lines. It was working, but it had accumulated state management, business logic and UI rendering in one place. Nothing in a conventional PR review necessarily says, “this component has crossed the point where its structure is now a risk.”

My view is that architectural quality needs its own feedback loop. Some characteristics are objective enough to enforce automatically, such as complexity, component size and certain structural violations. Then human reviewers can spend their limited attention on decisions that genuinely require judgment. That is the gap my framework addresses: detecting architectural decay while it is still cheap to correct, rather than waiting until it becomes a refactoring project.

How would you go about automating things like complexity, component size and other structural violations?

I would start by making a small set of architectural constraints non-negotiable. Before a developer can commit a change to their feature branch some pre-commit hooks should take place that should fail if there are any errors present in the code base. In order to do that ESLint needs to be configured such that it measures things like cyclomatic or component complexity and size against an agreed upon threshold for example complexity above 10 should fail rather than producing a warning.

The distinction between warnings and errors matter because warnings become background noise. If the team agrees that a complexity threshold represents unacceptable risk, the development workflow should enforce that decision consistently rather than asking every reviewer to remember it.

The goal is to automate the mechanical signals that tell us when architecture is beginning to deteriorate. A team could implement this through linting, static analysis and custom repository checks. In the AI era, I would also package additional rules like SRP (Single Responsibility Principle) and DRY (Don’t Repeat Yourself) into something like a /pr-review skill and integrate that in my pre-commit checks so that the engineer working on the feature could quickly get an initial review from AI before the code even goes on git. 

The important principle is that the feedback happens before human review. If a developer receives an immediate failure saying a component has exceeded the agreed complexity limit, they can fix the structure while they still understand the code. That is much cheaper than allowing the problem through and discovering it months later when several features depend on it.

Once architectural gates are in place, what should code reviewers actually focus on? How does that change the dynamic between automated checks and human judgment?

Once the mechanical checks are automated, I think code review becomes much more valuable because reviewers can stop spending their attention on things a machine can determine consistently.

A reviewer shouldn’t have to debate whether a component is objectively too complex if the repository already enforces an agreed threshold. Instead, the reviewer can ask higher-order questions: Is this abstraction actually necessary? Are we solving a requirement that doesn’t exist yet? Does the business logic correctly reflect the domain?

That creates a useful separation between constraint enforcement and architectural judgment.

For example, if a PR passes the complexity and structural gates, I don’t consider that evidence that the design is good. It simply means the obvious structural risks have been removed. A reviewer can then concentrate on whether the design makes sense in context.

This also changes the relationship between developers and reviewers. Automated gates become an objective baseline rather than a personal preference expressed during review. Human review becomes a discussion about trade-offs and intent rather than formatting, component size or whether someone remembered a particular architectural rule.

I see this as particularly important as teams adopt AI-assisted development. If AI increases the amount of code being produced, we cannot simply increase the amount of human review. We need to make the automated boundary stronger while making human judgment more deliberate.

You’ve worked in healthcare and fintech, where architectural decisions have real consequences. How does that constraint shape your framework differently than it would for, say, a social media app?

The framework becomes stricter when the consequences of architectural failure are higher. In a social media application, a poor abstraction might make a feature harder to maintain. In healthcare or fintech, the consequences can extend into data integrity, auditability, compliance and ultimately harm to users.

That changes how I think about architecture. In regulated environments, architecture isn’t just about producing elegant code. It is part of risk management.

In healthcare, for example, the integrity of data flowing through a system matters because an incorrect or inconsistent result can affect decisions about a person. In fintech, redundant or inconsistent API interactions can create problems around the system of record, transaction state or compliance trails. That doesn’t mean every regulated system should become excessively complicated. It means we should be deliberate about which constraints are non-negotiable and why.

My framework therefore starts with risk, not with a favourite architectural pattern. Complexity thresholds, separation of responsibilities and source-of-truth boundaries can be enforced where they protect the system. But the exact constraints should reflect the domain. A banking system and a consumer social application shouldn’t necessarily have identical architectural rules simply because they use the same programming language.

So Menahil, at Portaris, you came across a 5,000-line component and 39 redundant API calls. Walk us through how you diagnosed these as problems rather than accepting them as working code, and what you did about it.

The important part of the Portaris experience wasn’t simply that I encountered a 5,000-line component. It was recognising that its size represented an architectural problem rather than merely an unpleasant file.

The component had become responsible for state management, business logic and UI rendering at a monolithic scale. That meant changes in one responsibility could affect another, and understanding the consequences of a change required navigating a very large amount of unrelated behaviour. I decomposed it into seven focused subcomponents, giving each a clearer responsibility and reducing the amount of reasoning required to make changes.

I encountered a different form of the same problem elsewhere: 39 redundant API calls on a page. The application was working, so those calls could easily have been accepted as normal. Instead, I looked at what the calls represented. Multiple requests were retrieving information that could have been consolidated, creating unnecessary network activity and, more importantly, multiple opportunities for data to drift away from a single source of truth.

I removed that redundancy while delivering a feature rather than treating it as an isolated technical-debt project.

Those two examples reinforced the same principle for me: architectural decay often hides behind working behaviour. You have to examine the structure and the relationships in the system, not just whether the current feature produces the expected result.

If another engineering team adopted your framework with automated pre-commit checks, constraint-aware design, focused human review, what should they expect? What’s the realistic impact, and what’s the common failure mode?

A team adopting this framework shouldn’t expect a dramatic overnight transformation. The first benefit is usually visibility. Once complexity, component size and structural violations are measured consistently, teams begin to see patterns that were previously subjective or invisible.

The second benefit is that technical debt becomes cheaper to address. If a component fails a complexity gate while a developer is actively working on it, the cost of correcting the structure is relatively small. If the same component grows for another year and becomes a dependency for multiple features, the cost changes completely.

I would expect teams to see fewer oversized components, less structural duplication and more deliberate boundaries over time. The exact impact depends on the codebase and the thresholds the team chooses, so I wouldn’t promise a universal percentage improvement.

The common failure mode is turning the framework into a collection of arbitrary rules. If teams enforce every metric without understanding why it matters, developers will optimize for passing the checks rather than improving the architecture. You can end up with code that technically satisfies the gates but is still poorly designed.

That’s why the framework has three layers. Automation establishes the baseline, human review evaluates intent and trade-offs, and domain constraints determine what risks actually matter. The objective isn’t maximum compliance with rules. It is reducing the amount of architectural risk that reaches the point where it becomes expensive.

Your framework enforces structural constraints (SRP, complexity limits, etc.). Where does that end? What should still be left to human judgment and team convention?

I don’t believe everything should be turned into an automated rule. That would simply replace one problem with another: engineers would spend their time satisfying constraints instead of exercising judgment.

I would enforce structural characteristics where there is a clear relationship between the violation and maintainability or risk. Excessive complexity is a good example. A component that becomes extraordinarily complex is harder to reason about, test and change. Similarly, a clear single-responsibility violation can be a useful signal that responsibilities have become entangled.

But those checks don’t tell you what the correct design should be.

Whether an abstraction is appropriate is a judgment call. Whether duplication is genuinely harmful or deliberately isolates two domains is contextual. YAGNI is also fundamentally about judgment: sometimes an abstraction is unnecessary; sometimes you’re deliberately establishing a boundary because you know two concepts will evolve independently.

Team conventions matter here too. Different organisations can legitimately make different architectural choices.

So I see principles as boundaries, not prescriptions. The automated layer should stop the system drifting into known danger zones. Human engineers should decide what the system should actually look like within those boundaries. If we try to automate every architectural decision, we remove the very judgment we need experienced engineers to provide.

Looking back at these experiences, what do you think separates a good software engineer from an engineer who can genuinely influence the direction and success of a digital product?

A good engineer writes clean code. Tests thoroughly. Find clever solutions. That’s table stakes.

But an engineer who influences products? They understand the domain. They talk to users. They ask: Why are people dropping at this point in the journey? Is it UX friction or response time? What are users actually interested in, so we optimize that part first, not what we assume matters.

The difference is: good engineers optimize the code. Influential engineers optimize for the person using it. And that requires leaving your IDE and understanding the domain, the users, their constraints, and their goals.

The pattern I’ve seen repeat across LegalTech, HealthTech, and FinTech is that architectural decay is invisible until it’s expensive. Nobody wakes up wanting to write unmaintainable code. But a component that handles one more thing, an API called from one more place, a small duplication that ‘seems fine for now’, these are all sensible decisions at the moment. The cost compounds silently.

What shifted for me is asking teams to reframe code review: instead of asking ‘Did this pass the tests?’, also ask ‘What does this structure make harder six months from now?’ That second question doesn’t need new tools or frameworks. It needs the people building the system to think like the people who’ll inherit it. And that’s something every engineer can do tomorrow.

Comments

TechBullion

FinTech News and Information

Copyright © 2026 TechBullion. All Rights Reserved.

To Top

Pin It on Pinterest

Share This