Opens in a new tab
Insights / Blog

Good software design

Security, scalability, and maintainability are not future improvements. They are part of building reliable software from the beginning. A recent PHP security issue reminded me of something that…
Kiem HoangOctober 23, 2026
Good software design
On this page

Security, scalability, and maintainability are not future improvements. They are part of building reliable software from the beginning.

A recent PHP security issue reminded me of something that is easy to overlook when building software: getting an application to work today is only one part of the job. Keeping it secure, maintainable, and reliable over the years is another challenge entirely.

In the environment I work with, we maintain more than ten servers and support thousands of customers. Different parts of the system run on different PHP versions, including some very old ones. When a security issue comes out, the response is not always as simple as updating a package and restarting a service.

We first need to understand which systems are affected, which versions are running, and whether a patch is available. We also need to consider compatibility, existing dependencies, and the potential impact on customers.

During this particular incident, some of the older environments were not affected by the specific vulnerability because they did not include the functionality involved. Some newer environments were affected, so we applied the available security updates. IP allowlisting also provides an additional layer of protection for server access.

We were fortunate this time. But relying on luck is not a security strategy.

The cost of decisions

Legacy software is not necessarily bad software. Many systems have delivered real business value for years, sometimes decades. The challenge is that software evolves alongside the business, and not every early technical decision ages well.

An application that started with a small number of users may eventually serve thousands. A quick integration may become a critical part of daily operations. A PHP version that once worked perfectly may eventually become unsupported, making security updates and dependency upgrades increasingly difficult.

At that point, even a seemingly small change can require significant investigation and testing.

The problem is not simply that the code is old. It is that the cost and risk of changing it may have grown beyond what the original design anticipated.

Security cannot be an afterthought

Security updates are essential, but they are only one part of a broader security strategy.

A maintainable system should make it possible to identify affected components, apply patches, test changes, and deploy updates with confidence. Access controls, sensible network restrictions, monitoring, and backups can reduce risk, but they do not eliminate the need to keep software up to date.

It is also important to distinguish between being unaffected by one particular vulnerability and being secure overall. An older runtime may not contain the functionality involved in a specific issue, but that does not make it safe from other vulnerabilities.

The goal should be to reduce our dependence on emergency fixes and make security maintenance a normal part of operating the system.

Scalability is more than handling more traffic

Scalability is often discussed in terms of servers, databases, caching, and infrastructure. Those things matter, but the ability to scale a system also depends on how easy it is to change.

Can we upgrade a runtime without breaking unrelated features? Can we deploy a fix to one environment and verify the result before rolling it out more broadly? Can we identify which customers or components might be affected by a change?

When the answers are unclear, adding more infrastructure will not solve the underlying maintenance problem.

A scalable system should support not only growing usage, but also the growing complexity of the product and the team responsible for it.

Design for the changes you know will come

Good design does not mean predicting every future requirement or building an unnecessarily complicated architecture.

It means making reasonable decisions that keep future changes manageable.

For example:

  • Keep dependencies and runtimes maintainable. Use supported versions and plan upgrades before they become urgent.
  • Separate configuration from application logic. Different environments should not require risky code changes just to operate correctly.
  • Make deployments repeatable. Automated, controlled releases reduce manual mistakes and make rollbacks more practical.
  • Test important behavior. Tests help identify regressions before changes reach customers.
  • Understand the system’s dependencies. Knowing which components rely on one another makes security updates and migrations easier to plan.
  • Treat maintenance as ongoing work. Allocate time for upgrades, cleanup, monitoring, and technical debt instead of postponing everything until something breaks.

These practices may require additional effort at the beginning. Over the lifetime of a system, however, they can save much more time than they cost.

What about systems that are already old?

Of course, we cannot redesign every legacy application from scratch. In many cases, the business cannot afford a long migration or a risky rewrite, especially when customers depend on the system every day.

A more practical approach is to understand the current risks, prioritize the most urgent issues, and migrate in manageable stages.

That might mean upgrading one environment at a time, testing compatibility with real workflows, improving deployment procedures, or isolating components that cannot be upgraded immediately.

The important thing is to have a direction. If a system is running on an unsupported runtime, there should be a clear plan for reducing that dependency, even if the migration takes months rather than days.

Doing nothing is also a decision, and it comes with its own costs.

Build for today, maintain for tomorrow

The recent PHP security issue was a useful reminder that software design decisions have consequences long after the original development work is finished.

We were fortunate that some environments were unaffected by this particular issue, and we were able to patch the affected ones. But the next vulnerability may have a different impact. The next business requirement may expose a different limitation. The next upgrade may be harder than expected.

We cannot prevent every future problem. We can, however, build systems that are easier to understand, safer to change, and simpler to maintain.

Good software design is not about making everything perfect on day one. It is about making sure that improving the software tomorrow does not become unnecessarily difficult.

blank
Kiem HoangxDevLabs LLCHoang Kiem is a software engineer and founder of xDevLabs LLC, specializing in WordPress development, custom solutions, and turning creative ideas into practical digital experiences. He works closely with clients to build tailored websites that balance design, functionality, and maintainability.

Want this on
your app?

Book an audit