789 lines
16 KiB
Markdown
789 lines
16 KiB
Markdown
# Coding Standards
|
|
## AtlasOS - Calypso Backup Appliance
|
|
|
|
**Version:** 1.0.0-alpha
|
|
**Date:** 2025-01-XX
|
|
**Status:** Active
|
|
|
|
---
|
|
|
|
## 1. Overview
|
|
|
|
This document defines the coding standards and best practices for the Calypso project. All code must adhere to these standards to ensure consistency, maintainability, and quality.
|
|
|
|
## 2. General Principles
|
|
|
|
### 2.1 Code Quality
|
|
- **Readability**: Code should be self-documenting and easy to understand
|
|
- **Maintainability**: Code should be easy to modify and extend
|
|
- **Consistency**: Follow consistent patterns across the codebase
|
|
- **Simplicity**: Prefer simple solutions over complex ones
|
|
- **DRY**: Don't Repeat Yourself - avoid code duplication
|
|
|
|
### 2.2 Code Review
|
|
- All code must be reviewed before merging
|
|
- Reviewers should check for adherence to these standards
|
|
- Address review comments before merging
|
|
|
|
### 2.3 Documentation
|
|
- Document complex logic and algorithms
|
|
- Keep comments up-to-date with code changes
|
|
- Write clear commit messages
|
|
|
|
---
|
|
|
|
## 3. Backend (Go) Standards
|
|
|
|
### 3.1 Code Formatting
|
|
|
|
#### 3.1.1 Use gofmt
|
|
- Always run `gofmt` before committing
|
|
- Use `goimports` for import organization
|
|
- Configure IDE to format on save
|
|
|
|
#### 3.1.2 Line Length
|
|
- Maximum line length: 100 characters
|
|
- Break long lines for readability
|
|
|
|
#### 3.1.3 Indentation
|
|
- Use tabs for indentation (not spaces)
|
|
- Tab width: 4 spaces equivalent
|
|
|
|
### 3.2 Naming Conventions
|
|
|
|
#### 3.2.1 Packages
|
|
```go
|
|
// Good: lowercase, single word, descriptive
|
|
package storage
|
|
package auth
|
|
package monitoring
|
|
|
|
// Bad: mixed case, abbreviations
|
|
package Storage
|
|
package Auth
|
|
package Mon
|
|
```
|
|
|
|
#### 3.2.2 Functions
|
|
```go
|
|
// Good: camelCase, descriptive
|
|
func createZFSPool(name string) error
|
|
func listNetworkInterfaces() ([]Interface, error)
|
|
func validateUserInput(input string) error
|
|
|
|
// Bad: unclear names, abbreviations
|
|
func create(name string) error
|
|
func list() ([]Interface, error)
|
|
func val(input string) error
|
|
```
|
|
|
|
#### 3.2.3 Variables
|
|
```go
|
|
// Good: camelCase, descriptive
|
|
var poolName string
|
|
var networkInterfaces []Interface
|
|
var isActive bool
|
|
|
|
// Bad: single letters, unclear
|
|
var n string
|
|
var ifs []Interface
|
|
var a bool
|
|
```
|
|
|
|
#### 3.2.4 Constants
|
|
```go
|
|
// Good: PascalCase for exported, camelCase for unexported
|
|
const DefaultPort = 8080
|
|
const maxRetries = 3
|
|
|
|
// Bad: inconsistent casing
|
|
const defaultPort = 8080
|
|
const MAX_RETRIES = 3
|
|
```
|
|
|
|
#### 3.2.5 Types and Structs
|
|
```go
|
|
// Good: PascalCase, descriptive
|
|
type ZFSPool struct {
|
|
ID string
|
|
Name string
|
|
Status string
|
|
}
|
|
|
|
// Bad: unclear names
|
|
type Pool struct {
|
|
I string
|
|
N string
|
|
S string
|
|
}
|
|
```
|
|
|
|
### 3.3 File Organization
|
|
|
|
#### 3.3.1 File Structure
|
|
```go
|
|
// 1. Package declaration
|
|
package storage
|
|
|
|
// 2. Imports (standard, third-party, local)
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/atlasos/calypso/internal/common/database"
|
|
)
|
|
|
|
// 3. Constants
|
|
const (
|
|
defaultTimeout = 30 * time.Second
|
|
)
|
|
|
|
// 4. Types
|
|
type Service struct {
|
|
db *database.DB
|
|
}
|
|
|
|
// 5. Functions
|
|
func NewService(db *database.DB) *Service {
|
|
return &Service{db: db}
|
|
}
|
|
```
|
|
|
|
#### 3.3.2 File Naming
|
|
- Use lowercase with underscores: `handler.go`, `service.go`
|
|
- Test files: `handler_test.go`
|
|
- One main type per file when possible
|
|
|
|
### 3.4 Error Handling
|
|
|
|
#### 3.4.1 Error Return
|
|
```go
|
|
// Good: always return error as last value
|
|
func createPool(name string) (*Pool, error) {
|
|
if name == "" {
|
|
return nil, fmt.Errorf("pool name cannot be empty")
|
|
}
|
|
// ...
|
|
}
|
|
|
|
// Bad: panic, no error return
|
|
func createPool(name string) *Pool {
|
|
if name == "" {
|
|
panic("pool name cannot be empty")
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.4.2 Error Wrapping
|
|
```go
|
|
// Good: wrap errors with context
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create pool %s: %w", name, err)
|
|
}
|
|
|
|
// Bad: lose error context
|
|
if err != nil {
|
|
return err
|
|
}
|
|
```
|
|
|
|
#### 3.4.3 Error Messages
|
|
```go
|
|
// Good: clear, actionable error messages
|
|
return fmt.Errorf("pool '%s' already exists", name)
|
|
return fmt.Errorf("insufficient disk space: need %d bytes, have %d bytes", needed, available)
|
|
|
|
// Bad: unclear error messages
|
|
return fmt.Errorf("error")
|
|
return fmt.Errorf("failed")
|
|
```
|
|
|
|
### 3.5 Comments
|
|
|
|
#### 3.5.1 Package Comments
|
|
```go
|
|
// Package storage provides storage management functionality including
|
|
// ZFS pool and dataset operations, disk discovery, and storage repository management.
|
|
package storage
|
|
```
|
|
|
|
#### 3.5.2 Function Comments
|
|
```go
|
|
// CreateZFSPool creates a new ZFS pool with the specified configuration.
|
|
// It validates the pool name, checks disk availability, and creates the pool.
|
|
// Returns an error if the pool cannot be created.
|
|
func CreateZFSPool(ctx context.Context, name string, disks []string) error {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
#### 3.5.3 Inline Comments
|
|
```go
|
|
// Good: explain why, not what
|
|
// Retry up to 3 times to handle transient network errors
|
|
for i := 0; i < 3; i++ {
|
|
// ...
|
|
}
|
|
|
|
// Bad: obvious comments
|
|
// Loop 3 times
|
|
for i := 0; i < 3; i++ {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### 3.6 Testing
|
|
|
|
#### 3.6.1 Test File Naming
|
|
- Test files: `*_test.go`
|
|
- Test functions: `TestFunctionName`
|
|
- Benchmark functions: `BenchmarkFunctionName`
|
|
|
|
#### 3.6.2 Test Structure
|
|
```go
|
|
func TestCreateZFSPool(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid pool name",
|
|
input: "tank",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty pool name",
|
|
input: "",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := createPool(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("createPool() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.7 Concurrency
|
|
|
|
#### 3.7.1 Context Usage
|
|
```go
|
|
// Good: always accept context as first parameter
|
|
func (s *Service) CreatePool(ctx context.Context, name string) error {
|
|
// Use context for cancellation and timeout
|
|
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
|
defer cancel()
|
|
// ...
|
|
}
|
|
|
|
// Bad: no context
|
|
func (s *Service) CreatePool(name string) error {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
#### 3.7.2 Goroutines
|
|
```go
|
|
// Good: use context for cancellation
|
|
go func() {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
// ...
|
|
}()
|
|
|
|
// Bad: no cancellation mechanism
|
|
go func() {
|
|
// ...
|
|
}()
|
|
```
|
|
|
|
### 3.8 Database Operations
|
|
|
|
#### 3.8.1 Query Context
|
|
```go
|
|
// Good: use context for queries
|
|
rows, err := s.db.QueryContext(ctx, query, args...)
|
|
|
|
// Bad: no context
|
|
rows, err := s.db.Query(query, args...)
|
|
```
|
|
|
|
#### 3.8.2 Transactions
|
|
```go
|
|
// Good: use transactions for multiple operations
|
|
tx, err := s.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// ... operations ...
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Frontend (TypeScript/React) Standards
|
|
|
|
### 4.1 Code Formatting
|
|
|
|
#### 4.1.1 Use Prettier
|
|
- Configure Prettier for consistent formatting
|
|
- Format on save enabled
|
|
- Maximum line length: 100 characters
|
|
|
|
#### 4.1.2 Indentation
|
|
- Use 2 spaces for indentation
|
|
- Consistent spacing in JSX
|
|
|
|
### 4.2 Naming Conventions
|
|
|
|
#### 4.2.1 Components
|
|
```typescript
|
|
// Good: PascalCase, descriptive
|
|
function StoragePage() { }
|
|
function CreatePoolModal() { }
|
|
function NetworkInterfaceCard() { }
|
|
|
|
// Bad: unclear names
|
|
function Page() { }
|
|
function Modal() { }
|
|
function Card() { }
|
|
```
|
|
|
|
#### 4.2.2 Functions
|
|
```typescript
|
|
// Good: camelCase, descriptive
|
|
function createZFSPool(name: string): Promise<ZFSPool> { }
|
|
function handleSubmit(event: React.FormEvent): void { }
|
|
function formatBytes(bytes: number): string { }
|
|
|
|
// Bad: unclear names
|
|
function create(name: string) { }
|
|
function handle(e: any) { }
|
|
function fmt(b: number) { }
|
|
```
|
|
|
|
#### 4.2.3 Variables
|
|
```typescript
|
|
// Good: camelCase, descriptive
|
|
const poolName = 'tank'
|
|
const networkInterfaces: NetworkInterface[] = []
|
|
const isActive = true
|
|
|
|
// Bad: unclear names
|
|
const n = 'tank'
|
|
const ifs: any[] = []
|
|
const a = true
|
|
```
|
|
|
|
#### 4.2.4 Constants
|
|
```typescript
|
|
// Good: UPPER_SNAKE_CASE for constants
|
|
const DEFAULT_PORT = 8080
|
|
const MAX_RETRIES = 3
|
|
const API_BASE_URL = '/api/v1'
|
|
|
|
// Bad: inconsistent casing
|
|
const defaultPort = 8080
|
|
const maxRetries = 3
|
|
```
|
|
|
|
#### 4.2.5 Types and Interfaces
|
|
```typescript
|
|
// Good: PascalCase, descriptive
|
|
interface ZFSPool {
|
|
id: string
|
|
name: string
|
|
status: string
|
|
}
|
|
|
|
type PoolStatus = 'online' | 'offline' | 'degraded'
|
|
|
|
// Bad: unclear names
|
|
interface Pool {
|
|
i: string
|
|
n: string
|
|
s: string
|
|
}
|
|
```
|
|
|
|
### 4.3 File Organization
|
|
|
|
#### 4.3.1 Component Structure
|
|
```typescript
|
|
// 1. Imports (React, third-party, local)
|
|
import { useState } from 'react'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { zfsApi } from '@/api/storage'
|
|
|
|
// 2. Types/Interfaces
|
|
interface Props {
|
|
poolId: string
|
|
}
|
|
|
|
// 3. Component
|
|
export default function PoolDetail({ poolId }: Props) {
|
|
// 4. Hooks
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
// 5. Queries
|
|
const { data: pool } = useQuery({
|
|
queryKey: ['pool', poolId],
|
|
queryFn: () => zfsApi.getPool(poolId),
|
|
})
|
|
|
|
// 6. Handlers
|
|
const handleDelete = () => {
|
|
// ...
|
|
}
|
|
|
|
// 7. Effects
|
|
useEffect(() => {
|
|
// ...
|
|
}, [poolId])
|
|
|
|
// 8. Render
|
|
return (
|
|
// JSX
|
|
)
|
|
}
|
|
```
|
|
|
|
#### 4.3.2 File Naming
|
|
- Components: `PascalCase.tsx` (e.g., `StoragePage.tsx`)
|
|
- Utilities: `camelCase.ts` (e.g., `formatBytes.ts`)
|
|
- Types: `camelCase.ts` or `types.ts`
|
|
- Hooks: `useCamelCase.ts` (e.g., `useStorage.ts`)
|
|
|
|
### 4.4 TypeScript
|
|
|
|
#### 4.4.1 Type Safety
|
|
```typescript
|
|
// Good: explicit types
|
|
function createPool(name: string): Promise<ZFSPool> {
|
|
// ...
|
|
}
|
|
|
|
// Bad: any types
|
|
function createPool(name: any): any {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
#### 4.4.2 Interface Definitions
|
|
```typescript
|
|
// Good: clear interface definitions
|
|
interface ZFSPool {
|
|
id: string
|
|
name: string
|
|
status: 'online' | 'offline' | 'degraded'
|
|
totalCapacityBytes: number
|
|
usedCapacityBytes: number
|
|
}
|
|
|
|
// Bad: unclear or missing types
|
|
interface Pool {
|
|
id: any
|
|
name: any
|
|
status: any
|
|
}
|
|
```
|
|
|
|
### 4.5 React Patterns
|
|
|
|
#### 4.5.1 Hooks
|
|
```typescript
|
|
// Good: custom hooks for reusable logic
|
|
function useZFSPool(poolId: string) {
|
|
return useQuery({
|
|
queryKey: ['pool', poolId],
|
|
queryFn: () => zfsApi.getPool(poolId),
|
|
})
|
|
}
|
|
|
|
// Usage
|
|
const { data: pool } = useZFSPool(poolId)
|
|
```
|
|
|
|
#### 4.5.2 Component Composition
|
|
```typescript
|
|
// Good: small, focused components
|
|
function PoolCard({ pool }: { pool: ZFSPool }) {
|
|
return (
|
|
<div>
|
|
<PoolHeader pool={pool} />
|
|
<PoolStats pool={pool} />
|
|
<PoolActions pool={pool} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Bad: large, monolithic components
|
|
function PoolCard({ pool }: { pool: ZFSPool }) {
|
|
// 500+ lines of JSX
|
|
}
|
|
```
|
|
|
|
#### 4.5.3 State Management
|
|
```typescript
|
|
// Good: use React Query for server state
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['pools'],
|
|
queryFn: zfsApi.listPools,
|
|
})
|
|
|
|
// Good: use local state for UI state
|
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
|
|
|
// Good: use Zustand for global UI state
|
|
const { user, setUser } = useAuthStore()
|
|
```
|
|
|
|
### 4.6 Error Handling
|
|
|
|
#### 4.6.1 Error Boundaries
|
|
```typescript
|
|
// Good: use error boundaries
|
|
function ErrorBoundary({ children }: { children: React.ReactNode }) {
|
|
// ...
|
|
}
|
|
|
|
// Usage
|
|
<ErrorBoundary>
|
|
<App />
|
|
</ErrorBoundary>
|
|
```
|
|
|
|
#### 4.6.2 Error Handling in Queries
|
|
```typescript
|
|
// Good: handle errors in queries
|
|
const { data, error, isLoading } = useQuery({
|
|
queryKey: ['pools'],
|
|
queryFn: zfsApi.listPools,
|
|
onError: (error) => {
|
|
console.error('Failed to load pools:', error)
|
|
// Show user-friendly error message
|
|
},
|
|
})
|
|
```
|
|
|
|
### 4.7 Styling
|
|
|
|
#### 4.7.1 TailwindCSS
|
|
```typescript
|
|
// Good: use Tailwind classes
|
|
<div className="flex items-center gap-4 p-6 bg-card-dark rounded-lg border border-border-dark">
|
|
<h2 className="text-lg font-bold text-white">Storage Pools</h2>
|
|
</div>
|
|
|
|
// Bad: inline styles
|
|
<div style={{ display: 'flex', padding: '24px', backgroundColor: '#18232e' }}>
|
|
<h2 style={{ fontSize: '18px', fontWeight: 'bold', color: 'white' }}>Storage Pools</h2>
|
|
</div>
|
|
```
|
|
|
|
#### 4.7.2 Class Organization
|
|
```typescript
|
|
// Good: logical grouping
|
|
className="flex items-center gap-4 p-6 bg-card-dark rounded-lg border border-border-dark hover:bg-border-dark transition-colors"
|
|
|
|
// Bad: random order
|
|
className="p-6 flex border rounded-lg items-center gap-4 bg-card-dark border-border-dark"
|
|
```
|
|
|
|
### 4.8 Testing
|
|
|
|
#### 4.8.1 Component Testing
|
|
```typescript
|
|
// Good: test component behavior
|
|
describe('StoragePage', () => {
|
|
it('displays pools when loaded', () => {
|
|
render(<StoragePage />)
|
|
expect(screen.getByText('tank')).toBeInTheDocument()
|
|
})
|
|
|
|
it('shows loading state', () => {
|
|
render(<StoragePage />)
|
|
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
})
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Git Commit Standards
|
|
|
|
### 5.1 Commit Message Format
|
|
```
|
|
<type>(<scope>): <subject>
|
|
|
|
<body>
|
|
|
|
<footer>
|
|
```
|
|
|
|
### 5.2 Commit Types
|
|
- **feat**: New feature
|
|
- **fix**: Bug fix
|
|
- **docs**: Documentation changes
|
|
- **style**: Code style changes (formatting, etc.)
|
|
- **refactor**: Code refactoring
|
|
- **test**: Test additions or changes
|
|
- **chore**: Build process or auxiliary tool changes
|
|
|
|
### 5.3 Commit Examples
|
|
```
|
|
feat(storage): add ZFS pool creation endpoint
|
|
|
|
Add POST /api/v1/storage/zfs/pools endpoint with validation
|
|
and error handling.
|
|
|
|
Closes #123
|
|
|
|
fix(shares): correct dataset_id field in create share
|
|
|
|
The frontend was sending dataset_name instead of dataset_id.
|
|
Updated to use UUID from dataset selection.
|
|
|
|
docs: update API documentation for snapshot endpoints
|
|
|
|
refactor(auth): simplify JWT token validation logic
|
|
```
|
|
|
|
### 5.4 Branch Naming
|
|
- **feature/**: New features (e.g., `feature/object-storage`)
|
|
- **fix/**: Bug fixes (e.g., `fix/share-creation-error`)
|
|
- **docs/**: Documentation (e.g., `docs/api-documentation`)
|
|
- **refactor/**: Refactoring (e.g., `refactor/storage-service`)
|
|
|
|
---
|
|
|
|
## 6. Code Review Guidelines
|
|
|
|
### 6.1 Review Checklist
|
|
- [ ] Code follows naming conventions
|
|
- [ ] Code is properly formatted
|
|
- [ ] Error handling is appropriate
|
|
- [ ] Tests are included for new features
|
|
- [ ] Documentation is updated
|
|
- [ ] No security vulnerabilities
|
|
- [ ] Performance considerations addressed
|
|
- [ ] No commented-out code
|
|
- [ ] No console.log statements (use proper logging)
|
|
|
|
### 6.2 Review Comments
|
|
- Be constructive and respectful
|
|
- Explain why, not just what
|
|
- Suggest improvements, not just point out issues
|
|
- Approve when code meets standards
|
|
|
|
---
|
|
|
|
## 7. Documentation Standards
|
|
|
|
### 7.1 Code Comments
|
|
- Document complex logic
|
|
- Explain "why" not "what"
|
|
- Keep comments up-to-date
|
|
|
|
### 7.2 API Documentation
|
|
- Document all public APIs
|
|
- Include parameter descriptions
|
|
- Include return value descriptions
|
|
- Include error conditions
|
|
|
|
### 7.3 README Files
|
|
- Keep README files updated
|
|
- Include setup instructions
|
|
- Include usage examples
|
|
- Include troubleshooting tips
|
|
|
|
---
|
|
|
|
## 8. Performance Standards
|
|
|
|
### 8.1 Backend
|
|
- Database queries should be optimized
|
|
- Use indexes appropriately
|
|
- Avoid N+1 query problems
|
|
- Use connection pooling
|
|
|
|
### 8.2 Frontend
|
|
- Minimize re-renders
|
|
- Use React.memo for expensive components
|
|
- Lazy load routes
|
|
- Optimize bundle size
|
|
|
|
---
|
|
|
|
## 9. Security Standards
|
|
|
|
### 9.1 Input Validation
|
|
- Validate all user inputs
|
|
- Sanitize inputs before use
|
|
- Use parameterized queries
|
|
- Escape output
|
|
|
|
### 9.2 Authentication
|
|
- Never store passwords in plaintext
|
|
- Use secure token storage
|
|
- Implement proper session management
|
|
- Handle token expiration
|
|
|
|
### 9.3 Authorization
|
|
- Check permissions on every request
|
|
- Use principle of least privilege
|
|
- Log security events
|
|
- Handle authorization errors properly
|
|
|
|
---
|
|
|
|
## 10. Tools and Configuration
|
|
|
|
### 10.1 Backend Tools
|
|
- **gofmt**: Code formatting
|
|
- **goimports**: Import organization
|
|
- **golint**: Linting
|
|
- **go vet**: Static analysis
|
|
|
|
### 10.2 Frontend Tools
|
|
- **Prettier**: Code formatting
|
|
- **ESLint**: Linting
|
|
- **TypeScript**: Type checking
|
|
- **Vite**: Build tool
|
|
|
|
---
|
|
|
|
## 11. Exceptions
|
|
|
|
### 11.1 When to Deviate
|
|
- Performance-critical code may require optimization
|
|
- Legacy code integration may require different patterns
|
|
- Third-party library constraints
|
|
|
|
### 11.2 Documenting Exceptions
|
|
- Document why standards are not followed
|
|
- Include comments explaining deviations
|
|
- Review exceptions during code review
|
|
|
|
---
|
|
|
|
## Document History
|
|
|
|
| Version | Date | Author | Changes |
|
|
|---------|------|--------|---------|
|
|
| 1.0.0-alpha | 2025-01-XX | Development Team | Initial coding standards document |
|
|
|