15 принципів якісного коду: як писати зрозуміло, чисто й ефективно

15 принципів якісного коду: як писати зрозуміло, чисто й ефективно

  • 17 січня
  • читати 20 хв
Володимир Шайтан
Володимир Шайтан Senior Full Stack Developer у UKEESS Software House, Викладач Комп'ютерної школи Hillel.

Писати поганий код — як клеїти скотчем дірки на кораблі. Начебто працює, але не надовго. Якісний код — це не магія, а дотримання чітких принципів. Розберімось, як застосовувати їх на практиці. Додамо складніші приклади, щоб усе було максимально зрозуміло.

ПРИНЦИП 1: ДОТРИМУЙСЯ СТИЛЬОВОГО ПОСІБНИКА

Style Guide визначає єдиний підхід до написання коду: форматування, відступи, назви змінних. Консистентність робить код легким для читання і підтримки.

✅ Добре:
interface User {
   id: number;
   name: string;
   email: string;
}

function fetchUserData(userId: number): Promise<User> {
   return api.get(`/users/${userId}`);
}

❌ Погано:
interface user{
   Id:number,Name:string,email:string
}
function Fetch_user_data(id:Number){
   return Api.GET("/users/"+id)
}

Практична порада: використовуй ESLint і Prettier для автоматичного форматування. Не покладайся лише на свій зір — інструменти збережуть твої нерви.

ПРИНЦИП 2: ВИКОРИСТОВУЙ ЗРОЗУМІЛІ НАЗВИ

Назви повинні бути інтуїтивно зрозумілими, навіть якщо читаєш їх уперше.

✅ Добре:
function generateInvoice(items, taxRate) {
   return items.reduce((total, item) => total + item.price, 0) * (1 + taxRate);
}
❌ Погано:
function inv(items, t) {
   let s = 0;
   items.forEach(i => s += i.p);
   return s * (1 + t);
}

Складніший приклад: уреальному житті інколи функція має багато параметрів. Використовуй об'єкт для передачі параметрів.

✅ Добре:
function createReport({ userId, startDate, endDate, includeDetails }) {
   // Логіка для створення звіту
}

❌ Погано:
function createReport(userId, startDate, endDate, includeDetails) {
   // Логіка для створення звіту
}

Чому краще? Якщо параметрів стає понад п'ять, важко зрозуміти, що означає кожен із них.

ПРИНЦИП 3: ПИШИ КОМЕНТАРІ Й ДОКУМЕНТАЦІЮ

Коментарі пояснюють, що робить складний блок коду, а документація описує, як користуватися твоїм API.

✅ Добре:
/**
* Fetches user orders and aggregates them by status.
* @param userId - ID of the user
* @returns Promise resolving to an aggregated order summary
*/
async function getUserOrderSummary(userId: number): Promise<OrderSummary> {
   const orders = await api.get(`/users/${userId}/orders`);
   return orders.reduce((summary, order) => {
       summary[order.status] = (summary[order.status] || 0) + 1;
       return summary;
   }, {});
}

❌ Погано:
async function fetchOrders(id) {
   let o = await api.get(`/users/${id}/orders`);
   return o.reduce((s, i) => {
       s[i.status] = (s[i.status] || 0) + 1;
       return s;
   }, {});
}

Практична порада: документуй функції, які мають складну логіку або критично важливі для проєкту.

ПРИНЦИП 4: УНИКАЙ ПОВТОРЕНЬ

Дублювання коду — це головний біль під час підтримки.

✅ Добре:
function validateInput(input, rules) {
   return rules.every(rule => rule(input));
}

const isNotEmpty = (str) => str.trim() !== '';
const isEmail = (str) => /\S+@\S+\.\S+/.test(str);

validateInput('test@example.com', [isNotEmpty, isEmail]);

❌ Погано:
function validateEmail(email) {
   if (email.trim() === '') return false;
   if (!/\S+@\S+\.\S+/.test(email)) return false;
   return true;
}

function validateUsername(username) {
   if (username.trim() === '') return false;
   return true;
}

ПРИНЦИП 5: ОБРОБЛЯЙ ПОМИЛКИ ТА ВИКЛЮЧЕННЯ

Обробка помилок — це не просто try-catch, це план дій на випадок проблем.

✅ Добре:
async function fetchData(url) {
   try {
       const response = await fetch(url);
       if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
       return await response.json();
   } catch (error) {
       console.error('Fetch error:', error);
       throw new Error('Unable to fetch data');
   }
}
❌ Погано:
async function fetchData(url) {
   const response = await fetch(url);
   return await response.json(); // Без обробки помилок
}

ПРИНЦИП 6: РОЗБИВАЙ КОД НА ЗРОЗУМІЛІ БЛОКИ

Функції повинні бути атомарними.

✅ Добре:
function processUserData(user) {
   const isValid = validateUser(user);
   if (!isValid) throw new Error('Invalid user data');

   saveUserToDatabase(user);
   sendWelcomeEmail(user.email);
}

function validateUser(user) {
   return user.name && user.email;
}

❌ Погано:
function processUserData(user) {
   if (!user.name || !user.email) {
       throw new Error('Invalid user data');
   }
   saveUserToDatabase(user);
   sendWelcomeEmail(user.email);
}

ПРИНЦИП 7: ВИКОРИСТОВУЙ ГОТОВІ API ТА БІБЛІОТЕКИ

✅ Добре:
import _ from 'lodash';

