Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Coding Standards|Rules, conventions, and best practices I follow and recommend to teams

Code Structure

Establishing clear and consistent code organization brings long-term benefits to any codebase.

Well-structured code is easier to read, understand, and navigate, which accelerates onboarding, simplifies debugging, and improves collaboration across teams. It also helps prevent technical debt, reduces duplication, and makes scaling the project more predictable and maintainable.

Keep files under a reasonable size

Split files when they grow too large (e.g., 200+ lines). Long files make it hard to locate logic and increase cognitive load.

TypeScript
Copied!
// UserDashboard.tsx (700 lines)
TSX
Copied!
1UserDashboard.tsx
2useUserData.ts
3userCharts.tsx
4userStats.tsx

Group and sort imports consistently

Imports should be grouped by type (e.g., external libraries, internal modules, styles) and sorted alphabetically within each group. This improves clarity, reduces merge conflicts, and makes file scanning faster.

Recommended order:

  • External libraries (e.g. react, next, axios)

  • Internal modules (e.g. components/, utils/, hooks/)

  • Relative imports (./SomeComponent)

  • Styles (.css, .scss)

  • Blank line between groups

TypeScript
Copied!
1import styles from './App.module.css';
2import React from 'react';
3import { useUser } from '../../hooks/useUser';
4import { Button } from '../components/Button';
5import _ from 'lodash';
6import { formatDate } from '../../utils/date';
TypeScript
Copied!
1// External
2import React from 'react';
3import _ from 'lodash';
4
5// Internal with Module Path Aliases
6import { Button } from '@/components/Button';
7import { useUser } from '@/hooks/useUser';
8import { formatDate } from '@/utils/date';
9
10// Styles
11import styles from './App.module.scss';

Prefer module path aliases over deep relative imports

Use relative paths only when importing files that are in the same folder or a direct child folder. If you need to reach outside (e.g. ../../../../utils/), switch to module path aliases for cleaner, more readable imports. This makes the file structure more intuitive and easier to refactor, especially in large codebases.

TypeScript
Copied!
1import { formatDate } from '../../../../utils/date';
2import { Button } from '../../../components/ui/Button';
TypeScript
Copied!
1import { formatDate } from '@utils/date';
2import { Button } from '@components/ui/Button';

Use consistent code ordering in files

Follow a consistent order for declarations: types, constants, functions, exports. Helps readability and predictability

TypeScript
Copied!
1function main() {}
2const VERSION = '1.0';
3type Config = { ... };
4export default main;
TypeScript
Copied!
1type Config = { ... };
2
3const VERSION = '1.0';
4
5function main() { ... }
6
7export default main;

Favor composition over inheritance

Whenever possible, prefer building behavior through composition — combining smaller functions or modules — instead of using class inheritance.

Inheritance tightly couples classes and often leads to fragile hierarchies that are hard to change or extend. Composition provides more flexibility, better separation of concerns, and easier testing.

TypeScript
Copied!
1class Animal {
2  makeSound() {
3    console.log("Some sound");
4  }
5}
6
7class Dog extends Animal {
8  makeSound() {
9    console.log("Woof!");
10  }
11}
12
13class GuardDog extends Dog {
14  makeSound() {
15    super.makeSound();
16    console.log("...and growls");
17  }
18}
TypeScript
Copied!
1function makeSound(animal: string) {
2  if (animal === 'dog') console.log('Woof!');
3}
4
5function withGuardBehavior(fn: () => void) {
6  return () => {
7    fn();
8    console.log('...and growls');
9  };
10}
11
12const guardDogSound = withGuardBehavior(() => makeSound('dog'));
13guardDogSound();

Separate concerns clearly

Organize your code so that each module, function, or layer is responsible for a single concern — whether it’s data access, business logic, input validation, or rendering.

Mixing responsibilities leads to tangled code, makes testing harder, and increases maintenance complexity.

