C3 Software

What 25 Years of Building Business Software Has Taught Us

Technologies have changed repeatedly over more than 25 years of software work. The difficult parts have remained remarkably stable: understanding real work, protecting data, controlling change and supporting what happens after launch.

Timeline of software eras from 2001 to 2026

The durable lessons are not about frameworks

It is tempting to tell a long software career as a sequence of technologies. Languages, deployment models and devices do matter, but they are rarely the reason a business system succeeds. The durable questions are more human: Who owns this decision? What happens when the data is incomplete? Can somebody explain the result? How will this change without stopping the organisation?

Experience does not make estimates magically accurate or eliminate defects. Its value is pattern recognition. You learn which apparently small omissions become expensive, which abstractions are premature and which operational details deserve attention before the first user arrives. The following lessons come from building and supporting business software, including client systems and C3 Software's own products. They are principles, not invented war stories.

Requirements describe a proposed solution, not necessarily the problem

“We need a dashboard” often means somebody cannot answer a question. “We need an approval button” may mean responsibility is unclear. Implementing the noun faithfully can preserve the underlying dysfunction in a cleaner interface.

Ask what decision the requested feature enables, what happens today and what evidence would prove improvement. Then examine exceptions. The standard operating procedure tells you the intended process; experienced staff reveal the real process, including corrections, phone calls and spreadsheets used when the official route fails.

A requirement is therefore a hypothesis until users complete real work with it. This is why early prototypes and thin releases are not merely delivery techniques. They are tools for discovering whether the team has understood the business.

Data outlives both interfaces and development teams

User interfaces are replaced. Data is migrated, interpreted and relied upon for years. A shortcut in a data model can survive several generations of application code. Ambiguous status fields, reused identifiers and missing provenance eventually become business constraints.

Store the facts needed to explain important decisions, not just the latest display state. Distinguish an event from the current projection of it. Record which rule or document version applied when history must be defensible. Make ownership, retention and deletion explicit before “keep everything” becomes the accidental policy.

Migration teaches the same lesson from the other direction: moving rows is easy; preserving meaning is hard. Counts can reconcile while semantics do not. Successful migrations profile sources, define transformations, preserve traceability and let business owners validate representative records as well as totals.

Experienced UK software team reviewing a modern product
Long-lived software rewards teams that optimise for comprehension and change, not only the first release.

Simple architecture is a capability, not a lack of ambition

New technology makes sophisticated designs look inexpensive. Production makes their coordination cost visible. Every independently deployed component adds versioning, identity, telemetry, failure handling and on-call questions. Distribution should buy something concrete-independent scaling, isolation or team autonomy-not merely modernity.

A well-structured application with clear modules is often the most responsible beginning. It keeps transactions and debugging understandable while preserving boundaries that can be separated later. The test of architecture is not how impressive its diagram appears; it is whether a new developer can predict the effect of a change and an operator can find a failure.

Abstractions also have carrying costs. The best time to generalise is after repeated variation exposes the stable seam. Designing for every imagined future usually makes today's verified requirement harder to see.

Integration failures are business states

An external API call is not complete merely because it returned 200. The supplier may accept work asynchronously, apply different validation later or time out after committing the change. Blind retries then create duplicates; no retry loses work.

Define idempotency, correlation and reconciliation at the business level. Decide what users see while the outcome is unknown and who resolves records that remain inconsistent. Timeouts, authentication expiry and supplier schema changes belong in the workflow design, not in a generic catch block.

Teams often monitor whether an endpoint is reachable while missing whether invoices, bookings or registrations are actually progressing. Technical health and business health are related but not identical.

Security is mainly about boundaries and lifecycle

Encryption is necessary, but many serious weaknesses come from incorrect authorisation, excessive retention, shared accounts or forgotten administrative routes. Authentication establishes an identity; it does not prove that identity may access this tenant's record for this purpose.

Design access from resources and actions, then enforce tenant scope at every trusted boundary. Minimise collected data, restrict operational access, audit meaningful actions and test restoration as well as backup creation. Deletion needs equal care: removing a primary row while leaving exports, search indexes and logs may satisfy a user interface without satisfying the policy.

Security controls must survive operational pressure. If support cannot diagnose an issue safely, someone will eventually request direct database access or copy sensitive data into an unsafe channel.

Users judge reliability by uncertainty, not uptime percentages

A service can meet its availability target and still feel unreliable if users cannot tell whether Save worked. Ambiguous outcomes cause repeated clicks, duplicate work and calls to support. Clear state, safe retry behaviour and honest progress indicators often improve trust more than a tiny infrastructure improvement.

Performance is similarly experiential. Median latency hides the slow requests that users remember. Measure tail latency for complete journeys and include dependency time. A fast screen followed by an unpredictable background process is not a fast service.

Graceful failure means preserving a recoverable business state and telling the user what happens next. It does not mean replacing an actionable error with “Something went wrong”.

Support is where the real architecture becomes visible

Design documents describe intended boundaries. Incidents reveal actual dependencies, ownership gaps and hidden coupling. Supporting software changes how you build it: correlation identifiers become essential, audit trails become user-service tools, and vague logs stop looking harmless.

Observability should answer business questions: Which stage is stuck? Which tenant is affected? Was the supplier called? Is retry safe? Logs alone are not observability, and more logs do not compensate for missing context. Metrics show shape, traces show paths and structured events preserve useful evidence.

A system that requires the original author to diagnose ordinary failures has not transferred knowledge. Runbooks, automated deployment and readable decisions reduce key-person risk as much as documentation of classes and methods.

Delivery speed comes from small changes and short feedback

Speed is not the number of developers typing simultaneously. Large batches create long-lived branches, difficult reviews, painful testing and releases containing too many possible causes. Small vertical changes expose misunderstandings sooner and are easier to reverse.

Automation helps when it makes the routine path repeatable: build, tests, security checks, deployment and rollback. It cannot rescue an approval process in which nobody can decide whether the behaviour is right. Organisational latency frequently dominates compute time.

Technical debt is not simply untidy code. It is a choice that makes future change more expensive. Some debt is rational when deliberate, bounded and recorded. Unnamed debt compounds because estimates assume a flexibility the system no longer has.

Ownership continues after launch

Products need patching, dependency updates, cost review, access review, support and decisions about obsolete features. “Complete” software gradually becomes insecure and less compatible with the world around it. Maintenance is not evidence that engineering failed; it is part of operating a useful system.

Cloud services make resources easy to create, not free to understand. Cost follows architecture, traffic shape, retention and failure. Attribute spend to workloads and tenants where possible, establish budgets and investigate step changes. A service can scale technically while its unit economics deteriorate.

Owning our own products reinforces this lesson. Product work removes the comforting boundary between delivery and consequences: onboarding friction, false positives, operational tooling and support all belong to the same engineering problem.

Judgement is knowing which constraint matters now

There is no universally correct stack, process or architecture. A small internal workflow and a public multi-tenant platform should not carry identical machinery. The senior decision is often to reject complexity whose benefits do not match the present risk while leaving a credible path to change.

More than 25 years has not produced a secret formula. It has produced a dependable set of questions: What outcome matters? Which assumption could invalidate the plan? Who owns the data and decision? How does failure become visible? Can we reverse the release? Who supports it tomorrow?

Technology will keep changing. Software that answers those questions clearly has a much better chance of remaining useful when today's fashionable framework has become tomorrow's migration source.

Longevity also teaches humility: decisions that were sensible under old constraints can become liabilities without having been mistakes when made. Review them against current evidence rather than rewriting history.

Related reading