A curated collection of timeless software development principles from industry legends.
- Martin Fowler's Refactoring Principles
- Sajaniemi's 11 Variable Roles
- Robert Martin's Clean Code Principles
- Kent Beck's TDD (Test-Driven Development)
- Olaf Zimmermann's Microservice API Design Patterns
"Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior."
- Adding Function: Add new capabilities to the system
- Refactoring: Restructure the code without adding function
- Never wear both hats at the same time
Common refactorings to apply:
- Extract Method: Turn a code fragment into its own method
- Rename Variable: Make names reveal intention
- Move Method: Move method to more appropriate class
- Replace Temp with Query: Replace temporary variables with method calls
- Introduce Parameter Object: Group parameters that belong together
Signs that refactoring is needed:
- Long Method: Methods should be short and do one thing
- Large Class: Classes trying to do too much
- Long Parameter List: Too many parameters indicate poor abstraction
- Duplicated Code: Same code structure in multiple places
- Feature Envy: Method more interested in other class than its own
- Rule of Three: Refactor when you see duplication the third time
- Preparatory Refactoring: Make change easy, then make easy change
- Comprehension Refactoring: Refactor to understand code
- Litter-Pickup Refactoring: Always leave code cleaner than you found it
Professor Jorma Sajaniemi identified 11 stereotypical roles that variables play in programs:
- Value doesn't change after initialization
- Example:
const MAX_SIZE = 100
- Goes through succession of values in systematic way
- Example:
for (let i = 0; i < n; i++)
- Boolean variable holding state
- Example:
let isValid = true
- Traverses data structure
- Example:
let current = head; while (current) { current = current.next }
- Holds latest value encountered
- Example:
let lastError = null
- Holds best or most appropriate value found so far
- Example:
let maxValue = -Infinity
- Accumulates values
- Example:
let sum = 0; for (x of array) sum += x
- Data structure holding multiple values
- Example:
const items = []
- Keeps previous value of another variable
- Example:
let prev = curr; curr = next
- Rearranges or transforms data
- Example:
const sorted = array.sort()
- Holds value briefly for calculation
- Example:
const temp = a; a = b; b = temp
- A class should have only one reason to change
- Each module should do one thing well
- Open for extension, closed for modification
- Add new features by adding new code, not changing existing code
- Derived classes must be substitutable for their base classes
- Subtypes must fulfill the contract of their parent type
- Many client-specific interfaces better than one general interface
- Don't force clients to depend on methods they don't use
- Depend on abstractions, not concretions
- High-level modules shouldn't depend on low-level modules
- Use intention-revealing names
- Avoid disinformation
- Make meaningful distinctions
- Use pronounceable names
- Use searchable names
- Small (20 lines or less)
- Do one thing
- One level of abstraction per function
- Descriptive names
- Few arguments (ideally zero)
- Comments don't make up for bad code
- Explain yourself in code
- Good comments: legal, informative, explanation of intent
- Bad comments: redundant, misleading, mandated
- Vertical openness between concepts
- Vertical density for related concepts
- Horizontal alignment rarely helpful
- Team rules over personal preferences
// 1. Write test first
describe('Calculator', () => {
it('should add two numbers correctly', () => {
const result = add(2, 3);
expect(result).toBe(5);
});
});
// ❌ Test fails: add function doesn't exist
// 2. Write minimal code to pass
function add(a: number, b: number): number {
return a + b;
}
// ✅ Test passes
// 3. Refactor while keeping tests green
export const add = (a: number, b: number): number => {
validateNumbers(a, b);
return a + b;
};
const validateNumbers = (a: number, b: number): void => {
if (!Number.isFinite(a) || !Number.isFinite(b)) {
throw new Error('Invalid number input');
}
};
- Write one test at a time
- Each test validates one feature
- Verify failure before success
- Always write the test before the implementation
- Let tests drive the design
- Tests document the intended behavior
- Only refactor when all tests pass
- Never add features during refactoring
- Take small steps, run tests after each
- Improved design through test-first thinking
- Built-in regression test suite
- Documentation through examples
- Confidence in code changes
- Backend for Frontend (BFF): Dedicated backend for each frontend
- API Gateway: Single entry point for all clients
- Client-Side Composition: Frontend assembles data from multiple services
- Request-Response: Synchronous communication
- Request-Acknowledge: Async with acknowledgment
- Query-Response: Read-only operations
- Request-Callback: Async with callback
- Document Message: Self-contained message
- Command Message: Invoke specific action
- Event Message: Notify about state change
- Request-Reply: Correlated messages
- Pagination: Return results in chunks
- Wish List: Client specifies desired fields
- Conditional Request: Use ETags for caching
- Request Bundle: Batch multiple requests
- API Key: Simple authentication
- OAuth 2.0: Delegated authorization
- Rate Limiting: Prevent abuse
- Circuit Breaker: Fail fast pattern
- Version Identifier: Explicit version in API
- Semantic Versioning: Major.Minor.Patch
- Two in Production: Support current and previous
- Aggressive Deprecation: Clear sunset dates
- Tolerant Reader: Ignore unknown fields
- Consumer-Driven Contracts: Test from client perspective
- Published Language: Shared domain model
- Context Mapper: Manage bounded contexts
- Design First: API before implementation
- Contract Testing: Verify API contracts
- Documentation: OpenAPI/Swagger specs
- Monitoring: Track API usage and performance
- Governance: Consistent API standards
- Fowler, M. (2018). Refactoring: Improving the Design of Existing Code (2nd ed.)
- Sajaniemi, J. (2002). An Empirical Analysis of Roles of Variables in Novice-Level Procedural Programs
- Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship
- Beck, K. (2002). Test Driven Development: By Example
- Zimmermann, O. et al. (2022). Patterns for API Design: Simplifying Integration with Loosely Coupled Message Exchanges
This document serves as a quick reference for fundamental software development principles. Each principle deserves deeper study through the original sources.