We Got Better at Building Software — But Did We Get Better at Building the Right Software?

9 32 164
calendar_today agoschedule17 min read

There is something interesting happening in software development.

We can build applications faster than ever.

A developer with a laptop, a few good tools, an AI assistant, a cloud account, and a weekend can create something that might have taken a small team weeks or months a few years ago.

We have better frameworks.

Better databases.

Better deployment platforms.

Better observability.

Better testing tools.

Better documentation.

And now we have AI tools that can generate code, explain unfamiliar systems, write tests, refactor functions, create documentation, and help us move from an idea to a working prototype incredibly quickly.

Yet there is a question I think developers need to ask more often:

Are we actually getting better at building software, or are we simply getting faster at producing it?

Those two things are not the same.

Speed is valuable.

But speed without direction can create problems faster.

A badly designed feature can now be implemented in an afternoon instead of a week. A poor architectural decision can spread through an entire codebase before anyone notices. A product nobody needs can be launched before the team has even finished asking whether the problem is worth solving.

The future of software development may not belong to the people who can write the most code.

It may belong to the people who can decide what should be built, what should not be built, and why.


The New Developer Superpower Is Not Writing Code

For a long time, programming ability was strongly connected to how quickly someone could turn requirements into code.

You learned a programming language.

You learned frameworks.

You learned algorithms and data structures.

You learned databases.

You learned APIs.

You learned design patterns.

You became better at debugging.

And eventually, you became someone who could take a problem and translate it into software.

Those skills are still important.

But the development process is changing.

Today, many tools can help with the mechanical part of programming.

You can describe a function and receive an implementation.

You can describe a UI and generate a starting point.

You can paste an error message and receive possible explanations.

You can ask for a database schema.

You can ask for unit tests.

You can ask for refactoring suggestions.

You can even ask an AI coding assistant to help you understand code written by someone else.

This changes the value of programming knowledge.

It doesn't make programming knowledge useless.

It makes understanding more important.

Because when code becomes easier to produce, the difficult question becomes:

How do I know whether this is the right code?

That requires judgment.

And judgment is much harder to automate.


The Problem With Building Faster

Imagine two teams.

Team A can build a feature in two days.

Team B takes two weeks.

At first glance, Team A looks much better.

But now imagine what happens after six months.

Team A has 80 features.

Team B has 25.

However:

Team A's users are confused.

The codebase has become difficult to maintain.

Nobody is sure which features are actually being used.

The database contains years of unnecessary complexity.

Several services depend on each other in fragile ways.

Every new change introduces unexpected bugs.

Developers spend more time fixing old decisions than building new things.

Team B, meanwhile, has fewer features.

But the features solve important problems.

The architecture is understandable.

The team knows why most major decisions were made.

The product is easier to maintain.

Users understand how to use it.

Which team built the better software?

The answer isn't necessarily Team A.

This is why velocity alone is a dangerous measurement.

We often measure:

  • How many tickets were completed?
  • How many features were shipped?
  • How quickly did the sprint finish?
  • How many lines of code were produced?
  • How quickly can we deploy?

But perhaps we should also ask:

  • Did the feature solve the actual problem?
  • Did users need it?
  • Did it reduce complexity?
  • Did it create future maintenance costs?
  • Did we make the system easier or harder to understand?
  • Did we improve reliability?
  • Did we make the next change easier?

Software development is not a race to produce the maximum amount of code.

Code is a cost.

Every line potentially has to be reviewed, tested, documented, maintained, monitored, secured, upgraded, and eventually removed.

Sometimes the best code is the code you decide not to write.


Every Feature Has a Future Cost

When someone requests a new feature, the conversation often sounds simple.

"Can we add this?"

"Sure."

"How long will it take?"

"Maybe two days."

And then the feature gets added.

But the real cost is rarely two days.

A feature can introduce:

  • New database tables
  • New API endpoints
  • New UI components
  • New permissions
  • New business rules
  • New error cases
  • New tests
  • New monitoring requirements
  • New documentation
  • New security considerations
  • New customer support questions
  • New deployment risks
  • New dependencies
  • New edge cases

The implementation might take two days.

The responsibility might last five years.

That distinction is easy to forget.

Software doesn't disappear after deployment.

It becomes part of the system.

And once users depend on it, removing it becomes difficult.

