Освоєння мистецтва чистого коду: розкриття потужності принципів програмування
Кожна мова програмування має свій власний набір найкращих практик. Вони допомагають розробникам писати масштабований, легкочитний і організований код. Деякі з цих практик є універсальними й можуть бути застосовані в будь-якій мові програмування. Вони відомі як «Принципи програмування».
Принципи програмування — це набір правил і найкращих практик, які можна використовувати у будь-якій сфері програмування та з будь-якою мовою. Вони є фундаментальними знаннями для розробки програмного забезпечення. Незалежно від того, чи ви тільки почали програмувати, чи вже досвідчений розробник, розуміння та застосування цих принципів значно покращить вашу роботу.
У цій статті ми розглянемо три принципи програмування, які можуть і повинні бути використані в щоденній реалізації програм та які значно покращать якість вашого коду. Ці принципи, відомі як KISS (Keep It Simple, Stupid), DRY (Don’t Repeat Yourself) та SRP (Single Responsibility Principle), дають чудову настанову щодо того, як писати підтримуваний, адаптивний та надійний код, та можуть бути використані в усіх галузях розробки.
Ми спробуємо відрефакторити цей фрагмент коду, який може бути використаний для виконання HTTP-запитів:
// src/packages/users/libs/types/user-dto.type.ts type UserDto = { id: number; name: string; }; // src/packages/posts/libs/types/post-dto.type.ts type PostDto = { title: string; content: string; }; // src/libs/packages/http/http.package.ts class Http { baseUrl: string; headers: Headers; public constructor() { this.baseUrl = ''; this.headers = new Headers(); } public setBaseUrl(url: string): void { this.baseUrl = url; } public setHeaders(headers: Headers): void { this.headers = headers; } public async get<T = unknown>(url: string): Promise<T> { const response = await fetch(this.baseUrl + url, { method: 'GET', headers: this.headers, }); const data = await response.json(); console.log(`GET ${this.baseUrl + url} - Status: ${response.status}`); console.log('Response Data:', data); return data as T; } public async post<T = unknown>(url: string, data: T): Promise<T> { const response = await fetch(this.baseUrl + url, { method: 'POST', headers: this.headers, body: JSON.stringify(data), }); const responseData = await response.json(); console.log(`POST ${this.baseUrl + url} - Status: ${response.status}`); console.log('Response Data:', responseData); return responseData as T; } } // src/libs/packages/http/http.ts const http = new Http(); http.setBaseUrl('https://api.example.com'); const headers = new Headers(); headers.append('Content-Type', 'application/json'); http.setHeaders(headers); // src/slices/users/actions.ts const getUserById = (id: number): Promise<UserDto> => { return http.get<UserDto>(`/user/${id}`); }; // src/slices/posts/actions.ts const createPost = (title: string, content: string): Promise<PostDto> => { return http.post<PostDto>('/posts', { title, content }); };
KISS — Keep It Simple, Stupid!
Часто розробники надто ускладнюють код, який вони пишуть. Якщо код написаний складно, це робить його подальше відлагодження важким та подовжує час розробки в цілому. Принцип KISS закликає розробників писати код якнайпростіше. У більшості випадків це може бути важко, або навіть неможливо, але ви завжди повинні намагатися зробити свій код простим. Простота коду гарантує більш швидку розробку, легше відлагодження та збільшує читабельність коду в кілька разів.
Що таке простий код
Простий код — це не лише логіка коду, а також чітке іменування змінних та дотримання критеріїв якості. Ці кроки забезпечують створення читабельного та підтримуваного коду. Простий код не завжди означає короткий код. Особливо це слід враховувати під час написання однорядкових виразів. Виконане завдання лише в одному рядку коду може виглядати стильно. Але коли йдеться про читабельність, важливо, щоб він був простим. Переваги дотримання принципу KISS:
- Простота та читабельність: код, який є простим і легким для розуміння, зменшує ризик появи помилок і полегшує відлагодження.
- Легша підтримка: принцип KISS зменшує складність кодової бази. Коли код простий, його підтримка, оновлення та рефакторинг стають набагато простішими.
- Швидша розробка: простий код розробляється, тестується та оновлюється швидше, оскільки він мінімізує непотрібну складність.
У нашому фрагменті коду нам не потрібно мати методи setBaseUrl
та setHeaders
. За допомогою цих методів нам може знадобитися встановлювати різний baseUrl
та headers
для кожного запиту. Замість цього ми можемо відправляти baseUrl
під час ініціалізації екземпляр сервісу та обчислювати headers
всередині нього.
Ось як виглядає код з початку цієї статті, якщо застосувати принцип KISS:
// src/libs/types/content-type.type.ts type ContentType = 'application/json' | 'multipart/form-data'; // src/libs/packages/http/http.package.ts type Constructor = { baseUrl: string; }; class Http { private baseUrl: string; public constructor({ baseUrl }: Constructor) { this.baseUrl = baseUrl; } // Other methods private getHeaders(contentType?: ContentType): Headers { const headers = new Headers(); if (contentType) { headers.append('Content-Type', contentType); } return headers; } // Other methods } // src/libs/packages/http/http.ts const http = new Http({ baseUrl: 'https://api.example.com' });
Виправлена версія набагато легше підтримується. Ми можемо легко використовувати цей клас для різних кінцевих точок, створюючи нові екземпляри з різним baseUrl
, і ми легко можемо додавати нові заголовки, наприклад, якщо наш Backend вимагає аутентифікації. Нові заголовки додаються в одному місці, і нам не потрібно окремо додавати їх в кожному місці, де ми використовуємо цей клас.
Як застосовувати принцип KISS
- Розбивайте великі шматки коду на кілька менших.
- Використовуйте читабельні та змістовні імена змінних.
- Уникайте непотрібних ускладнень.
DRY — Don’t Repeat Yourself
Як і сказано в назві цього принципу, він закликає розробників не повторювати один і той самий код двічі. Згідно із цим принципом, певна логіка повинна бути написана лише один раз в кодовій базі. Найпростіший спосіб порушити принцип DRY — це копіювати та вставляти власний код.
Переваги написання коду, який дотримується принципу DRY:
- Можливість повторного використання коду: уникнення дубльованого коду заощаджує час розробки, забезпечує послідовність у поведінці й зменшує ризик появи нових помилок.
- Легше відлагодження та підтримка в цілому: пошук помилки або додавання нової функціональності до коду, який не дотримується принципу DRY, зазвичай передбачає внесення тих самих змін у багато різних файлів.
- Послідовність коду: DRY-код забезпечує послідовність логіки у всіх файлах та різних частинах програми. Це зменшує ризик помилок, спричинених неузгодженістю між дубльованим кодом.
У нашому фрагменті коду ми повторюємо логіку для запитів GET
та POST
. Ми можемо об’єднати методи для запитів GET
та POST
і створити один з назвою load
, який буде обробляти всі методи запитів.
Ось як виглядає код з початку цієї статті, якщо застосувати принцип DRY:
// src/libs/packages/http/libs/types/http-request-parameters.ts type HttpRequestParameters = { method: HttpMethod; url: string; payload?: BodyInit | null; contentType?: ContentType; }; // src/libs/packages/http/http.package.ts class Http { // Other methods public async load<T = unknown>({ method, url, payload, contentType, }: HttpRequestParameters): Promise<T> { const headers = this.getHeaders(contentType); const response = await fetch(`${this.baseUrl}${url}`, { method, headers, body: payload ? JSON.stringify(payload) : null, }); console.log(`${method} ${url} - Status: ${response.status}`); console.log('Response:', response); return response.json(); } // Other methods } // src/libs/packages/http/http.ts const http = new Http({ baseUrl: ' const http = new Http({ baseUrl: 'https://api.example.com' });
Як писати код, який дотримується принципу DRY
- Завжди пишіть код, який може бути використаний повторно, де це можливо.
- Ніколи не копіюйте той самий код.
- Розбивайте свій код на модулі та функції.
SRP — Single Responsibility Principle
Принцип єдиної відповідальності (SRP) стверджує, що клас або модуль повинен мати лише одну причину для зміни. Іншими словами, кожен клас або модуль повинен мати одну єдину відповідальність або завдання для виконання. Цей принцип допомагає підтримувати кодову базу організованою, підтримуваною та масштабованою.
За допомогою впровадження принципу SRP можна досягти наступних переваг:
- Організація коду: кожен клас або модуль має чітко визначену мету, що спрощує знаходження та розуміння його функціональності.
- Зручність підтримки: з однією відповідальністю зміни та оновлення певної функціональності можуть бути внесені без впливу на інші частини кодової бази.
- Дотримання принципів KISS і DRY: з чіткою та спрямованою відповідальністю стає легше підтримувати простоту коду та уникати дублювання коду.
У нашому фрагменті коду Http-
сервіс відповідальний за виконання запитів та логування повідомлень. Функціонал логування повинен бути оброблений у власному класі, Http-
сервіс не повинен бути відповідальний за це. Як цей принцип стверджує, кожен сервіс повинен бути відповідальний за виконання однієї задачі.
Ось як виглядає код з початку цієї статті, якщо застосувати принцип SRP:
// src/libs/packages/http/http.package.ts type Constructor = { baseUrl: string; logger: Logger; }; class Http { private baseUrl: string; private logger: Logger; public constructor(baseUrl, logger) { this.baseUrl = baseUrl; this.logger = logger; } // Other methods public async load<T = unknown>({ method, url, payload, contentType, }: LoadArguments): Promise<T> { const headers = this.getHeaders(contentType); const response = await fetch(`${this.baseUrl}${url}`, { method, headers, body: payload ? JSON.stringify(payload) : null, }); this.logger.log(`${method} ${url} - Status: ${response.status}`); this.logger.log(`Response: ${JSON.stringify(response)}`); return response.json(); } // Other methods } // src/libs/packages/logger/logger.package.ts class Logger { public log(message: string) { console.log('LOGGER: ', message); } } // src/libs/packages/logger/logger.ts const logger = new Logger(); // src/libs/packages/http/http.ts const http = new Http({ baseUrl: 'https://api.example.com', logger, }); ;
Ми створили окремий клас Logger
, який відповідає за логування повідомлень. Тепер запити HTTP та логування повідомлень розділені та обробляються різними класами. Дотримуючись принципу SRP, ви можете створювати більш модульний та підтримуваний код, що призводить до загальної вищої якості програмного забезпечення.
Як застосовувати принцип SRP
- Чітко визначайте відповідальності.
- Дотримуйтесь принципу SRP в функціях і методах.
- Спочатку визначте відповідальності, а потім розділіть їх.
Висновок
Принципи програмування, які ми розглянули (KISS, DRY і SRP), є важливими для написання високоякісного, підтримуваного та читабельного коду. Притримуючись цих принципів, розробники можуть покращити ефективність свого процесу розробки, зменшити дублювання коду та зробити його більш організованим і адаптивним.
Важливо відзначити, що ці принципи не є специфічними для жодної мови програмування і можуть бути універсально застосовані в будь-якій галузі розробки. Впровадження цих принципів значно сприятиме розробникам у їхньому програмувальному шляху.
Існують також інші принципи, як-от SOLID, YAGNI та інші. Ви можете спробувати дізнатися більше про них, але вони будуть вимагати більше вашої уваги, бо самі собою є складнішими. Але як і принципи, що ми з вами розібрали в цій статті, вони є дуже корисними.
Після рефакторингу нашого фрагмента коду, одним принципом за одним, кінцева версія виглядає так:
// src/libs/types/content-type.type.ts type ContentType = 'application/json' | 'multipart/form-data'; // src/libs/packages/http/libs/types/http-method.type.ts type HttpMethod = 'GET' | 'POST'; // src/packages/users/libs/types/user-dto.type.ts type UserDto = { id: number; name: string; }; // src/packages/posts/libs/types/post-dto.type.ts type PostDto = { title: string; content: string; }; // src/libs/packages/http/http.package.ts type LoadArguments = { method: HttpMethod; url: string; payload?: BodyInit | null; contentType?: ContentType; }; type Constructor = { baseUrl: string; logger: Logger; }; class Http { private baseUrl: string; private logger: Logger; public constructor({ baseUrl, logger }: Constructor) { this.baseUrl = baseUrl; this.logger = logger; } private getHeaders(contentType?: ContentType): Headers { const headers = new Headers(); if (contentType) { headers.append('Content-Type', contentType); } return headers; } public async load<T = unknown>({ method, url, payload, contentType, }: LoadArguments): Promise<T> { const headers = this.getHeaders(contentType); const response = await fetch(`${this.baseUrl}${url}`, { method, headers, body: payload ? JSON.stringify(payload) : null, }); this.logger.log(`${method} ${url} - Status: ${response.status}`); this.logger.log(`Response: ${JSON.stringify(response)}`); return response.json(); } } // src/libs/packages/logger/logger.package.ts class Logger { public log(message: string) { console.log('LOGGER: ', message); } } // src/libs/packages/logger/logger.ts const logger = new Logger(); // src/libs/packages/http/http.ts const http = new Http({ baseUrl: 'https://api.example.com', logger }); // src/slices/users/actions.ts const getUserById = (id: number): Promise<UserDto> => { return http.load<UserDto>({ url: `/user/${id}`, method: 'GET', }); }; // src/slices/posts/actions.ts const createPost = (title: string, content: string): Promise<PostDto> => { return http.load<PostDto>({ url: '/posts', method: 'POST', contentType: 'application/json', payload: JSON.stringify({ title, content }), }); }; <span data-token-index="0"> </span>
23 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів