Rethinking Software Architecture: When Best Practices Hold Us Back
What if many of our cherished software "best practices" aren't actually best for the software—but just coping mechanisms for human cognitive limitations? A recent debugging session revealed this uncomfortable truth.
The Build That Changed Everything
During a licensing system implementation, we hit a wall. TypeScript was happy, the architecture looked "clean," but the build system refused to cooperate. After multiple attempts at "proper" solutions, the fix was surprisingly simple: put everything in one file.
This simple change forced a fundamental question: If the "messy" solution works better than the "clean" one, maybe our definition of clean is wrong.
The Incident That Started It All
The "Clean" Architecture
/types/license.ts (20 lines - type definitions) /hooks/useLicense.ts (100 lines - business logic) /components/licensing/ (200+ lines - UI components) /utils/helpers.ts (40 lines - utility functions) /constants/license.ts (10 lines - constants) Total: 6 files, 370+ lines, complex import graph
This architecture followed every best practice we've been taught:
- Separation of concerns
- Single responsibility principle
- Clean folder organization
- Logical file boundaries
Yet it wouldn't build. Vite's module system couldn't resolve the exports, despite TypeScript's approval.
The Solution That Worked
/hooks/useLicense.ts (370 lines - everything co-located)
// Types, enums, business logic, utilities, constants
interface LicenseInfo { ... }
enum Feature { ... }
const LICENSE_CONSTANTS = { ... }
export function useLicense() { ... }
export function validateLicense() { ... }
Total: 1 file, 370 lines, zero import complexityThe Uncomfortable Truth
This incident revealed a fundamental truth: many software architecture decisions exist to accommodate human cognitive limitations, not technical requirements.
Why Humans Separate Files
Human Limitation
Working Memory Limit
~7±2 items in working memory
Coping Strategy
Small, Focused Files
Break code into digestible chunks
Human Limitation
Categorical Thinking
Think in rigid categories
Coping Strategy
Folder Organization
/types, /hooks, /components
Human Limitation
Visual Scanning
Can only look at one screen
Coping Strategy
Small Files
Files that fit on one screen
What AI Doesn't Need
AI assistants like Claude don't share these human limitations:
- Unlimited Working Memory: Can hold entire codebase in context
- Relationship Thinking: Sees connections, not just categories
- Instant Search: No need to "know where things are"
- Parallel Analysis: Can analyze multiple files simultaneously
The Real Cost of "Clean" Architecture
Our human-optimized patterns create real technical debt:
Hidden Costs of Separation
- • Build Complexity: More files = more module resolution = more failure points
- • Bundle Size: Unnecessary boundaries prevent optimal tree-shaking
- • Runtime Performance: Extra module loading overhead
- • Development Velocity: Time spent managing imports and finding files
- • Tooling Conflicts: TypeScript says it's fine, Vite disagrees
What Really Matters vs. What We Think Matters
What We Obsess Over
- • "Separation of Concerns"
- • "Single Responsibility Principle"
- • "Clean Architecture"
- • "Proper File Organization"
- • "Following Best Practices"
What Actually Matters
- • Does it compile?
- • Does it run correctly?
- • Can it be modified safely?
- • Is it performant?
- • Does it solve the problem?
The Pattern Emerges
Once you see it, the pattern is everywhere. Many software "best practices" are actually human coping mechanisms:
Small Files
Exist because humans can't hold large contexts in working memory
Category Folders
Exist because humans think categorically, not relationally
Import/Export Ceremonies
Exist because humans need explicit boundaries to navigate code
Separation Patterns
Exist because humans get confused by multiple concerns in one place
AI-Optimized Architecture
When AI leads development, we can optimize for what actually matters:
Optimize For
- • Build Tool Happiness: Structure code how build tools prefer
- • Runtime Efficiency: Minimize module boundaries
- • Change Safety: Co-locate related changes
- • Feature Completeness: Everything about a feature together
Stop Optimizing For
- • Human Scanning: Files that fit on screen
- • Category Comfort: Artificial type/logic separation
- • Mental Models: Boundaries that help humans navigate
- • Best Practice Compliance: Following rules for their own sake
Example: AI-Optimized Structure
// license-system.ts
// 500 lines but completely self-contained
// Types, business logic, UI components, all co-located
// Zero import complexity, builds instantly, runs fast
interface LicenseInfo {...}
enum Feature {...}
const CONSTANTS = {...}
export function useLicense() {...}
export function LicenseGate() {...}
export function LicenseDialog() {...}
This approach eliminates import complexity, builds instantly, runs efficiently, and can be modified safely—everything that actually matters for software quality.
When to Break the Rules
This doesn't mean abandon all structure. It means being intentional about why we structure code the way we do:
Questions to Ask
- • Is this separation helping the software, or just making humans comfortable?
- • Does this boundary solve a technical problem, or a cognitive one?
- • Are we optimizing for build success or developer ego?
- • Would the computer care if we combined these files?
Keep Separations That Matter
- Security boundaries: Different privilege levels
- Performance boundaries: Code splitting for loading
- Team boundaries: Different ownership areas
- Technology boundaries: Client vs server code
Question Separations Based On
- File size limits: "This file is getting too big"
- Category thinking: "Types go in /types"
- Visual comfort: "I can't see it all at once"
- Convention following: "That's how we always do it"
The Meta-Lesson
This discovery reveals something profound about software development: we've spent decades creating complex architectures to work around human limitations that AI doesn't share.
The Core Insight
Human Tech Debt is real and pervasive. Many "best practices" exist solely to compensate for human cognitive limitations, not technical requirements. With AI partners that don't share these limitations, we can build simpler, more robust systems.
The "messy" single-file solution wasn't messy at all—it was the cleanest solution from the perspective of the system that actually matters: the one that compiles and runs.
Practical Implications
For Human Developers
- Question whether architectural decisions serve the software or just human comfort
- Consider co-location when it simplifies the build process
- Optimize for compilation success, not organizational aesthetics
- Remember that readable doesn't always mean separated
For AI-Human Teams
- Let AI suggest structures that optimize for technical requirements
- Don't impose human organizational patterns on AI-generated code
- Focus on what works, not what looks familiar
- Be willing to challenge decades of conventional wisdom
For the Future
As AI becomes more integrated into development workflows, we need new patterns that optimize for human-AI collaboration rather than just human-human collaboration. This means questioning fundamental assumptions about how code should be organized and being open to approaches that prioritize function over form.
The Uncomfortable Question
If you discovered that many of your core beliefs about software architecture were actually just workarounds for human limitations, would you be willing to change them?
The single-file solution that "worked" challenged everything we thought we knew about clean code. But it compiled instantly, ran efficiently, and could be modified safely. Maybe it's time to redefine what "clean" really means.
Ready to Challenge Your Architecture Assumptions?
Learn how UpNorthDigital.ai can help you discover more effective development patterns that optimize for what actually matters in software systems.
Explore Modern Architecture Patterns