This is why experienced developers often appear slower at the beginning of a project.

They ask questions.

"What happens if this value is missing?"

"What happens when the user does this twice?"

"Who is allowed to perform this action?"

"What happens when the external service is unavailable?"

"What happens if the database contains old records?"

"How will we migrate this later?"

"What happens when traffic increases?"

"What happens if someone abuses this endpoint?"

"Do we actually need this feature?"

Those questions don't produce code.

But they prevent bad code from becoming a permanent part of the system.


AI Makes This Even More Important

AI-assisted development creates an interesting paradox.

The easier it becomes to generate code, the more important it becomes to understand software engineering.

Why?

Because generated code can be convincing.

It can compile.

It can pass a few tests.

It can look clean.

It can even look professional.

And still be wrong.

This is not necessarily because the tool is bad.

It's because software problems are rarely just syntax problems.

Consider a simple request:

"Create an API endpoint that lets users update their email address."

That sounds straightforward.

An AI tool can generate an endpoint very quickly.

But a production developer needs to think about much more.

Should the user be authenticated?

Can an administrator change another user's email?

Should the old email receive a confirmation message?

Should the new email require verification?

What happens if the email already belongs to another account?

Should sessions be invalidated?

What happens if the request is repeated?

Should the operation be logged?

Can attackers use it for account takeover?

Should rate limiting be applied?

What happens if the email provider rejects the message?

Should the database update happen before or after verification?

What happens if the verification token expires?

The code may be easy.

The system behavior is not.

This is why AI doesn't remove the need for developers.

It raises the importance of developers who understand the system around the code.


The Most Dangerous Developer Is Not the Beginner

A beginner can make mistakes.

That's expected.

The more dangerous situation is when someone becomes confident enough to stop questioning their assumptions.

A developer who doesn't know something often searches for an answer.

A developer who thinks they already know the answer may skip the investigation.

That is where problems begin.

Software engineering contains countless assumptions:

"We will never have more than this many users."

"This service will always be available."

"This field will always contain a value."

"Nobody will call this endpoint twice."

"The client will always send valid data."

"This database table will never become large."

"Nobody will need this feature later."

"Only internal users will access this API."

"Nobody will modify this configuration manually."

"The third-party API won't change."

These assumptions may be reasonable.

But reasonable assumptions still need to be recognized.

A mature engineering culture doesn't try to eliminate uncertainty.

It tries to make uncertainty visible.


Before You Build, Understand the Problem

One of the most valuable habits a developer can develop is learning to pause before implementation.

Not every problem needs immediate code.

Sometimes the first step should be investigation.

Let's say a product manager says:

"Customers are complaining that the application is slow."

A developer might immediately start optimizing queries.

But what does "slow" mean?

Is login slow?

Is the dashboard slow?

Is the API slow?

Is the page rendering slowly?

Is the problem only happening on mobile networks?

Is the database responsible?

Is an external API responsible?

Is the application actually slow, or does the user interface simply feel slow?

Are only certain customers affected?

Did the problem begin after a recent deployment?

Without answering these questions, optimization becomes guesswork.

A better process might be:

Observe → Measure → Identify → Hypothesize → Change → Measure again.

Not:

Guess → Change everything → Hope.

This distinction matters.

Good engineering is not about making random improvements.

It is about reducing uncertainty through evidence.


Metrics Should Help You Think, Not Replace Thinking

Modern software teams have access to more data than ever.

We can measure:

  • Response times
  • Error rates
  • CPU usage
  • Memory usage
  • Database performance
  • Conversion rates
  • Feature usage
  • User retention
  • Deployment frequency
  • Incident frequency
  • Test coverage
  • Build duration

These measurements are valuable.

But metrics can also create strange behavior.

If developers are rewarded for closing tickets, they may optimize for closing tickets.

If teams are rewarded for deployment frequency, they may optimize for deployment frequency.

If managers focus heavily on lines of code, people may produce more code.

If a company focuses only on reducing infrastructure costs, engineers may make decisions that increase developer complexity.

The measurement can slowly become the objective.

And that can be dangerous.

A metric is a signal.

It is not reality.

A dashboard cannot tell you everything about whether a product is good.

Sometimes the most important information is a conversation with a user.

Sometimes it is a developer saying:

"This solution works, but I don't think we should build it this way."

That sentence can be more valuable than another hundred lines of code.


Technical Debt Is Not Always Bad

"Technical debt" has become almost a negative phrase.

But debt itself isn't automatically bad.

Borrowing money can be useful when it helps you achieve something valuable.

The same can be true for technical decisions.

Suppose you're building a new product.

You don't know whether users will actually adopt it.

Creating a highly sophisticated architecture may take six months.

A simpler architecture might let you test the idea in three weeks.

Choosing the simpler approach isn't necessarily poor engineering.

It may be a conscious trade-off.

The problem begins when temporary shortcuts become permanent without anyone recognizing them.

There is a difference between:

"We're intentionally using a simple solution because we're validating the product."

and:

"We know this is problematic, but we'll pretend it doesn't exist."

The first is strategy.

The second is neglect.

Good engineers don't eliminate all technical debt.

They understand it.

They document important trade-offs.

They know where the risks are.

And they revisit those decisions when the system changes.


Sometimes Deleting Code Is a Bigger Achievement Than Writing It

Developers often celebrate adding functionality.

But removing unnecessary functionality can be just as valuable.

Imagine a system with:

50,000 lines of code.

Several old dependencies.

Unused API endpoints.

Legacy configuration.

Duplicate services.

Old database tables.

Features nobody uses.

Over time, the system becomes difficult to understand.

A new developer joins the team and asks:

"Why does this exist?"

Nobody knows.

That's a dangerous sentence in software engineering.

Because if nobody knows why something exists, developers become afraid to remove it.

So it remains.

Then another feature depends on it.

Then another.

Eventually, something that was supposed to be temporary becomes infrastructure.

This is how complexity grows.

Removing unused code can:

  • Reduce maintenance
  • Reduce security risk
  • Reduce cognitive load
  • Simplify testing
  • Improve onboarding
  • Reduce build time
  • Reduce operational complexity

But deletion rarely feels as productive as creation.

There is no exciting demo.

Nobody says:

"Look at this amazing feature we removed."

Yet sometimes removing 5,000 lines of unnecessary code is one of the best improvements a team can make.


The Best Architecture Is the One Your Team Can Understand

Architecture discussions can become extremely complicated.

Microservices.

Event-driven systems.

Distributed systems.

Service meshes.

Message brokers.

Container orchestration.

Serverless.

CQRS.

Event sourcing.

Caching layers.

Multiple databases.

These technologies can be useful.

But using sophisticated architecture doesn't automatically create sophisticated software.

Sometimes a small application needs a simple architecture.

A single application with a well-designed database may be better than ten services communicating through multiple queues.

Not because monoliths are always better.

Not because microservices are always better.

But because architecture should follow the problem.

Technology should serve the product.

Not the other way around.

If a system requires ten diagrams just to explain how a simple request moves through the application, that's worth questioning.

Complexity should have a reason.

If you cannot explain why a particular architectural decision exists, you should probably investigate it.


Documentation Is a Form of Engineering

There is another thing developers sometimes underestimate:

Writing down what you know.

A codebase can tell you what the system does.

It often cannot explain why.

Why was this database chosen?

Why does this service retry three times?

Why can't this field be changed after account creation?

Why does this API return a specific error?

Why was a seemingly strange workaround introduced?

Why isn't a particular component allowed to communicate directly with another?

Without documentation, future developers have to rediscover the reasoning.

That costs time.

Good documentation doesn't need to be enormous.

Sometimes a short explanation is enough.

For example:

"We intentionally don't retry this operation because it is not idempotent and could create duplicate records."

That sentence could save someone hours of debugging in the future.

Documentation isn't paperwork.

Good documentation is memory for a team.


Reliability Is a Feature

Users don't care how elegant your architecture is if the application doesn't work when they need it.

Reliability is often invisible when everything goes well.

A user clicks a button.

The action works.

They continue.

Nothing remarkable happens.

But behind that simple action may be:

  • Authentication
  • Network communication
  • Load balancing
  • Application servers
  • Database queries
  • Caching
  • External APIs
  • Logging
  • Monitoring
  • Retry policies
  • Error handling
  • Infrastructure

The user doesn't see any of this.

They simply expect:

"I clicked it, so it should work."

That's the real product experience.

Reliability isn't an optional engineering quality.

For many products, it is the product.


Security Should Not Be a Final Step

Another dangerous mindset is treating security as something to check after development.

