Skip to content

Coding Standards

This document defines the coding standards for DiveSuite. Consistency makes the codebase easier to read, review, and maintain.

TypeScript is configured with strict mode:

{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
// Good: explicit types
function calculateNDL(depth: number, gasMix: GasMix): number {
// ...
}
// Bad: implicit any
function calculateNDL(depth, gasMix) {
// ...
}
// Good: use unknown for truly unknown types
function parseImport(data: unknown): DiveLog {
// validate and parse
}
// Bad: using any
function parseImport(data: any): DiveLog {
// no type safety
}

Prefer interface for object shapes:

// Preferred: interface for objects
interface DiveLog {
id: string;
depth: number;
duration: number;
}
// Use type for unions, intersections, primitives
type DiveType = 'OC' | 'CCR' | 'SCR';
type Meters = number;

Use named exports, not default exports:

// Good: named export
export function DiveProfileChart() { ... }
export interface DiveLog { ... }
// Bad: default export
export default function DiveProfileChart() { ... }

Never swallow errors:

// Good: handle errors explicitly
try {
const result = await planningService.calculate(input);
return result;
} catch (error) {
logger.error('Planning calculation failed', { error, input });
throw new PlanningError('Calculation failed', { cause: error });
}
// Bad: swallowing errors
try {
return await planningService.calculate(input);
} catch {
return null; // Lost error information
}

All public functions must have doc comments:

/// Calculates the no-decompression limit for the given parameters.
///
/// # Arguments
/// * `depth` - Depth in meters
/// * `gas_mix` - The breathing gas mixture
/// * `gf_high` - Gradient factor high (0-100)
///
/// # Returns
/// NDL in minutes, or 0 if already in decompression
///
/// # Example
/// ```
/// let ndl = calculate_ndl(30.0, &GasMix::air(), 85);
/// assert!(ndl > 0);
/// ```
pub fn calculate_ndl(depth: f64, gas_mix: &GasMix, gf_high: u8) -> u32 {
// ...
}

No panics in production code:

// Good: return Result
pub fn calculate_mod(gas_mix: &GasMix, pp_o2_limit: f64) -> Result<f64, DecoError> {
if gas_mix.o2_fraction <= 0.0 {
return Err(DecoError::InvalidGasMix("O2 fraction must be positive"));
}
Ok((pp_o2_limit / gas_mix.o2_fraction - 1.0) * 10.0)
}
// Bad: panicking
pub fn calculate_mod(gas_mix: &GasMix, pp_o2_limit: f64) -> f64 {
assert!(gas_mix.o2_fraction > 0.0); // Will panic!
(pp_o2_limit / gas_mix.o2_fraction - 1.0) * 10.0
}

Every function needs at least one test:

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_ndl_air_18m() {
let gas = GasMix::air();
let ndl = calculate_ndl(18.0, &gas, 85).unwrap();
assert_eq!(ndl, 56); // Expected value from reference
}
#[test]
fn test_calculate_ndl_zero_depth() {
let gas = GasMix::air();
let ndl = calculate_ndl(0.0, &gas, 85).unwrap();
assert_eq!(ndl, 999); // Unlimited at surface
}
}
File Type Max Lines Action When Exceeded
Screen component 250 Extract sub-components
UI component 150 Extract logic into hooks
Custom hook 100 Split into smaller hooks
Service class 300 Split by responsibility
Utility module 200 Group by domain
Rust module 400 Split into sub-modules
Test file 500 Split by test category
Type definitions 200 Split by domain
Metric Limit Rationale
Line length 100 chars Readable without horizontal scroll
Function body 30 lines Fits on one screen
Function parameters 4 max Use options object beyond 4
Nesting depth 3 levels Extract helper or return early
Imports per file 15 max Sign of too many dependencies
// Bad: too many parameters
function createDivePlan(
depth: number,
duration: number,
gasMix: GasMix,
gfLow: number,
gfHigh: number,
altitude: number,
ascentRate: number
): DivePlan { ... }
// Good: options object
interface PlanOptions {
depth: number;
duration: number;
gasMix: GasMix;
gradientFactors: { low: number; high: number };
altitude?: number;
ascentRate?: number;
}
function createDivePlan(options: PlanOptions): DivePlan { ... }
Element Convention Example
Files (TS) kebab-case.ts dive-plan-service.ts
Files (Components) PascalCase.tsx DiveProfileChart.tsx
Files (Hooks) useCamelCase.ts useDivePlan.ts
Interfaces PascalCase DiveLog
Types PascalCase DiveType
Functions camelCase calculateNDL
Constants UPPER_SNAKE_CASE MAX_DEPTH_METERS
Database tables snake_case dive_log
i18n keys namespace.section.key planning.ndl.title
Rust modules snake_case buhlmann.rs
Rust functions snake_case calculate_ndl
Rust types PascalCase GasMix

All user-facing strings must use the t() function:

// Good: i18n
const { t } = useTranslation();
return <Text>{t('planning.ndl.result', { minutes: ndl })}</Text>;
// Bad: hardcoded string
return <Text>NDL: {ndl} minutes</Text>;

Use Conventional Commits:

feat: add altitude diving calculation
fix: correct NDL at 30m with EAN32
refactor: extract gas calculation utilities
test: add property-based tests for tissue loading
docs: update deco engine validation spec
chore: upgrade Expo to SDK 52
<type>(<scope>): <subject>
<body>
<footer>

Example:

feat(planning): add reverse planning calculation
Implements P1-F08 (reverse planning) - given tank size and gas mix,
calculate maximum bottom time at depth.
Closes #42