❖ SOLID, Clean Architecture & Unix philosophy
As software systems grow more complex, it's easy for code to become tangled, with classes that do too much, logic that's hard to reuse, and changes in one place breaking unrelated parts of the app. To combat this, developers lean on design principles, guidelines that help us write clean, modular, and maintainable code.
One of the most influential sets of principles in modern software engineering is known as 'SOLID'.
SOLID outlines five key rules for object-oriented design. But even beyond OOP, these principles help teams write better code , whether you're building a mobile app, scripting in shell, or designing a backend service.
Understanding SOLID means understanding how to break software into well-defined, independent parts — so each part is easier to change, extend, and reason about.
This article contains 3 separate parts:
- Does Unix follow SOLID principles?
- When does a system become large enough to require separation of responsibilities?
- When does it start making sense to introduce Clean Architecture into a project?
Does Unix follow SOLID principles?
Not literally, the SOLID principles were coined for object-oriented programming by Robert C. Martin (Uncle Bob). Unix, on the other hand, is rooted in process-based design, with utilities built primarily in C and shell scripts, not classes and objects.
But philosophically, Unix aligns with many of the same ideas, especially the most important one: Single Responsibility Principle (SRP).
What is SOLID? Why it Matters
SOLID is an acronym for five design principles that aim to make software easier to understand, maintain, and extend.
| Principle | Description |
|---|---|
| Single Responsibility | A class should have only one reason to change |
| Open/Closed | Software should be open for extension, but closed for modification |
| Liskov Substitution | Subtypes must be substitutable for their base types |
| Interface Segregation | Prefer many small, specific interfaces over large, general ones |
| Dependency Inversion | Depend on abstractions, not concrete implementations |
These principles are key in OOP and Clean Architecture — but they also echo Unix's composable, minimalist philosophy.
S - Single Responsibility Principle
In OOP:
A class should have one, and only one, reason to change.
It means every class (or function, or module) should do one thing well. If it has multiple responsibilities, it will be harder to maintain and more prone to bugs.
In Unix:
Each command-line utility is designed to do one job and do it well.
Examples:
grep- filters lines that match a patterncut- extracts specific columns or fieldssort- sorts lines in a file or stream
These tools are intentionally small and composable. You combine them using pipes (|) to build complex behaviors.
Example:
cat access.log | grep 404 | cut -d' ' -f1 | sort | uniq -c | sort -nr
Each tool:
- Does one thing
- Can be updated independently
- Has a clear reason to change
That’s SRP — just not in class-based code, but in process-based design.
Other SOLID principles in Unix
| Principle | Unix Alignment |
|---|---|
| Single Responsibility | ✅ Strong: each tool has a narrow purpose |
| Open/Closed | 🟡 Somewhat: tools are extended through scripting, not internal modification |
| Liskov Substitution | ❌ Not really applicable — inheritance isn’t used |
| Interface Segregation | ✅ Strong: tools use minimal, focused inputs/outputs (text streams, args) |
| Dependency Inversion | 🟡 Weak: tools call each other via scripts but lack abstraction mechanisms |
Unix doesn't enforce these principles through classes — but its *culture* encourages similar separation of concerns.
SRP in Android App Architecture
SRP isn’t just for classes. In an Android app (especially with Clean Architecture), SRP applies to:
Layers
Presentation layer: handles UI state onlyDomain layer: handles business logicData layer: handles data access
Components
ViewModel: handles user interactions and stateUseCase: handles one specific business operation (e.g.,GetUserPosts)Repository: abstracts data source access (e.g., DB, network)
If a component does too many things — UI + business logic + data access — it becomes hard to test, understand, or change.
SRP Examples
| Component | Responsibility |
|---|---|
LoginViewModel | Manage login UI state and events |
LoginUseCase | Handle login logic and validation |
UserRepo | Fetch and cache user data (from DB or API) |
Analogy: SRP in Unix vs Android
| Aspect | Unix Utility | Android Component |
|---|---|---|
| One Concern | cut extracts fields | LoginUseCase handles login only |
| Composability | Combine via pipes (|) | Combine via layers (domain/UI) |
| Reason to Change | grep only changes if filtering changes | UserRepo changes only if data layer changes |
Visual Diagram: Unix Shell Pipeline vs Android Architecture
Here’s a conceptual side-by-side comparison:
| Unix Shell Pipeline | Android Architecture Flow |
|---|---|
cat file.txt | Repository provides raw data |
grep error | UseCase filters meaningful business data |
cut -d' ' -f1 | ViewModel extracts/display-relevant state |
sort | uniq -c | UI Layer formats and displays info |
Both systems:
- Break problems into small, testable steps
- Compose functionality through clearly defined boundaries
- Emphasize separation of concerns
Interpreting Open/Closed and Dependency Inversion in Unix
Open/Closed Principle (OCP) in Unix
"Software entities should be open for extension but closed for modification."
How Unix achieves this:
- You
'extend' commands by combining them in scripts - You
'wrap' commands with functions or aliases (without changing internal code) - Environment variables (e.g.,
$PAGER,$EDITOR) let programs integrate external tools
Example:
mygrep() {
grep --color=auto "$@" | less
}
The grep command wasn’t modified — it was extended.
Dependency Inversion Principle (DIP) in Unix
"High-level modules should not depend on low-level modules. Both should depend on abstractions."
Unix tools don't have formal abstractions (like interfaces or abstract classes), but:
- They rely on text streams — a form of implicit interface
- You can swap tools if they follow common conventions (e.g., replacing
lesswithmore) - You can script plug-in architectures with exec calls or sourcing shell functions dynamically
Example:
if [ -x ./mybackend ]; then ./mybackend "$@" else default_backend "$@" fi
This allows swappable implementations — an inversion of control.
Summary
- Unix strongly embodies SRP: each tool does one thing.
- Other SOLID principles (especially Interface Segregation and Open/Closed) show up in Unix behavior, if not structure.
- Whether in pipes or layers, the key is small, composable, testable units that reduce coupling and increase maintainability.
When does a system become large enough to require separation of responsibilities?
This is one of the hardest and most important questions in applying the Single Responsibility Principle (SRP) well. There’s no universal line, it’s a judgment call, but there are practical guidelines and heuristics to help you decide when a piece of code needs to be split due to too many responsibilities.
Don’t split prematurely, SRP is about clarity and safety, not bureaucracy.
Understanding Responsibility
A responsibility is a reason to change, not just a “thing it does.”
**Robert C. Martin (Uncle Bob):**“A responsibility is a reason to change.”
So the key test is: If a change request comes in, which part of this component will it affect?
🧭 Guiding Principles to Decide When to Split
1. ❗ You’re Changing for More Than One Reason
Signal: If two or more distinct types of changes would require touching the same file/class/module.
Examples:
- You need to change a UI layout and also fix a bug in database syncing — and both require touching the same class? Split it.
2. 🧪 It's Hard to Unit Test
If you're struggling to write a small, focused unit test, the class probably does too much.
Heuristic:
- If you must mock too many unrelated dependencies to test a method, that method may not have a single responsibility.
3. It Uses Unrelated Domains
Signal: The class uses concepts from more than one domain.
Android example:
- snippet.kotlin
class UserProfileManager { fun drawUI() { ... } fun fetchFromServer() { ... } fun saveToDatabase() { ... } }
This class:
- knows how to draw,
- talks to network,
- and stores to local DB.
These are 3 separate domains → split it.
4. 🔁 You Keep Coming Back to It for Unrelated Fixes
When one class keeps being involved in a wide variety of issues or changes — it's a red flag.
5. Cognitive Load (Lines of Code is a symptom, not a cause)
There’s no magic LOC number, but if:
- You can't explain what a class does in one sentence,
- Or a function needs multiple scrolls to read through,
…it likely violates SRP.
Scaling Thresholds for Separation
| Project Size | SRP Enforcement Strategy |
|---|---|
| Small scripts / proof-of-concepts | Don't over-engineer. A bit of mess is fine. |
| Single-developer apps | Start separating when logic becomes harder to reuse or test. |
| Growing teams or production apps | Enforce SRP early. Clean separation avoids tech debt. |
| Large codebases | SRP is mandatory — otherwise, changes become dangerous. |
Summary
Split when:
- You have more than one reason to change,
- Code spans multiple unrelated concerns,
- Testing becomes awkward,
- Changes become risky or confusing.
When does it start making sense to introduce Clean Architecture into a project?
That’s a deep and important question, and the answer depends on balancing project scope, risk, and maintenance horizon.
Minimum Scale Where Clean Architecture Becomes Worthwhile
My minimum: For a solo developer or small team, I’d adopt Clean Architecture principles (modular layers, separation of concerns, testability) as soon as:
- The app has multiple distinct business rules, user flows, or data sources.
- It’s expected to evolve or be maintained for months or more.
- There’s a need for testing logic independently of the UI or Android framework.
That threshold can be surprisingly low — for example:
- An Android podcast player with downloads, metadata fetching, playback, favorites
- A task manager with offline sync and reminders
- A health tracking app with charts and history
If your app:
- Has more than one core feature,
- Or touches more than one domain (UI, DB, network),
- Or is not trivial to unit test as-is…
…then going with a modular layered approach like Clean Architecture pays off early.
When Clean Architecture Is Overkill
Clean Architecture is not needed (and can actively slow you down) if:
- You’re building a throwaway prototype or MVP.
- The app has one screen, one user flow, or just UI + local data.
- You’re solo, and need to ship fast, with low future change risk.
Example:
A flashlight app, a simple calculator, a QR scanner, you can write these with just an Activity and a couple helper classes. No need for UseCases, Repositories, DataSources, etc.
Heuristics to Decide
| If you have… | Consider |
|---|---|
| >1 data source (e.g., DB + API) | ✅ Use layers (repository, datasource) |
| Business logic that must be tested | ✅ Use usecases or interactors |
| Multiple devs / long-term codebase | ✅ Clean separation helps scale teamwork |
| Tight deadline + short-lived MVP | ❌ Keep it flat, maybe refactor later |
| Simple CRUD with no complex logic | ❌ Stick to MVVM or even plain Activity + ViewModel |
| External SDKs or unstable APIs | ✅ Use abstractions to decouple |
Evolution Strategy (Start Simple → Grow into Clean)
You don’t need to go full Clean from day one.
Here’s a path:
- Start flat with clear separation: UI → ViewModel → Repo → Network/DB.
- As logic grows, extract
UseCasesto isolate operations. - If the app keeps growing, split into
domain,data, andpresentationmodules. - Add interfaces and DI (like Hilt/Koin) to fully decouple and test.
This is sometimes called “clean-lite” or “onion-by-need”.
Summary
You should adopt Clean Architecture if:
- The app has nontrivial logic, multiple domains, or business rules.
- You plan to maintain/test/grow it seriously.
- You want a clear separation of concerns.
You shouldn’t go full Clean if:
- It’s just UI + a bit of state.
- You're prototyping.
- Overhead will slow you more than help.
- snippet.cpp
by: ▖ ▘▖▖ ▌ ▌▌▛▘▌▙▌ ▙▖▙▌▙▖▌ ▌ published: June 16 2025.

