UPGRADE PARTY
v28.0 changeset — origin log

First Upgradian Experiences — Programmer (Upgraded)

The same 17 lessons, still in the First Upgradian's own words — with a note under each one connecting it to how the same problem gets solved today.

Version 2 · Upgraded with Claude

Prefer it unannotated? See the verbatim version.

Balance Optimistic or Pessimistic Code

Fully optimistic code means that the programmers have assumed that everything will always work. They assume that the database is always available and that every database interaction will return a result when desired. In the real world, there are times when the database is down and other data problems happen. So the optimistic code fails and the users see ugly error messages or worse. Fully pessimistic code has tons of extra lines which test every database interaction — slowing down every page. The best code is balanced to be ready for the most-common causes of failure.

Modern Context

This is essentially what site-reliability engineers now call graceful degradation — patterns like retries with exponential backoff, circuit breakers (fail fast instead of hanging), and fallback content when a dependency is down. The 2000s version of this lesson and the 2020s version are the same idea; only the vocabulary and the off-the-shelf libraries have changed.

Have a Common Library

Successful teams have a common library with short function names for the functions they use all day.

Modern Context

Today this usually lives as an internal shared package published to a private registry, or as a shared workspace inside a monorepo. The underlying principle hasn't moved an inch: the more often a function gets typed, the more its name should reward familiarity over cleverness.

No Duplicate Code

Every keystroke saved is money in the bank!

Modern Context