"Let's build it first and secure it later."

The problem is that architecture influences security.

Authentication decisions affect API design.

Authorization affects data access.

Data storage affects privacy.

Logging affects sensitive information.

Third-party integrations create new trust boundaries.

Caching can accidentally expose information.

File uploads can create attack surfaces.

Security isn't a sticker you put on software after it's finished.

It needs to be part of the design.

This doesn't mean every developer must become a cybersecurity specialist.

It means every developer should develop the habit of asking:

"What could go wrong if someone intentionally misuses this?"

That single question can prevent many problems.


The Human Side of Software Engineering

There is another part of software development that technology cannot solve easily.

People.

Software is built by teams.

And teams are affected by communication.

A technically brilliant developer can still damage a project by refusing to communicate.

A less experienced developer can become extremely valuable by asking good questions.

A strong code review isn't about proving someone wrong.

It's about improving the software.

A useful review comment sounds like:

"Could we consider handling this case explicitly? I think this could happen when the external service returns an empty response."

A poor review comment sounds like:

"This is wrong."

The difference isn't technical knowledge.

It's collaboration.

Software engineering is not just about creating systems.

It is also about creating systems together.


The Best Developers Keep Learning

Technology changes constantly.

Languages evolve.

Frameworks disappear.

New tools appear.

AI changes workflows.

Cloud platforms introduce new services.

Architectural patterns become popular and then fall out of fashion.

If your entire identity as a developer is connected to one technology, change can feel threatening.

But the deeper skills remain useful.

How to reason about problems.

How to understand systems.

How to communicate.

How to debug.

How to investigate.

How to simplify.

How to design.

How to evaluate trade-offs.

How to learn.

These skills survive technological changes.

A developer who understands only a tool can become outdated when the tool changes.

A developer who understands how to learn tools becomes much harder to replace.


What Should Developers Focus on Now?

I don't think the answer is simply:

"Learn AI."

AI is important.

But learning another tool is not enough.

Developers should strengthen the skills that become more valuable as code generation becomes easier.

1. Learn to ask better questions

The quality of the solution often depends on the quality of the question.

Instead of:

"How do I implement this?"

Try:

"What problem are we actually solving?"

Instead of:

"How do I optimize this?"

Try:

"What evidence tells us this is the bottleneck?"

Instead of:

"Which framework should we use?"

Try:

"What constraints should determine our technology choice?"


2. Understand systems, not just files

Know how the pieces interact.

Understand:

  • APIs
  • Databases
  • Networks
  • Authentication
  • Caching
  • Queues
  • Deployment
  • Monitoring
  • Failure modes

You don't need to be an expert in everything.

But understanding the boundaries between components makes you much stronger.


3. Become comfortable with uncertainty

Real projects rarely provide perfect requirements.

You will have incomplete information.

Some assumptions will be wrong.

Some users will behave unexpectedly.

Some technical decisions will need to change.

That's normal.

Good engineers don't wait for perfect certainty.

They make uncertainty visible and reduce it step by step.


4. Learn to simplify

Complexity is easy to create.

Simplicity requires discipline.

Ask:

"Can this be done with fewer moving parts?"

"Can this abstraction be removed?"

"Do we really need another service?"

"Do we need this dependency?"

"Can the database handle this without another layer?"

"Will the next developer understand this?"

Simple doesn't mean primitive.

Simple means easy to reason about relative to the problem.


5. Build things that solve real problems

A technically impressive application nobody needs is still a poor product.

Before building, ask:

Who has this problem?

How frequently does it happen?

How are they solving it today?

What does the current solution cost them?

Would they actually use a better solution?

Sometimes these questions eliminate months of unnecessary development.

That's not failure.

That's good engineering judgment.


A New Definition of Developer Productivity

Maybe we need to rethink what productivity means.

A productive developer isn't necessarily the person who writes the most code.

It might be the person who:

  • Prevents a bad architectural decision
  • Finds a production bug before release
  • Simplifies a complicated module
  • Automates repetitive work
  • Helps another developer understand the system
  • Removes unnecessary code
  • Improves reliability
  • Writes useful documentation
  • Identifies an important edge case
  • Challenges an unnecessary requirement
  • Makes a difficult system easier to maintain

Sometimes productivity looks like code.

Sometimes it looks like deleting code.