const numbers = [1, 2, 3, 4, 5];
const doubled = _.map(numbers, (num) => num * 2);
❌ Погано:
const numbers = [1, 2, 3, 4, 5];
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
   doubled.push(numbers[i] * 2);
}

ПРИНЦИП 8: УНИКАЙ НАДМІРНОГО ПРОЄКТУВАННЯ

✅ Добре:
class User {
   constructor(name) {
       this.name = name;
   }
}

❌ Погано:
class UserFactory {
   static createUser(name) {
       return new User(name);
   }
}

ПРИНЦИП 9: БУДЬ ПОСЛІДОВНИМ

Коли ти пишеш код, дотримуйся одних і тих самих патернів та підходів у всьому проєкті. Це стосується форматування, назв змінних, структури компонентів і навіть способів обробки помилок.

✅ Добре:
// Обидві функції використовують async/await
async function fetchData(url) {
   const response = await fetch(url);
   return await response.json();
}

async function getUser(id) {
   const response = await fetch(`/user/${id}`);
   return await response.json();
}
❌ Погано:
// Функція з колбеком
function fetchData(url, callback) {
   fetch(url).then((response) => callback(response));
}

// Функція з async/await
async function getUser(id) {
   const response = await fetch(`/user/${id}`);
   return await response.json();
}

Пояснення: у першому випадку код використовує два різних підходи для роботи з асинхронними операціями. Це заплутує інших розробників. Вибери один стиль і дотримуйся його всюди.

ПРИНЦИП 10: ДУМАЙ ПРО БЕЗПЕКУ

Безпека коду — це не лише про складні паролі. Це про те, як ти працюєш із вхідними даними, API, базами даних і зовнішніми запитами.

✅ Добре:
const userId = req.query.userId;
db.query('SELECT * FROM users WHERE id = ?', [userId]);
❌ Погано:
const userId = req.query.userId;
db.query(`SELECT * FROM users WHERE id = ${userId}`);

Що сталося? У першому прикладі SQL-запит вразливий до SQL-ін'єкцій. У другому прикладі використовується параметризований запит, що захищає базу даних від подібних атак.

Інші практики:

  • завжди валідуй вхідні дані;
  • використовуй лінтери для пошуку вразливостей (наприклад, eslint-plugin-security);
  • зберігай секрети (API-ключі, паролі) у змінних середовища.

ПРИНЦИП 11:ВИКОРИСТОВУЙ ЕФЕКТИВНІ СТРУКТУРИ ДАНИХ

Ефективний вибір структури даних може значно покращити продуктивність твого коду.

✅ Добре:
const users = new Map([
   [1, { id: 1, name: 'Alice' }],
   [2, { id: 2, name: 'Bob' }]
]);

function getUserById(id) {
   return users.get(id);
}
❌ Погано:
const users = [
   { id: 1, name: 'Alice' },
   { id: 2, name: 'Bob' }
];


function getUserById(id) {
   return users.find(user => user.id === id);
}

Що сталося? У першому прикладі кожен пошук користувача виконується через ітерацію по масиву. У другому прикладі використовується Map, що забезпечує доступ до елементів за ключем майже миттєво.

ПРИНЦИП 12: ПИШИ ТЕСТИ

Тестування — це не додатковий квест, а частина розробки. Воно дозволяє впевнено вносити зміни в код і не боятися його поламати.

Складніший приклад:

✅ Добре:
// Функція для обчислення знижки
function calculateDiscount(price, discount) {
   if (discount > price) throw new Error('Discount cannot exceed price');
   return price - discount;
}

// Тест
test('calculateDiscount correctly calculates discount', () => {
   expect(calculateDiscount(100, 20)).toBe(80);
   expect(() => calculateDiscount(100, 200)).toThrow('Discount cannot exceed price');
});

Що сталося? Тест перевіряє як коректні сценарії, так і випадки, коли функція повинна викидати помилку.

Інструменти:

  • Jest
  • Mocha
  • Vitest

ПРИНЦИП 13: ЗАБЕЗПЕЧУЙ ПОРТАТИВНІСТЬ КОДУ

Твій код повинен працювати однаково добре на різних середовищах і платформах.

✅ Добре:
const path = require('path');
const filePath = path.join(__dirname, 'file.txt');

❌ Погано:
const filePath = 'C:\\Users\\user\\file.txt'; // Windows-подібний шлях

Чому це важливо? Функція path.join гарантує правильний формат шляху незалежно від операційної системи.

ПРИНЦИП 14: АВТОМАТИЗУЙ ЗБІРКУ

Сучасний проєкт повинен мати автоматизовану систему збірки, яка включає: компіляцію коду, мініфікацію, запуск тестів.

Запуск тестів
✅ Добре:
{
   "scripts": {
   "build": "webpack --mode production",
       "start": "node dist/server.js",
       "test": "jest"
}
}

Чому це важливо? Один простий скрипт допомагає команді розробників запускати всі необхідні процеси.

ПРИНЦИП 15: ВИКОРИСТОВУЙ КОНТРОЛЬ ВЕРСІЙ

Кожна зміна в коді повинна бути зафіксована і задокументована.

✅ Добре:
git commit -m "fix: corrected discount calculation logic"

❌ Погано:
git commit -m "update"

Порада: використовуй GitFlow для організації гілок і пиши осмислені коміти.

ВИСНОВОК

Ось і все — 15 принципів, які роблять код чистішим, підтримуванішим і зрозумілішим. Це не список жорстких правил, а набір порад, які можна впроваджувати поступово.

Рекомендуємо публікації по темі