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

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
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

  1. Install React DevTools browser extension
  2. Use Components tab to inspect props/state
  3. Use Profiler to find performance issues

Network Debugging

  1. Open DevTools → Network tab
  2. Filter by XHR/Fetch
  3. Check request/response payloads
  4. 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                    │
└─────────────────────────────────────────────────────────────┘