Sometimes it looks like a conversation.

Sometimes it looks like spending an afternoon investigating a problem before writing a single line.

And sometimes the most productive sentence a developer can say is:

"I don't think we should build this yet."


We Don't Have a Code Shortage

We have more ways to produce code than ever.

The challenge is becoming something different.

We need better decisions.

Better problem selection.

Better architecture.

Better communication.

Better testing.

Better security thinking.

Better understanding of users.

Better maintenance practices.

Better judgment.

AI can help us produce software faster.

Automation can help us deploy faster.

Cloud platforms can help us scale faster.

Modern frameworks can help us build faster.

But none of these tools can automatically answer the most important question:

"Is this the right thing to build?"

That question still belongs to us.

And perhaps that is good news.

Because the future of software engineering doesn't have to be about humans competing with machines to type code faster.

It can be about humans using machines to remove repetitive work while spending more time on the things that require judgment, creativity, empathy, and responsibility.


The Future Developer

I don't think the future developer will simply be someone who knows how to code.

The future developer will be someone who can move comfortably between several levels of thinking.

At one moment, they may be writing a function.

A few minutes later, they may be debugging an infrastructure problem.

Then they may be discussing product requirements.

Then reviewing a database design.

Then questioning whether the feature should exist.

Then using AI to accelerate implementation.

Then reviewing the generated result carefully.

The ability to move between these levels will become increasingly valuable.

The developer who understands only implementation may struggle.

The developer who understands implementation and context will have an advantage.

Because software isn't valuable because code exists.

Software is valuable because it helps people accomplish something.


So, Are We Getting Better at Software?

I think we are.

But not automatically.

Better tools don't automatically create better engineering.

Faster development doesn't automatically create better products.

AI doesn't automatically create better decisions.

More features don't automatically create more value.

Better infrastructure doesn't automatically create better user experiences.

We become better when we use these tools with stronger judgment.

We become better when we measure what matters.

We become better when we are willing to question assumptions.

We become better when we remove complexity instead of constantly adding it.

We become better when we treat reliability and security as part of the product.

We become better when we listen to users.

We become better when developers communicate instead of working in isolated technical bubbles.

And perhaps most importantly, we become better when we stop treating "Can we build it?" as the most important question.

The better question is:

"Should we build it, and if we do, what is the simplest responsible way to build it?"

That question can change the way we approach software.

It can change how we work with AI.

It can change how teams measure productivity.

It can change how products are designed.

And it can change what it means to be a good developer.


Final Thought

We are entering a period where producing software is becoming dramatically easier.

That is exciting.

But it also creates a responsibility.

When creating something becomes cheap, deciding what deserves to exist becomes more important.

When code can be generated quickly, understanding that code becomes more important.

When features can be shipped faster, deciding which features matter becomes more important.

When technology gives us more possibilities, knowing which possibilities to ignore becomes a valuable skill.

So maybe the next generation of great developers won't be remembered because they wrote millions of lines of code.

Maybe they'll be remembered because they built fewer things — but built the right things.

They'll use powerful tools.

They'll use AI.

They'll automate repetitive work.

They'll move quickly when speed matters.

But they'll also know when to slow down.

They'll ask questions before writing code.

They'll measure before optimizing.

They'll simplify before adding another layer.

They'll delete when deletion is better than expansion.

And they'll remember that software engineering has never really been about writing code.

It has always been about solving problems.

The code is just one of the tools we use to do it.


What do you think?

As developers, are we becoming better engineers — or are we simply becoming faster at producing software?

And with AI making code generation easier, which developer skills do you think will become more valuable over the next five years?

I'd love to hear different perspectives in the comments.

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

Everyone says DeepSeek is cheaper, but I got tired of guessing the exact math. So I built a calculat

abarth23 - Apr 27

Systems Thinking: Thriving in the Third Golden Age of Software

Tom Smithverified - Apr 15

The Trust Gap: Why Your Product Fails Even When the Math is Right

Karol Modelski - Jul 16

The Sovereign Vault — A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

Your Backup Data Knows More Than You Think. HYCU aiR Is Finally Asking It the Right Questions.

Tom Smithverified - May 14
chevron_left
5k Points205 Badges
83Posts
148Comments
49Connections
I enjoy building web applications and exploring new technologies. Most of my time goes into improvin... Show more

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!