Development Tips & Best Practices
Development Tips & Best Practices
Code principles, architecture patterns, and productivity tips for the EPIC application.
Table of Contents
- The 3 Laws of Readable Code
- EPIC Architecture Patterns
- Component Design Principles
- Naming Conventions
- VS Code Productivity
- Git Best Practices
- Debugging Tips
- Performance Tips
The 3 Laws of Readable Code
1. Code Should Explain Itself
The reader should understand the code without needing comments.
This means:
| Do | Don't |
|---|---|
| Clear, descriptive variable names | Single-letter variables (except loops) |
| Functions that do one thing | Functions with side effects |
| Logic flows predictably | Hidden behavior |
| Explicit over clever | Tricks that confuse future you |
Example:
// Bad
const d = users.filter(u => u.a && !u.d).map(u => u.n);
// Good
const activeUserNames = users
.filter(user => user.isActive && !user.isDeleted)
.map(user => user.name);
If someone has to ask "what does this do?", it's not readable.
2. Code Should Be Easy to Change
Readable code is modifiable without causing chain-reaction bugs.
This means:
| Principle | Description |
|---|---|
| Low Coupling | Components depend on as little as possible |
| High Cohesion | Functions/classes handle one responsibility |
| No Magic Numbers | Use named constants |
| DRY | Don't Repeat Yourself |
| Good Structure | Organized folders and files |
Example:
// Bad - magic numbers, tight coupling
if (items.length > 50) {
paginate(items, 10);
}
// Good - named constants, configurable
const MAX_ITEMS_BEFORE_PAGINATION = 50;
const ITEMS_PER_PAGE = 10;
if (items.length > MAX_ITEMS_BEFORE_PAGINATION) {
paginate(items, ITEMS_PER_PAGE);
}
If it's easy to break the code when editing it, it's not readable.
3. Reduce Cognitive Load
Code should minimize how much the brain must keep in memory to understand it.
This means:
| Do | Don't |
|---|---|
| Keep functions short (< 20 lines ideal) | 100+ line functions |
| Keep conditionals shallow | Deep nesting (> 3 levels) |
| Break into meaningful chunks | Monolithic blocks |
| Consistent formatting | Mixed styles |
| Linear flow | Jumping all over the file |
Example:
// Bad - deep nesting, hard to follow
function processOrder(order) {
if (order) {
if (order.items) {
if (order.items.length > 0) {
if (order.customer) {
if (order.customer.isActive) {
// finally do something
}
}
}
}
}
}
// Good - early returns, flat structure
function processOrder(order) {
if (!order?.items?.length) return;
if (!order.customer?.isActive) return;
// do something
}
Readable code lets the reader understand it in one mental pass.
EPIC Architecture Patterns
Data Flow
The Golden Rule:
Component → Hook → Utils (if needed) → Service → API
API → Service → Utils (if needed) → Hook → Component re-renders
Layer Responsibilities
| Layer | Location | Responsibility | Example |
|---|---|---|---|
| Component | miniappmodules/ |
UI rendering, user interaction | CodexModule.tsx |
| Hook | hooks/ |
State management, side effects | useCodexEntries.ts |
| Utils | utils/ |
Pure logic, no API calls | formatDate.ts |
| Service | services/ |
Business logic with API calls | codex.service.ts |
| API | api/ |
HTTP client, request/response | codex.api.ts |
Component Rules:
- Parent components own the state
- Child components are "dumb" - they receive props and emit events
- State flows down, events flow up
Frontend Folder Structure
frontend/
├── api/ # HTTP client layer
│ └── codex.api.ts # axios/fetch calls to backend
│
├── services/ # Business logic layer
│ └── codex/
│ └── codex.service.ts # transforms data, calls API
│
├── hooks/ # State management layer
│ └── CodexModuleHooks/
│ └── useCodexEntries.ts # React state + effects
│
├── utils/ # Pure utility functions
│ └── formatters.ts # no side effects, no API
│
├── miniappmodules/ # Feature modules (the "apps")
│ └── CodexModule/
│ └── CodexModule.tsx # uses hooks, renders components
│
├── components/ # Reusable UI components
│ └── ui/
│ ├── Button/
│ ├── Modal/
│ ├── Card/
│ └── ...
│
├── contexts/ # React Context providers
│ └── NovelContext.tsx
│
├── pages/ # Next.js pages (routing)
│ ├── index.tsx
│ └── dashboard.tsx
│
├── styles/ # Global styles and themes
│ └── globals.css
│
└── public/ # Static assets
└── assets/
Component Design Principles
1. Single Responsibility
// Bad - does too much
function UserDashboard() {
// fetches data
// formats data
// handles forms
// manages modals
// renders everything
}
// Good - separated concerns
function UserDashboard() {
const { user, isLoading } = useUser();
if (isLoading) return <Spinner />;
return <UserProfile user={user} />;
}
2. Composition Over Inheritance
// Build complex UIs from simple pieces
<Card>
<CardHeader>
<CardTitle>Settings</CardTitle>
</CardHeader>
<CardContent>
<SettingsForm />
</CardContent>
</Card>
3. Props Down, Events Up
// Parent owns state
function Parent() {
const [value, setValue] = useState('');
return <Child value={value} onChange={setValue} />;
}
// Child is controlled
function Child({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />;
}
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Components | PascalCase | UserProfile, CodexEntry |
| Hooks | camelCase with use prefix |
useCodexEntries, useTheme |
| Services | camelCase with .service suffix |
codex.service.ts |
| API files | camelCase with .api suffix |
codex.api.ts |
| Utils | camelCase | formatDate, parseJSON |
| Constants | SCREAMING_SNAKE_CASE | MAX_ITEMS, API_BASE_URL |
| Types/Interfaces | PascalCase | CodexEntry, UserSettings |
| CSS classes | kebab-case | user-profile, card-header |
| Folders | camelCase or PascalCase (match content) | CodexModule/, utils/ |
VS Code Productivity
Essential Shortcuts
| Shortcut | Action |
|---|---|
Ctrl+P |
Quick open file |
Ctrl+Shift+P |
Command palette |
Ctrl+Shift+F |
Search across files |
Ctrl+. |
Quick fix / code actions |
F2 |
Rename symbol |
F12 |
Go to definition |
Shift+F12 |
Find all references |
Ctrl+Shift+K |
Delete line |
Alt+Up/Down |
Move line |
Shift+Alt+Up/Down |
Duplicate line |
Multi-Cursor Editing
| Shortcut | Action |
|---|---|
Ctrl+D |
Select next occurrence |
Ctrl+Shift+L |
Select ALL occurrences |
Alt+Click |
Add cursor |
Ctrl+Alt+Up/Down |
Add cursor above/below |
Navigation
| Shortcut | Action |
|---|---|
Ctrl+G |
Go to line |
Ctrl+Shift+O |
Go to symbol in file |
Ctrl+T |
Go to symbol in workspace |
Alt+Left/Right |
Navigate back/forward |
Ctrl+Tab |
Switch between open files |
Git Best Practices
Commit Messages
# Format
<type>: <short description>
# Types
feat: New feature
fix: Bug fix
refactor: Code change (no new feature or fix)
style: Formatting, semicolons, etc.
docs: Documentation only
test: Adding tests
chore: Maintenance tasks
Examples:
feat: add dark mode toggle to settings
fix: resolve memory leak in text editor
refactor: extract validation logic to utils
docs: update API documentation
Branch Naming
feature/add-dark-mode
bugfix/fix-memory-leak
refactor/extract-validation
Before Committing
# Check status
git status
# Review changes
git diff
# Stage specific files (not everything)
git add src/components/Button.tsx
# Commit with message
git commit -m "feat: add hover state to button"
Debugging Tips
Console Methods
// Group related logs
console.group('User Data');
console.log('Name:', user.name);
console.log('Email:', user.email);
console.groupEnd();
// Table format for arrays/objects
console.table(users);
// Timing
console.time('fetch');
await fetchData();
console.timeEnd('fetch'); // fetch: 234ms
// Stack trace
console.trace('How did we get here?');
React DevTools
- Install React DevTools browser extension
- Use Components tab to inspect props/state
- Use Profiler to find performance issues
Network Debugging
- Open DevTools → Network tab
- Filter by XHR/Fetch
- Check request/response payloads
- Look for failed requests (red)
Performance Tips
React Optimization
// Memoize expensive calculations
const sortedItems = useMemo(() =>
items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// Memoize callbacks
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
// Memoize components
const MemoizedComponent = React.memo(ExpensiveComponent);
Avoid Re-renders
// Bad - creates new object every render
<Component style={{ color: 'red' }} />
// Good - stable reference
const style = useMemo(() => ({ color: 'red' }), []);
<Component style={style} />
Lazy Loading
// Lazy load heavy components
const HeavyChart = React.lazy(() => import('./HeavyChart'));
// Use with Suspense
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
Bundle Size
# Analyze bundle
npm run build
npx source-map-explorer 'build/static/js/*.js'
Quick Reference Card
┌─────────────────────────────────────────────────────────────┐
│ EPIC DATA FLOW │
├─────────────────────────────────────────────────────────────┤
│ Component → Hook → Service → API → Backend │
│ Component ← Hook ← Service ← API ← Backend │
├─────────────────────────────────────────────────────────────┤
│ LAYER RULES │
├─────────────────────────────────────────────────────────────┤
│ Component: UI only, uses hooks │
│ Hook: State + effects, calls services │
│ Service: Business logic, calls API │
│ API: HTTP calls only │
│ Utils: Pure functions, no side effects │
├─────────────────────────────────────────────────────────────┤
│ NAMING │
├─────────────────────────────────────────────────────────────┤
│ Component: PascalCase UserProfile.tsx │
│ Hook: useXxx useUserData.ts │
│ Service: xxx.service user.service.ts │
│ API: xxx.api user.api.ts │
│ Constant: SCREAMING MAX_RETRIES │
└─────────────────────────────────────────────────────────────┘