Worth pairing with a counterpoint that's gained traction since: Sandi Metz's "rule of three" — duplication is fine, even preferable, until the third repetition, because a premature abstraction built on only two examples is often harder to fix than the duplication it was meant to prevent. DRY (Don't Repeat Yourself) is still the right instinct; the timing of when to apply it is the nuance modern practice has added.

Have a Code-Standards Document

Define the naming conventions and programming standards for the team.

Modern Context

A huge share of this is now automated rather than documented: linters and formatters (ESLint, Prettier, Black, gofmt) enforce most style rules on save or on commit, so the "document" is increasingly a config file the whole team shares, not a PDF nobody rereads.

Prepare For High Traffic

The code needs to be optimized as it is created in case the site or business is successful. Count on success. Preparations include the ability to have all images on an alternate server, caching database hits, and load balancing with multiple servers.

Modern Context

Cloud infrastructure has automated much of this: CDNs handle the "images on an alternate server" instinct by default now, and autoscaling groups or Kubernetes handle load balancing across servers that spin up and down with demand. The judgment call has shifted from "did we build for scale" to "did we set the autoscaling limits correctly" — same underlying discipline, different failure mode.

Create Administrative Panels First

Many projects have the site administrative panels created toward the end of the project. Usually, a massive amount of time would have been saved if the admin screens were created in the beginning.

Modern Context

Frameworks that auto-generate an admin panel from the data model (Django's admin being the classic example) exist largely because this lesson kept getting learned the hard way, over and over, across the industry. Internal-tool builders like Retool are the modern version of the same fix.

Standard Issue: Do You Use In-Line SQL or Stored Procedures?

If the project has unlimited funding and time (some do) then use Stored Procedures since they are faster and provide higher security. If you have deadline or funding problems, use in-line sql and only create Stored Procedures when there is a performance benefit.

Modern Context

This exact debate has largely been superseded by a third option: the ORM (SQLAlchemy, Prisma, ActiveRecord) with parameterized queries, which sidesteps the original security concern (SQL injection) regardless of which side you'd have picked, while keeping most of in-line SQL's speed of development. Stored procedures still earn their keep for genuinely performance-critical, high-volume operations — the underlying trade-off the lesson describes hasn't disappeared, it's just moved.

Create an Audit Trail — Install History and Log Tables at Beginning of Project

There almost always is trouble. If the tables involve money, then at least with triggers and log tables, you have a way to point to the source of the trouble and a way to reverse the transactions.

Modern Context

This has grown into its own discipline: event sourcing (storing every state change as an immutable event rather than just the current row) and structured audit logging are now standard expectations for any system touching money, largely driven by compliance frameworks like SOC 2 that require exactly this kind of traceability.

Use a Debug Table to Track Behavior

It is impossible to know when things fail when there are thousands of users unless you track the start and end of their transactions.

Modern Context

The direct descendant of the "debug table" is distributed tracing (OpenTelemetry being the current open standard) paired with an APM tool like Datadog or New Relic — same instinct, but now able to follow a single request across dozens of services instead of just one application's table.

Personalize

Store user preferences and experiences so when they return to your application, they feel as though they have not left. Save the user time and increase their comfort level!

Modern Context

Still excellent advice, with one addition the original era didn't have to reckon with as heavily: privacy regulation (GDPR, CCPA) now requires being deliberate about what's stored, for how long, and with what consent — personalization and data minimization have to be balanced together now, not personalization alone.

Prepare for Design Changes

They are coming. Make it easy to add buttons or pages to an application.

Modern Context

Component-based UI frameworks (React, Vue) and design systems were built almost entirely to answer this lesson — reusable components mean a "new button" is a configuration choice, not a new piece of code. Feature flags have also become the standard way to ship design changes safely, letting a team turn a new page on for 5% of users before committing to 100%.

Do Not Rely on Automated Processes and Supplied Tools

The supplied tools are often quite elegant. But if they fail in the middle of the process, you have to start them over at the beginning and you have no idea when they failed. Specifically, for data migration or transfer tasks, it is sometimes better to have an actual piece of code to do the work!

Modern Context

The modern term for the fix this lesson is reaching for is idempotency — writing migration and transfer code so that re-running it after a partial failure is always safe, plus checkpointing so a restart resumes where it left off instead of from zero. The specific advice (don't blindly trust the vendor tool) is still sound, but the deeper fix is making your own code resumable in the first place.

Be Willing to Break Rules

Some of the best applications are ones that do not follow all the so-called Best Practices. But be sure you know what the rules are before you break them and of course let management know about the issues!

Modern Context

This lines up closely with the "Boy Scout Rule" from The Pragmatic Programmer, and with modern tech-debt tracking practices: breaking a convention deliberately, and logging why, is very different from breaking it by accident. The "let management know" instinct has become formalized as documented tech debt in a backlog, rather than left as tribal knowledge.

Have a Backup Plan

Expect the worst to happen, because it will.

Modern Context

Taken to its logical extreme, this is the whole premise of chaos engineering — Netflix's "Chaos Monkey" tool deliberately kills production servers at random specifically to force teams to build systems that already assume the worst will happen, rather than hope it won't.

Three Kinds of Code

There is code that benefits the customer, code that benefits the programmer, and code that benefits no one. Our goal is to have no code that benefits no one, and a balance of the code for the programmer and the customer. For example, if it is going to take the programmer hours or days to write code that will save the customer a few seconds, then the customer gets to wait a second. And if the customer will have his experience greatly improved by the programmer working a few more minutes, then the programmer should do it :)

Modern Context

This is a plain-language version of a real, named trade-off product teams still wrestle with today: developer experience (DX) versus user experience (UX), and the judgment call of which one a given hour of engineering time should serve. Naming the third category — code that benefits no one — is arguably the sharpest part of the whole list; most retrospectives on failed projects can point to a lot of code that falls exactly there.

Agreement to Argue Until Clarity Is Reached

Often, people will not offer their input because a supervisor is autocratic or difficult. Projects fail because the team members were reluctant to voice their opinions. The best designs happen as a result of people brainstorming or discussing (sometimes resembling arguing) until the best course of action is found. It is important to be able to discuss things until the best course of action is found.

Modern Context

Google's own internal research project on team performance (Project Aristotle, published 2015) found that psychological safety — whether team members feel safe voicing disagreement without fear of embarrassment or punishment — was the single strongest predictor of team effectiveness they measured, ahead of who was actually on the team. This lesson was naming that finding a decade before the research existed to back it up. Blameless postmortems are the modern process built specifically to protect this norm after something goes wrong.

Watch Worker Body Language

When you walk into a programmer area and see people on the edge of their seats, typing like crazy, it means that these workers are very productive, time is passing quickly for them, and their "struggle factor" is low. When they are sitting back in their chairs staring at the screen, the opposite is true. It is probably normal to be in the "struggle mode" while designing something or learning something for the first time. On some projects, people spend massive amounts of time struggling. The Team Leader or Project Manager must ensure that the team is making the best use of their time.

Modern Context

This is describing flow state, the concept popularized by psychologist Mihaly Csikszentmihalyi — full absorption in a task where time perception shifts and productivity climbs. Modern engineering-management practice tries to protect it deliberately: limiting work-in-progress, batching meetings instead of scattering them through the day, and treating context-switching itself as a measurable productivity cost, not just a vague annoyance.

I hope you found some of these tidbits interesting. They were gathered from some of the best managers I have worked with. Perhaps I should make another list of the tragic scenarios I have encountered :)

Related