Принцип разделения ответственностей
В прошлой статье «Правило 7±2» говорилось о важности разделения кода на небольшие функции, что помогает в написании понятного и расширяемого кода.
После разделения кода на небольшие функции, каждая из них начинает отвечать за определенную ответственность (concern).
В статье используется термин «функция» для единообразия, но может использоваться и термин «объект», если в проекте применяется ООП.
Concern – ответственность, функциональность.
Разделение кода на различные ответственности – это базовый принцип, без которого не получиться разработать понятную архитектуру приложения. Данный принцип получил название – принцип разделения ответственностей (separation of concerns, SoC).
«Разделение ответственности – программа должны состоять из функциональных блоков, как можно меньше дублирующих функциональность друг друга» – Э. Дейкстра.
Рассмотрим простое приложение, которое получает список отелей от сервера, выполняет некоторые преобразования данных и отображает их пользователю.
<template id="list">
<ul class="list"></ul>
</template>
<template id="item">
<li class="item"></li>
</template>
<script>
async function main() {
// 1. Получаем данные от сервера
let hotels
try {
const hotelsReq = await fetch('/hotels')
hotels = await hotelsReq.json()
} catch (error) {}
if (!hotels) {
return
}
// 2. Выполняем преобразования данных
const fiveStarHotels = hotels.filter(({ stars }) => stars === 5)
// 3. Отображаем данные пользователю
const list = document.body.querySelector('#list')
const listClone = list.content.cloneNode(true)
const item = document.body.querySelector('#item')
const elems = fiveStarHotels.reduce((acc, { title }) => {
const itemClone = item.content.cloneNode(true)
itemClone.querySelector('.item').textContent = title
acc.append(itemClone)
return acc
}, listClone.querySelector('.list'))
document.body.append(elems)
}
main()
</script>
Вся функциональность находится в одной функции main()
. Этот код можно разбить на три функциональных блока (ответственности):
- Получение данных.
- Преобразование данных.
- Отображение данных.
Вышеперечисленные ответственности есть в большинстве клиентских приложений.
Сейчас данный код не следует принципу разделения ответственности. Попробуем его применить, выделив каждую ответственность в отдельную функцию.
<template id="list">
<ul class="list"></ul>
</template>
<template id="item">
<li class="item"></li>
</template>
<script>
// ДОСТУП К ДАННЫМ
async function fetchHotels() {
try {
const hotelsReq = await fetch('/hotels')
return await hotelsReq.json()
} catch (error) {}
}
// БИЗНЕС-ЛОГИКА
async function main() {
const hotels = await fetchHotels()
if (!hotels) {
return
}
const fiveStarHotels = getFiveStarHotels(hotels)
showHotels(fiveStarHotels)
}
function getFiveStarHotels(hotels) {
return hotels.filter(({ stars }) => stars === 5)
}
// ПРЕДСТАВЛЕНИЕ
function showHotels(fiveStarHotels) {
const elems = showList(fiveStarHotels)
document.body.append(elems)
}
function showList(items) {
const list = document.body.querySelector('#list')
const listClone = list.content.cloneNode(true)
const item = document.body.querySelector('#item')
const elems = items.reduce((acc, { title }) => {
const itemClone = item.content.cloneNode(true)
itemClone.querySelector('.item').textContent = title
acc.append(itemClone)
return acc
}, listClone.querySelector('.list'))
return elems
}
main()
</script>
Теперь код приложения стал состоять из нескольких функций с отчетливыми ответственностями, не дублирующих друг друга. Эти функции можно сгруппировать в три отдельные ответственности:
- Доступ к данным. Функция
fetchHotels()
. Бизнес-логика. Функции
getFiveStarHotels()
иmain()
.В
getFiveStarHotels()
происходит преобразование данных, что является бизнес-логикой. Функцияmain()
содержит основной код приложения, какую ценность оно несёт клиентам.Представление. Функции
showHotels()
иshowList()
.Дополнительно, была «выделена» функция
showList()
, которая получилась универсальной. Если потребуется отобразить другой список, будет достаточно вызывать готовую функциюshowList()
, передав данные в нужном формате, что сэкономят время на разработку.
Опытные разработчики могли заметить сходство с 3-tier архитектурой, которая разделяет код программы на три архитектурных слоя: доступ к данным, бизнес-логика и представление.
Разделения кода на ответственности полезно при внесении изменений. Часто требуется внести изменения в определенный функциональный блок. Например, требуется изменить представление. Т.к. код разделён по ответственностям, требуемый блок становится проще найти, не изучая весь код приложения.
Сложно представить понятную архитектуру без отчетливых ответственностей и связей между ними. SoC является базовым принципом при разработке ПО и проектировании архитектуры приложения. Не рекомендуется забывать про него. Это поможет писать надежный и расширяемый код.