This is a series. Previous posts: #1, #2, #3, #4, #5, #6; this is the seventh.

The Psychology of Bad Code is a framework, developed by Tanya Janca, that maps common secure coding failures to the specific cognitive biases that drive them, paired with structural fixes for each.

Repetitive code reuse and lazy error handling happen because developers rely on satisficing (choosing “good enough” over optimal), optimism bias (assuming past success predicts future behavior), and anchoring bias (sticking with the first solution that worked). The fix isn’t just training, it’s secure defaults, environmental tooling, friction at the right moments, and a culture that talks about failure as openly as it celebrates success.

The behaviour: Repetitive Code and Lazy Error Handling.

This post is a mash up of two intertwined issues. The first is repeating old code patterns we know are not great, but the second is not doing a good job of handling exceptional conditions (error handling) in a strict and proper way. Looking back upon my notes and my talk slides, I am honestly not sure why I choose to combine these two (I didn’t document it, see what I did there? From the previous post!). I suspect that because both were caused by the same biases and heuristics and the solutions where also the same that it would be more efficient to just write one post.

What does lazy error handling actually look like in code?

  • Copying and reusing the same code patterns over and over because they worked before
  • Handling errors with something simple like returning null, printing a message, or ignoring it entirely (try, catch…. nothing.)
  • Catching exceptions without actually doing anything meaningful with them
  • Assuming the same inputs, conditions, or environment will always apply (especially if it hasn’t had real testing, you don’t know otherwise)
  • Writing “good enough” logic to get things working, but not thinking through failure/abuse cases

We don’t necessarily know we’re doing this. It feels safe. It feels like enough.

This often shows up when we’re moving fast, solving a problem we’ve seen before, or trying to unblock ourselves.

Behavioural biases at play

  • Satisficing: We choose something that is “good enough” instead of the best or safest solution
  • Optimism bias: We assume things will continue to work the way they have in the past
  • Anchoring Bias: We stick to the first solution that worked and keep reusing it, even when the context changes.

These are all incredibly common.

In most cases, reusing what worked before is efficient. And most of the time, systems do behave the way we expect them to.

Until they don’t.

Why this behaviour makes sense in the moment

We’ve seen this pattern before. It worked. It was fine.

We’re trying to move quickly, not reinvent everything from scratch.

Error handling often feels like:

  • Extra work
  • Edge cases that “probably won’t happen”
  • Something we can come back to later

Also:

  • The system might not fail during testing
  • The happy path works perfectly
  • There’s pressure to ship, not to explore every possible failure

So we move on.

This is not careless. It’s efficient, in the moment.

The security risk

When we reuse patterns without thinking, we also reuse their weaknesses.

  • Missing or weak error handling can leak sensitive information
  • Systems can fail in unpredictable ways when assumptions break
  • Attackers often look for exactly these edge cases and failure paths
  • Silent failures can hide security issues entirely

And with duplicated code:

  • Fixes don’t get applied everywhere
  • Vulnerabilities get repeated across the system
  • Inconsistent behavior creates new attack surface

This is how small, “good enough” decisions compound into larger problems.

Solutions:

Training

You can’t train someone out of time pressure. You also can’t expect developers to think through every edge case manually, remember every past lesson, or revisit every reused pattern on their own.

When the system rewards speed and reuse, that’s what people will optimize for.

Secure Defaults

Provide safe, reusable patterns. If developers are going to reuse code (and they will), give them approved error handling patterns, standardized ways to deal with failures, and shared utilities instead of copy-paste duplication. Make the safe thing easy to reuse.

Environmental Design

Use tools to surface issues automatically: IDE warnings for unhandled exceptions, CI/CD checks for missing error handling, and detection of duplicated logic where possible. Catch problems where they happen, not months later.

Friction

Add just enough friction to make people think. Require meaningful error handling before merge, flag empty catch blocks or ignored errors, and encourage developers to answer “what happens when this fails?” Not every edge case, just the important ones.

Social / Cultural

Normalize thinking about failure. Talk about how systems break, what happens when assumptions fail, and why error handling matters. Right now, most teams celebrate success paths. We should also talk about failure paths.

Conclusion

Reusing code and writing “good enough” logic isn’t the problem. Doing it without thinking about failure, context, or long-term impact is.

Most of the time, things work. Security problems show up when they don’t.

Tanya Janca (SheHacksPurple) is the author of Alice and Bob Learn Secure Coding and Alice and Bob Learn Application Security, and owner of She Hacks Purple Consulting. The Psychology of Bad Code is her framework for mapping secure coding failures to the cognitive biases behind them. This is Part 7 of the series, based on a talk delivered internationally throughout 2026.

Leave a Reply

Discover more from SheHacksPurple

Subscribe now to keep reading and get access to the full archive.

Continue reading