TypeScript
Copied!
1/*
2  All concerns in one place:
3  Input validation, DB access, hashing, and response logic are tightly mixed
4  Difficult to test or reuse separately
5  Harder to refactor or change requirements
6*/
7
8// routes/userRoute.js
9app.post('/register', async (req, res) => {
10  const { email, password } = req.body;
11
12  if (!email || !password) {
13    return res.status(400).json({ error: 'Missing credentials' });
14  }
15
16  const existing = await db.query('SELECT * FROM users WHERE email = $1', [email]);
17  if (existing.rows.length > 0) {
18    return res.status(409).json({ error: 'User already exists' });
19  }
20
21  const hashed = await bcrypt.hash(password, 12);
22  await db.query('INSERT INTO users (email, password) VALUES ($1, $2)', [email, hashed]);
23
24  res.status(201).json({ message: 'User created' });
25});
TypeScript
Copied!
1// routes/userRoute.js
2app.post('/register', validate(registerSchema), registerUser);
3
4// validators/registerSchema.js
5export const registerSchema = {
6  body: {
7    email: { type: 'string', required: true },
8    password: { type: 'string', minLength: 8, required: true }
9  }
10};
11
12// controllers/registerUser.js
13export async function registerUser(req, res) {
14  const { email, password } = req.body;
15
16  const exists = await userRepo.existsByEmail(email);
17  if (exists) {
18    return res.status(409).json({ error: 'User already exists' });
19  }
20
21  const hashed = await hashPassword(password);
22  await userRepo.create({ email, password: hashed });
23
24  res.status(201).json({ message: 'User created' });
25}

Document intent, not mechanics

Write comments that explain why something is done, not what is done — the code already shows the “what”.

Use comments to clarify purpose, assumptions, edge cases, or non-obvious decisions. Avoid redundant or obvious comments that duplicate the code.

JavaScript
Copied!
1// increment i by 1
2i++;
3
4// call the API
5fetch('/api/user');
6
7// loop through array
8for (let i = 0; i < items.length; i++) { ... }
JavaScript
Copied!
1// This API returns stale data — adding a cache-busting query param
2fetch(`/api/user?_=${Date.now()}`);
3
4// Using a manual loop for better performance on large arrays
5for (let i = 0; i < items.length; i++) { ... }
6
7// Avoid using debounce here — user expects instant feedback
8handleInputChange(value);

Avoid commented-out code

Dead code clutters the file and confuses collaborators. Use version control instead of commenting-out unused logic.

One export per file (unless grouping makes sense)

Keep each file focused and predictable.

Exceptions: utility files, constant groups, or small related functions.

JavaScript
Copied!
1// Single file
2export function createUser() {}
3export function deleteUser() {}
4export function banUser() {}
JavaScript
Copied!
1// createUser.ts
2export function createUser() {}
3
4// deleteUser.ts
5export function deleteUser() {}
6
7// Or group when appropriate:
8// userActions.ts
9export const userActions = {
10  create: () => {},
11  delete: () => {},
12  ban: () => {}
13};

Minimize side effects in top-level code

Avoid logic that runs immediately when a module is loaded unless necessary (e.g., fetching, mutating global state). Prefer deferring side effects until explicitly invoked.

TypeScript
Copied!
1// services/logger.ts
2connectToLogServer(); // runs on import
3
4export function log(message: string) {
5  // ...
6}
TypeScript
Copied!
1export function initLogger() {
2  connectToLogServer();
3}
4
5export function log(message: string) {
6  // ...
7}

Align closing brackets in multiline expressions

When an expression spans multiple lines (function calls, object literals, arrays, etc.), place the closing bracket directly under the opening bracket or aligned with the start of the expression. This improves visual structure and makes deeply nested code easier to parse.

JavaScript
Copied!
1const importantRule = {
2  ruleId: 'align-closing-brackets',
3  title: 'Align Closing Brackets',
4  content: [
5    { type: ruleType.TEXT,
6      text: 'When an expression ...',
7    },
8  ],
9};

Use trailing commas in multiline structures

When working with multiline arrays, objects, or parameter lists, always add a trailing comma after the last item. This leads to cleaner version control diffs, easier refactoring, and consistent formatting.

  • Cleaner diffs in version control (only new lines change)

  • No syntax errors when reordering or commenting

  • Visually consistent formatting

  • Supported in all modern JS/TS environments (ES2017+)

Exceptions: Do not use trailing commas in JSON or in single-line structures (unless your formatter does).

JavaScript
Copied!
1const user = {
2  name: 'Alice',
3  age: 30,
4};
5
6const roles = [
7  'admin',
8  'editor',
9];
10
11function greet(
12  name: string,
13  age: number,
14) {
15  // ...
16}

End files with a single newline

Always leave exactly one newline character (\n) at the end of each file. This ensures compatibility with POSIX systems, keeps Git diffs clean, and aligns with formatting tools and linters.

It’s a small but meaningful consistency rule in professional codebases.

JavaScript
Copied!
1export function add(a, b) {
2  return a + b;
3}
JavaScript
Copied!
1export function add(a, b) {
2  return a + b;
3}
4
Styleguide: Code Structure | Yevhen Klymentiev