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.
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.
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.
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:
That’s SRP — just not in class-based code, but in process-based design.
| 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 isn’t just for classes. In an Android app (especially with Clean Architecture), SRP applies to:
Presentation layer: handles UI state onlyDomain layer: handles business logicData layer: handles data accessViewModel: 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.
| 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) |
| 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 |
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:
"Software entities should be open for extension but closed for modification."
How Unix achieves this:
'extend' commands by combining them in scripts'wrap' commands with functions or aliases (without changing internal code)$PAGER, $EDITOR) let programs integrate external toolsExample:
mygrep() {
grep --color=auto "$@" | less
}
The grep command wasn’t modified — it was extended.
"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:
less with more)Example:
if [ -x ./mybackend ]; then ./mybackend "$@" else default_backend "$@" fi
This allows swappable implementations — an inversion of control.
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.
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?
Signal: If two or more distinct types of changes would require touching the same file/class/module.
Examples:
If you're struggling to write a small, focused unit test, the class probably does too much.
Heuristic:
Signal: The class uses concepts from more than one domain.
Android example:
class UserProfileManager { fun drawUI() { ... } fun fetchFromServer() { ... } fun saveToDatabase() { ... } }
This class:
These are 3 separate domains → split it.
When one class keeps being involved in a wide variety of issues or changes — it's a red flag.
There’s no magic LOC number, but if:
…it likely violates SRP.
| 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. |
Split when:
That’s a deep and important question, and the answer depends on balancing project scope, risk, and maintenance horizon.
My minimum: For a solo developer or small team, I’d adopt Clean Architecture principles (modular layers, separation of concerns, testability) as soon as:
That threshold can be surprisingly low — for example:
If your app:
…then going with a modular layered approach like Clean Architecture pays off early.
Clean Architecture is not needed (and can actively slow you down) if:
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.
| 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 |
You don’t need to go full Clean from day one.
Here’s a path:
UseCases to isolate operations.domain, data, and presentation modules.This is sometimes called “clean-lite” or “onion-by-need”.
by: ▖ ▘▖▖ ▌ ▌▌▛▘▌▙▌ ▙▖▙▌▙▖▌ ▌ published: June 16 2025.