1. Навіщо взагалі потрібен README
Щойно ви створили CLI-застосунок, дуже легко потрапити в пастку авторського погляду. Вам здається, що все й так зрозуміло: як запускати проєкт, які є команди, що вони приймають на вхід і що друкують у відповідь. Але це зрозуміло вам, тому що ви щойно писали код, бачили структуру проєкту й памʼятаєте всі домовленості.
Користувач опиняється в зовсім іншій ситуації. Він уперше відкриває репозиторій. У нього немає ні вашої памʼяті, ні вашого контексту, ні телепатичного звʼязку з main.swift. Для нього проєкт — це просто папка з файлами й одне запитання: «Як цим користуватися?»
Саме тут зʼявляється README.
README — це перша точка входу до проєкту. Він потрібен не для краси й не для галочки, а для дуже практичного завдання: допомогти людині швидко зрозуміти:
- що це за програма;
- як її запустити;
- які команди вона підтримує;
- який ввід вважається коректним;
- що застосунок виводить у відповідь.
Якщо цього немає, користувач починає вгадувати. А коли користувач вгадує, він майже завжди помиляється. Він пробує не ту команду, не той формат аргументів, не ту назву виконуваного файла — і за кілька хвилин уже думає, що проблема в проєкті, хоча насправді проблема в тому, що інструкції немає.
Тому README зручно сприймати як коротку й чесну інструкцію з використання. Не як довгий трактат про нутрощі проєкту, а як зрозумілий путівник: ось що це таке, ось як запустити, ось як користуватися.
Для CLI-інструментів це особливо важливо. У графічного застосунку є кнопки, вікна й підказки на екрані. У CLI майже все спілкування з користувачем відбувається через текст. Отже, README тут відіграє роль першої й дуже важливої частини інтерфейсу.
2. README — це «контракт із користувачем»
Коли ми вже зрозуміли, навіщо README потрібен загалом, можна зробити наступний крок.
README — це не просто «трохи тексту про проєкт». Для CLI-інструмента README краще сприймати як контракт із користувачем.
Уявіть, що LibraryCLI — це маленький автомат із кнопками. README в такому разі — це наліпка над панеллю керування: що робить кожна кнопка, які дії взагалі підтримуються, який ввід вважається нормальним і що станеться у відповідь.
Тобто README має знімати найважливіші запитання ще до того, як людина полізе у вихідний код:
- як правильно запускати застосунок;
- як називається команда;
- які аргументи вона приймає;
- як виглядає коректний ввід;
- який вивід вважати нормальним;
- що вважається помилкою.
У хорошого README є дуже важлива властивість: людина може почати користуватися застосунком без читання коду. Це і є головний тест якості. Якщо для запуску CLI потрібно відкрити Package.swift, подивитися main.swift, здогадатися про назви команд і ще розібратися в парсері аргументів, значить README не виконує свою роботу.
Водночас README не повинен перетворюватися на роман на сорок сторінок. Його завдання — не описати весь світ проєкту, а дати користувачеві коротку, точну й робочу картину. Достатньо настільки детально, щоб людина змогла впевнено запустити й використовувати застосунок, але без зайвої теорії та екскурсії архітектурою.
Саме тому далі в лекції ми будемо будувати README не як «вільний текст про проєкт», а як набір конкретних розділів: запуск, Usage, список команд, формат введення й виведення, приклади.
3. Найважливіший розділ «Як запустити»
Цю частину дуже легко недооцінити. Автор проєкту думає: «ну, запуск же очевидний». Користувач думає: «а що у вас вважається очевидним: Xcode? SwiftPM? Docker? Фаза Місяця?». Тому в README ми пишемо запуск так, ніби людина, яка читає, ніколи не бачила ваш проєкт.
Для нашого навчального проєкту на SwiftPM базовий запуск виглядає так:
swift run LibraryCLI
SwiftPM прямо пропонує такий стиль запуску для CLI-проєктів: «build/run/test» — основний цикл, а swift run запускає виконуваний таргет за назвою.
У README важливо не просто показати команду, а й мінімально пояснити контекст. Наприклад: «з кореня репозиторію», «потрібен Swift toolchain», «якщо команда не знайдена — перевірте, що Swift установлено».
При цьому ми не заглиблюємося в тонкощі середовища та встановлення SDK — це окрема тема. Ми просто даємо користувачеві швидкий старт.
4. Usage: робимо «міні-граматику» команд
Коли README складається лише з прикладів, користувач опиняється в ситуації «вгадай правило за трьома прикладами». Іноді це працює, але частіше перетворює використання CLI на квест. Тому майже в кожного нормального інструмента є блок Usage: один рядок, який задає форму команди.
Тут зручно мислити як парсер. Ми вже говорили про граматику команд: є команда, є аргументи, є опції. Usage — це опис того, що CLI взагалі приймає, якщо дивитися зверху вниз.
Ось розумний мінімум для LibraryCLI:
Usage:
LibraryCLI <command> [arguments]
Run:
swift run LibraryCLI <command> [arguments]
Зверніть увагу на два рівні. Перший — як команда виглядає загалом, якщо бінарник уже встановлено. Другий — як ми запускаємо її саме в нашому навчальному проєкті через SwiftPM. Цей другий варіант спирається на стандартний підхід swift run <executable>.
Невелика блок-схема: де живе Usage
Інколи допомагає візуалізація, особливо якщо ви тільки починаєте.
flowchart TD
A[Користувач відкриває README] --> B[Бачить розділ «Використання»]
B --> C[Копіює приклад команди]
C --> D[Запускає: swift run LibraryCLI ...]
D --> E[Отримує передбачуваний результат]
Сенс простий: README має скорочувати шлях від «хочу спробувати» до «вийшло».
5. Команди: як описувати список команд
Список команд — серце README для CLI. Але є тонкий момент: якщо ви просто напишете «Команди: add, remove, list», це виглядатиме як меню ресторану без опису страв. А якщо ви напишете по абзацу на кожну команду, README розростеться, і його перестануть читати.
Хороший баланс — описувати команди структуровано: назва, аргументи, що робить, приклад. Для читабельності чудово працює таблиця: вона компактна й дає «сканований» огляд.
Наприклад, на поточному етапі курсу ми можемо тримати базовий набір:
| Команда | Аргументи | Призначення | Приклад |
|---|---|---|---|
|
— | Показує довідку | |
|
— | Показує версію | |
|
|
Додає книжку до поточної сесії | |
|
— | Показує книжки | |
|
|
Шукає за назвою чи автором | |
|
|
Видаляє книжку за ідентифікатором | |
Зверніть увагу: ми не обіцяємо зберігання у файлі або JSON-експорт, тому що якщо цього не реалізовано — README почне брехати. README, який бреше, перестає бути документацією і перетворюється на фанфік.
6. Формат даних: «що таке книжка» в термінах CLI
Коли люди чують «формат даних», вони часто думають про JSON, CSV та інші дорослі речі. Але на нашому поточному етапі все простіше: формат даних — це, по суті, відповідь на два запитання.
Перше запитання звучить так: «Які поля в сутності книжки важливі для користувача команди?» Друге запитання звучить так: «Як ці поля подані у вводі й виводі, щоб це було стабільно й передбачувано?»
Нехай наша предметна модель книжки поки що мінімальна: id, title, author, year. У README ми фіксуємо, що користувач вводить title/author/year, а id генерується програмою і використовується для видалення.
Стабільний вивід: ключ-значення замість «красивостей»
Є спокуса зробити вивід «як у застосунку», з рамками, емодзі та мистецтвом ASCII. Це весело перші 30 секунд, але погано для передбачуваності. Чим простіший формат, тим легше його читати очима й парсити скриптами.
Наприклад, для list можна домовитися про такий вивід:
id=B1 title="Dune" author="Frank Herbert" year=1965
id=B2 title="Clean Code" author="Robert C. Martin" year=2008
Так, це не виставка дизайну. Зате це залізобетонний контракт. Якщо ви потім захочете покращити формат, ви хоча б будете розуміти, що ламаєте зовнішній API.
Формат введення: що ми вважаємо валідним
README має явно сказати, який ввід вважається коректним. Наприклад, year — ціле число, title і author — рядки, краще в лапках, якщо є пробіли. Якщо ваш CLI підтримує лапки в токенізації — а ми це обговорювали на парсингу — README має показати приклади саме з лапками.
І тут важлива педагогічна думка: README допомагає не тільки користувачеві, а й вам як розробнику. Він змушує формалізувати правила. Якщо правило не можна пояснити в README, значить і в коді воно теж буде «плавати».
7. Приклади команд: робимо їх копійованими і самодостатніми
Приклади команд — це те місце, де користувач найчастіше й працює. Він не читає всю документацію, він робить Copy → Paste → Enter. Отже, приклади мають бути такими, щоб цей сценарій працював.
Тому приклад має мати три якості.
Перша якість: приклад запускається «як є» — принаймні синтаксично. Друга якість: приклад показує очікуваний вивід, хай навіть частково. Третя якість: приклад використовує єдину назву команди — знову LibraryCLI.
Ось приклад блоку README — саме того контенту, який ви справді покладете в README.md:
```md
## Використання
Запуск через SwiftPM (з кореня репозиторію):
```bash
swift run LibraryCLI <command> [arguments]
```
## Приклади
Додати книжку:
```bash
swift run LibraryCLI add --title "Dune" --author "Frank Herbert" --year 1965
```
Список книжок:
```bash
swift run LibraryCLI list
```
Приклад виведення:
```text
id=B1 title="Dune" author="Frank Herbert" year=1965
```
```
Зверніть увагу: ми не зобовʼязані показувати «Building for debugging…» та інші рядки SwiftPM, тому що це шум і може відрізнятися між середовищами. Ми показуємо те, що стосується контракту нашого застосунку.
8. Коди завершення: фіксуємо «0 / не 0» без нумерології
Користувацький досвід у терміналі — це не тільки текст, а й код завершення процесу. На цьому етапі достатньо базового принципу: 0 — успіх, будь-яке ненульове значення — помилка. І цього достатньо, щоб README став чеснішим.
У README це можна описати коротко, без таблиць:
## Коди завершення
`LibraryCLI` повертає код завершення `0` у разі успіху та ненульовий код у разі помилки.
Чому без таблиці? Тому що якщо ви сьогодні вигадаєте «2 — помилка парсингу, 3 — помилка домену…», а завтра зміните архітектуру помилок, README миттєво стане сміттям. Таблиці кодів завершення мають сенс, коли схема стабілізована і закріплена в проєкті. Зараз ми тримаємося мінімуму: «успіх/помилка».
Надмірна деталізація кодів завершення потрібна лише тоді, коли схема вже стабілізована і закріплена в проєкті. Зараз тримаємося мінімуму: «успіх/помилка».
9. Як не розсинхронізувати README і help
Дуже типова проблема CLI-проєктів: README каже одне, а команда help — інше. Людина читає README, запускає help, і починається філософська дискусія: яка з двох реальностей справжня?
Щоб цього уникати, корисно зробити так, щоб help-текст був максимально близьким до README за форматом і назвами. І ще краще — щоб він жив у коді як константа або невеликий генератор, який легко оновлювати.
Давайте додамо в наш LibraryCLI невелике «джерело істини» для довідки.
Модель команд: список в одному місці
Невеликий фрагмент коду, який розширює наш застосунок і водночас допомагає README: список команд можна взяти з enum Command.
import Foundation
enum Command: String, CaseIterable {
case help, version, add, list, find, remove
}
Код короткий, читабельний, і найголовніше — якщо ви додасте нову команду, ви не забудете про неї в одному з місць: README, help або парсері.
Генерація help-тексту з єдиним неймінгом
Тепер зберемо help-текст так, щоб він повторював те, що ми пишемо в README: LibraryCLI, структуру Usage, список команд.
import Foundation
struct HelpText {
static let usage = """
Використання:
LibraryCLI <command> [arguments]
Запуск:
swift run LibraryCLI <command> [arguments]
"""
}
Якщо хтось запитає «чому рядок багаторядковий?», ви можете гордо відповісти: «тому що так краще читається». І це буде правдою.
Друк списку команд із CaseIterable
Тепер додамо функцію, яка друкує команди. Тут важливий нюанс: ми друкуємо їх стабільно і простим текстом.
import Foundation
func printCommands() {
print("Команди:")
for cmd in Command.allCases {
print(" \(cmd.rawValue)")
}
}
Якщо printCommands() викличеться, вивід буде приблизно таким:
Команди:
help
version
add
list
find
remove
І ось це вже можна копіювати в README як «офіційний список».
Міні-main: команда help
Покажемо крихітну точку входу, яка обробляє help. Ми зараз не занурюємося в повноцінний парсер — він у вас уже є в проєкті — просто демонструємо принцип.
import Foundation
let args = Array(CommandLine.arguments.dropFirst())
let cmd = args.first ?? "help"
if cmd == "help" {
print(HelpText.usage)
printCommands()
}
Так, це «наївний main», але він показує важливу ідею: help-повідомлення й список команд можна тримати поруч, і README буде простіше підтримувати.
10. Шаблон README для LibraryCLI
Нижче — приклад того, як може виглядати ваш README.md. Він спеціально зроблений «без зайвого»: короткий, але з контрактом.
```md
# LibraryCLI
Невеликий інструмент командного рядка для керування списком книжок.
## Запуск
З кореня репозиторію:
```bash
swift run LibraryCLI <command> [arguments]
```
## Використання
```text
LibraryCLI <command> [arguments]
```
## Команди
```text
help
version
add --title <string> --author <string> --year <int>
list
find --query <string>
remove --id <string>
```
## Формат виведення
`list` друкує одну книжку на рядок:
```text
id=<ID> title="<TITLE>" author="<AUTHOR>" year=<YEAR>
```
## Приклади
Додати:
```bash
swift run LibraryCLI add --title "Dune" --author "Frank Herbert" --year 1965
```
Список:
```bash
swift run LibraryCLI list
```
Приклад виведення:
```text
id=B1 title="Dune" author="Frank Herbert" year=1965
```
## Коди завершення
```text
`0` означає успіх. Будь-який ненульовий код означає помилку.
```
```
Ви можете розширювати цей README, але важливо тримати структуру: запуск → Usage → команди → формат → приклади. Тоді документ читається як інструкція, а не як потік свідомості.
11. Типові помилки під час написання README для CLI
Помилка №1: README і реальність розходяться.
Найгірша ситуація — коли README обіцяє можливість, якої немає, або показує приклад команди, яка вже не парситься. Це відбувається непомітно: ви змінили парсер, додали новий обовʼязковий аргумент, а README не оновили. За тиждень ви самі будете тим самим користувачем, який запускає приклад із README й отримує помилку, а потім підозрює, що проєкт «зламався сам». На практиці рятує тільки дисципліна: змінюємо поведінку CLI — одразу правимо README й help-текст.
Помилка №2: приклади не можна копіювати.
Дуже часта дрібниця: у прикладі написано LibraryCli замість LibraryCLI, або приклад передбачає, що ви вже нібито зібрали бінарник, але не пояснює як. Користувач копіює, отримує «command not found», і на цьому знайомство закінчується. Тому приклади краще писати у вигляді команд swift run LibraryCLI ..., тому що це стандартний шлях запуску executable у SwiftPM.
Помилка №3: надто «красивий», але нестабільний вивід.
Якщо вивід оформлено як «табличка із символів», його приємно дивитися, але важко використовувати і майже неможливо гарантувати стабільність. У результаті ви випадково змінюєте кількість пробілів — і ламаєте всім парсинг або власні тести, якщо робите їх через порівняння рядків. Стабільний plain text із ключами (id=... title=...) виглядає нудно, зате працює роками.
Помилка №4: немає опису формату даних, лише слова «додає книжку».
Фраза «додає книжку» не відповідає на запитання «які поля?», «як виглядає id?», «як вводити рік?», «чи можна пробіли?». У результаті користувач починає експериментувати, а CLI перетворюється на генератор сюрпризів. Навіть один маленький розділ «Формат виведення» різко зменшує кількість запитань і помилок.
Помилка №5: README перетворюється на опис внутрішностей проєкту.
Інколи хочеться розповісти про targets, шари, протоколи, Repository, Service, DI — і це справді цікаво, особливо нам із вами. Але README для користувача CLI має описувати використання, а не влаштування. Щойно README починає пояснювати архітектуру замість команд і форматів, він перестає виконувати свою роботу: допомагати запустити й застосувати інструмент.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