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 TypeMax LinesAction When Exceeded
Screen component250Extract sub-components
UI component150Extract logic into hooks
Custom hook100Split into smaller hooks
Service class300Split by responsibility
Utility module200Group by domain
Rust module400Split into sub-modules
Test file500Split by test category
Type definitions200Split by domain
MetricLimitRationale
Line length100 charsReadable without horizontal scroll
Function body30 linesFits on one screen
Function parameters4 maxUse options object beyond 4
Nesting depth3 levelsExtract helper or return early
Imports per file15 maxSign 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 { ... }
ElementConventionExample
Files (TS)kebab-case.tsdive-plan-service.ts
Files (Components)PascalCase.tsxDiveProfileChart.tsx
Files (Hooks)useCamelCase.tsuseDivePlan.ts
InterfacesPascalCaseDiveLog
TypesPascalCaseDiveType
FunctionscamelCasecalculateNDL
ConstantsUPPER_SNAKE_CASEMAX_DEPTH_METERS
Database tablessnake_casedive_log
i18n keysnamespace.section.keyplanning.ndl.title
Rust modulessnake_casebuhlmann.rs
Rust functionssnake_casecalculate_ndl
Rust typesPascalCaseGasMix

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