coding-style.md 5.2 KB

Coding Style

General coding standards and best practices. These rules apply to all code in the GROWI monorepo.

Immutability (CRITICAL)

ALWAYS create new objects, NEVER mutate:

// ❌ WRONG: Mutation
function updateUser(user, name) {
  user.name = name  // MUTATION!
  return user
}

// ✅ CORRECT: Immutability
function updateUser(user, name) {
  return {
    ...user,
    name
  }
}

// ✅ CORRECT: Array immutable update
const updatedPages = pages.map(p => p.id === id ? { ...p, title: newTitle } : p);

// ❌ WRONG: Array mutation
pages[index].title = newTitle;

File Organization

MANY SMALL FILES > FEW LARGE FILES:

  • High cohesion, low coupling
  • 200-400 lines typical, 800 max
  • Functions < 50 lines
  • Extract utilities from large components
  • Organize by feature/domain, not by type

Naming Conventions

Variables and Functions

  • camelCase for variables and functions
  • PascalCase for classes, interfaces, types, React components
  • UPPER_SNAKE_CASE for constants

    const pageId = '123';
    const MAX_PAGE_SIZE = 1000;
    
    function getPageById(id: string) { }
    class PageService { }
    interface PageData { }
    type PageStatus = 'draft' | 'published';
    

Files and Directories

  • PascalCase for React components: Button.tsx, PageTree.tsx
  • kebab-case for utilities: page-utils.ts
  • lowercase for directories: features/page-tree/, utils/

Export Style

Prefer named exports over default exports:

// ✅ Good: Named exports
export const MyComponent = () => { };
export function myFunction() { }
export class MyClass { }

// ❌ Avoid: Default exports
export default MyComponent;

Why?

  • Better refactoring (IDEs can reliably rename across files)
  • Better tree shaking
  • Explicit imports improve readability
  • No ambiguity (import name matches export name)

Exception: Next.js pages require default exports.

Type Safety

Always provide explicit types for function parameters and return values:

// ✅ Good: Explicit types
function createPage(path: string, body: string): Promise<Page> {
  // ...
}

// ❌ Avoid: Implicit any
function createPage(path, body) {
  // ...
}

Use import type for type-only imports:

import type { PageData } from '~/interfaces/page';

Error Handling

ALWAYS handle errors comprehensively:

try {
  const result = await riskyOperation();
  return result;
} catch (error) {
  logger.error('Operation failed:', { error, context });
  throw new Error('Detailed user-friendly message');
}

Async/Await

Prefer async/await over Promise chains:

// ✅ Good: async/await
async function loadPages() {
  const pages = await fetchPages();
  const enriched = await enrichPageData(pages);
  return enriched;
}

// ❌ Avoid: Promise chains
function loadPages() {
  return fetchPages()
    .then(pages => enrichPageData(pages))
    .then(enriched => enriched);
}

Comments

Write comments in English (even for Japanese developers):

// ✅ Good: English comment
// Calculate the total number of pages in the workspace

// ❌ Avoid: Japanese comment
// ワークスペース内のページ総数を計算

When to comment:

  • Complex algorithms or business logic
  • Non-obvious workarounds
  • Public APIs and interfaces

When NOT to comment:

  • Self-explanatory code (good naming is better)
  • Restating what the code does

Test File Placement

Co-locate tests with source files in the same directory:

src/utils/
├── helper.ts
└── helper.spec.ts        # Test next to source

src/components/Button/
├── Button.tsx
└── Button.spec.tsx       # Test next to component

Test File Naming

  • Unit tests: *.spec.{ts,js}
  • Integration tests: *.integ.ts
  • Component tests: *.spec.{tsx,jsx}

Git Commit Messages

Follow conventional commit format:

<type>(<scope>): <subject>

<body>

Types: feat, fix, refactor, test, docs, chore

Example:

feat(page-tree): add virtualization for large trees

Implemented react-window for virtualizing page tree
to improve performance with 10k+ pages.

Cross-Platform Compatibility

GROWI must work on Windows, macOS, and Linux. Never use platform-specific shell commands in npm scripts.

// ❌ WRONG: Unix-only commands in npm scripts
"clean": "rm -rf dist",
"copy": "cp src/foo.ts dist/foo.ts",
"move": "mv src dist"

// ✅ CORRECT: Cross-platform tools
"clean": "rimraf dist",
"copy": "node -e \"require('fs').cpSync('src/foo.ts','dist/foo.ts')\"",
"move": "node -e \"require('fs').renameSync('src','dist')\""

Rules:

  • Use rimraf instead of rm -rf
  • Use Node.js one-liners or cross-platform tools (cpy-cli, cpx2) instead of cp, mv, echo, ls
  • Never assume a POSIX shell in npm scripts

Code Quality Checklist

Before marking work complete:

  • Code is readable and well-named
  • Functions are small (<50 lines)
  • Files are focused (<800 lines)
  • No deep nesting (>4 levels)
  • Proper error handling
  • No console.log statements (use logger)
  • No mutation (immutable patterns used)
  • Named exports (except Next.js pages)
  • English comments
  • Co-located tests