Disposable vs Permanent Code
Here's a controversial opinion that'll piss off some people: most of your code doesn't deserve unit tests. If you treat UI components and service implementations the same way you treat business logic, you're wasting time and slowing down your team.
The traditional approach of applying uniform standards across all code is no longer the best strategy. This is because there is now a clear asymmetry in the realm of AI-assisted development between disposable and permanent code.
Defining the Principle
The principle is straightforward: If you're writing permanent code, focus on quality and do it well. If you're writing disposable code, focus on quantity and leverage AI tooling.
Permanent Code refers to our application's intellectual property:
- Domain business logic and rules
- Core algorithms that provide our competitive edge
- Integration contracts and interfaces
- Security and compliance measures
- Data models for key business entities
Disposable Code refers to the shifting periphery:
- User interface components and layouts
- Adapters for various third-party services
- Implementations specific to frameworks
- Scripts for configuration and deployment
- Service implementations and adapters
Note: I'm not saying "Disposable code should be shit and permanent code should be beautiful."
Code should always be high-quality.
I'm saying we need to consider time and attention allocation as engineers. Permanent code deserves comprehensive testing, good design, and needs a human in the loop before it reaches production. On the other hand, we can generate disposable code easily, and often replace it without ceremony.
For example, consider an AI chatbot. The main conversation logic involves understanding user intent, keeping context, and applying business rules. This is permanent code that requires thorough testing and careful design. But the UI components, WebSocket adapters, and code that calls the AI models? These can change as we experiment with different providers and frameworks.
Architectural Implications
This principle suggests specific architectural decisions:
Module Separation: Create clear boundaries between stable and volatile code. In practice, this usually matches clean architecture patterns. Our domain layer should be permanent, but our infrastructure and presentation layers can be disposable.
Interface-Driven Design: Build robust interfaces for disposable implementations. Our messaging interface can stay stable for years. Using that interface, we can try different providers and protocols to our heart's content.
Testing Strategy: Apply comprehensive testing to permanent code and minimal testing to disposable code. This doesn't mean disposable code is untested. Instead, ensure tests are at the interface level rather than the implementation level.
Implementation Guidance
Start by auditing our current codebase:
Permanent Code Indicators:
- Implements core business rules or domain logic
- Based on regulatory requirements or industry standards
- Embodies competitive advantages or proprietary algorithms
- Changes infrequently and for well-understood reasons
Disposable Code Indicators:
- Handles presentation or user interface concerns
- Adapts to external service APIs or protocols
- Implements framework-specific patterns
- Changes frequently due to experimentation or external factors
For permanent code, invest in comprehensive testing, clear documentation, and careful review processes. For disposable code, focus on rapid iteration and interface compliance.
The Trade-offs
This approach requires accepting some trade-offs:
Uneven Quality: Our system will have areas with extensive testing and others with minimal coverage. This can feel uncomfortable if we are used to uniform standards.
Upfront Investment: Properly designing permanent code takes longer upfront. We're taking our time to get certain decisions right, which requires confidence in our domain understanding.
Look, this framework isn't universal, and I'm not being dogmatic here. In heavily regulated environments, we might need comprehensive testing everywhere. But for most applications using AI assisted development? Lets be more intentional, and put our time and attention to what matters, not towards checking boxes.
I'm curious about how other builders are handling this balance in their AI applications. Are you seeing similar patterns in your development process? What criteria do you use to determine where to invest quality efforts versus where to embrace rapid iteration?
If you're working on AI applications and facing similar architectural decisions, I'd love to hear about your experiences. Contact me at avi.santoso@gmail.com and let's have a chat.