1. Імена функцій у Swift: base name і мітки
Якщо ви тільки починаєте, може здаватися, що головне — щоб застосунок працював: додали книгу, видалили книгу, вивели список — і все, ура. Але в проєкті, який трохи більший, зʼясовується: ви значно частіше читаєте код, ніж пишете його. А API — це саме те, що ви читаєте в call site, тобто в місці виклику.
Уявіть, що ваш модуль Domain — це кавомашина. Можна зробити 17 кнопок без підписів і додати інструкцію на 40 сторінок. А можна зробити три великі кнопки: «Espresso», «Americano», «Milk». В обох випадках кава буде. Але в другому варіанті шанс, що користувач випадково зварить «латте без чашки», помітно менший. У коді роль підписів виконують імена функцій та мітки.
У мовах на кшталт Java/C# багато хто звик до думки: «імʼя методу — це слово перед дужками». У Swift усе дещо складніше: argument labels є частиною імені. Історично це підтримує дві важливі речі: читабельність виклику та можливість мати кілька перевантажень з однаковим base name, але різними labels (наприклад, як у UIKit‑подібних API). Цю ідею прямо обговорюють у матеріалах Swift Evolution щодо compound names і розрізнення перевантажень за мітками аргументів.
Labels — частина імені функції
Тобто, якщо дуже спростити, функція:
func move(from: Int, to: Int)
має імʼя (у термінах Swift) move(from:to:), а не просто move.
Це виявляється напрочуд корисним у реальному житті. Наприклад, ви можете мати кілька методів insertSubview, які відрізняються за змістом саме через labels — «вставити за індексом», «вставити над», «вставити під». Такий приклад якраз наводиться в обговоренні найменування функцій із labels.
Call site як речення
Коли кажуть «читається як речення», зазвичай мають на увазі таке: якщо ви дивитеся на рядок коду, вам не треба лізти в реалізацію, щоб зрозуміти сенс параметрів. Swift прямо заохочує цей стиль: багато рішень мови й стандартної бібліотеки історично приводили до того, що labels стали повноцінним елементом читабельності.
Показовий момент: у сучасному Swift немає «особливого випадку» для першого параметра, і мітка першого аргументу поводиться так само, як і решта. Це результат уніфікації поведінки labels.
Порівняймо дві версії однієї й тієї самої дії в нашому CLI‑проєкті LibraryCLI.
Приклад: невдалий call site без міток
struct Library {
mutating func addBook(_ book: Book) {
// ...
}
}
Виклик буде таким:
library.addBook(book)
Начебто нормально. Але проблема виникає, коли параметрів стає два-три або коли типи однакові (наприклад, String, String). Тоді без міток легко переплутати аргументи.
Приклад: краще — сенс читається з виклику
struct Library {
mutating func add(_ book: Book) {
// ...
}
mutating func removeBook(withID id: BookID) {
// ...
}
}
Виклики:
library.add(book)
library.removeBook(withID: id)
Тут withID — це маленька підказка для мозку: зараз буде ID, а не назва, не індекс і не щось інше.
Коли приховувати мітку через _
Дуже хочеться зробити API «коротшим» і почати всюди писати _, прибираючи зовнішні мітки. Але в Swift _ — це не «зробімо гарно», а «ми впевнені, що сенс аргументу очевидний і без підпису».
Просте правило для новачка: якщо ви вагаєтеся, чи потрібна мітка, — найімовірніше, потрібна. Загалом Swift рухається в бік того, щоб мітки допомагали читачеві, а не заважали йому. Це видно і в підході до перекладу Objective‑C API на Swift‑стиль, де активно додають і нормалізують мітки та прибирають зайві слова.
Приклад: _ доречний, коли аргумент «головний»
struct BookID {
let rawValue: String
}
func findBook(_ id: BookID) {
print("Шукаємо \(id.rawValue)")
// Шукаємо 123
}
Тут _ можна виправдати: у контексті findBook(...) майже завжди і так зрозуміло, що аргумент — це ID.
Приклад: мітки рятують, коли параметри легко переплутати
func move(from startIndex: Int, to endIndex: Int) {
print("move from \(startIndex) to \(endIndex)")
// перемістити з 2 до 10
}
move(from: 2, to: 10)
Якщо прибрати мітки, ви отримаєте класичне: «а що було першим — звідки чи куди?». А такі помилки особливо неприємні тим, що код компілюється і навіть працює… але робить не те.
Прийменники в мітках: from, to, in, at, with, for
Swift любить, коли мітки виглядають як прийменники й складаються у фразу. Ба більше, мова навіть спеціально розвʼязувала проблему, що деякі вдалі прийменники збігаються з ключовими словами (наприклад, in). У Swift Evolution окремо обговорювали, що такі ключові слова варто дозволити як argument labels, бо вони справді підсилюють виразність API.
Це означає, що «людяний» API може виглядати так:
indexOf(value, in: collection)
І це не «дивина», а буквально те, чого Swift прагне, — щоб інтерфейс читався як речення.
Для нашого LibraryCLI це означає на практиці:
- searchBooks(containing:) — «шукай книги, що містять рядок»
- removeBook(withID:) — «видали книгу з таким ID»
- updateBook(withID:to:) — «онови книгу з ID на нове значення»
Сенс міток тут — показати роль аргументу, а не його тип.
3. Неймінг за ефектом і за змістом
Коли ви проєктуєте API, ви не лише називаєте функцію — ви даєте обіцянку: «ця операція робить ось це». Якщо назвати метод process() або handle(), ви обіцяєте фактично нічого. Компілятор не заперечує, але користувачі вашого коду страждатимуть мовчки, а потім — голосно.
Дієслова для дій і «не обіцяй зайвого»
Дуже практичний прийом — розрізняти операції за ефектом.
Якщо метод змінює стан, імʼя зазвичай дієслівне: add, remove, update. Якщо метод повертає обчислене значення, імʼя часто має вигляд іменника або прикметника: count, isEmpty, title.
У стандартній бібліотеці й довкола неї є багато прикладів, де неймінг підштовхує до правильного прочитання і правильного очікування. Історично Swift також намагався прибрати зайві слова, що повторюють тип, щоб залишався сенс, а не тавтологія.
Приклад: мутуюча операція в Library
struct Book {
let id: String
let title: String
}
struct Library {
private(set) var books: [Book] = []
mutating func add(_ book: Book) {
books.append(book)
}
}
Зверніть увагу: add — коротко і дієслівно. addBook теж можна, але якщо контекст уже «Library», слово Book іноді стає зайвим. Ми ще повернемося до omit needless words.
Булеві властивості та методи: is…, has…, can…
Булеві значення — особлива пастка для новачка: true/false самі по собі нічого не пояснюють; усе вирішує назва.
Swift‑стиль зазвичай такий: булеві властивості читаються як твердження про сам обʼєкт: isEmpty, isValid, hasPrefix, canUndo. Навіть під час перекладу API з Objective‑C у Swift окремо підкреслювали, що булевим властивостям логічно додавати is, щоб вони читалися як твердження.
Приклад: невдалий bool, який не читається
struct LibraryCLIConfig {
let verbose: Bool
}
func verbose(_ config: LibraryCLIConfig) -> Bool {
config.verbose
}
Виклик if verbose(config) { ... } виглядає дивно: «якщо verbose(config)… що саме?»
Приклад: краще — читається як фраза
struct LibraryCLIConfig {
let isVerbose: Bool
}
func isVerbose(_ config: LibraryCLIConfig) -> Bool {
config.isVerbose
}
Тепер if isVerbose(config) { ... } читається майже як англійське речення: «if is verbose».
Omit needless words: прибираємо слова, що повторюють контекст
Одна з найчастіших помилок — назвати все максимально докладно: addBookToLibrary(book: Book). Здається, що так зрозуміліше. Але на практиці це перетворюється на шум.
Swift‑підхід часто такий: якщо контекст уже зрозумілий із типу або місця, слово можна прибрати. У документах про перетворення API з Objective‑C у Swift показано, як із назв прибирають шматки, що повторюють тип, і як результат стає ближчим до читабельності за змістом, а не за тавтологією.
Порівняймо на прикладах, близьких до нашого LibraryCLI.
| Погано (зайве) | Краще (зміст) | Чому |
|---|---|---|
|
або |
«Library» уже є в типі отримувача |
|
|
«ByID» — зайве, withID уже показує роль |
|
|
«FromLibrary» зайве, ми й так у Library |
Важливо: omit needless words не означає «робимо все коротким». Воно означає «прибираємо те, що не додає сенсу».
4. Перевантаження і посилання на методи: роль міток
Swift дає змогу мати кілька методів з однаковим base name, які відрізняються мітками. Це зручно, але вимагає дисципліни: мітки мають справді відображати різні змісти, а не бути «синонімами заради синонімів».
Перевантаження з одним base name
В обговоренні generalized naming наводиться класична ситуація: один base name (insertSubview), три різні дії, і саме мітки роблять API однозначним.
Перенесімо ідею в Library.
Приклад: кілька способів пошуку, розрізняємо за мітками
struct Library {
private(set) var books: [Book] = []
func book(withID id: String) -> Book? {
books.first { $0.id == id }
}
func books(containing titlePart: String) -> [Book] {
books.filter { $0.title.contains(titlePart) }
}
}
Виклики виглядатимуть так:
let one = library.book(withID: "42")
let many = library.books(containing: "Swift")
Одне й те саме слово book/books, але різні форми: один обʼєкт або багато обʼєктів, і це видно прямо із сигнатури.
Коли метод передається як значення
Це здається суто теорією, доки ви не почнете активно використовувати замикання та функції як значення. У Swift буває так, що кілька методів мають один base name, і без міток посилання на метод стає неоднозначним.
У Swift Evolution про generalized naming це описано прямо: якщо є три insertSubview, то someView.insertSubview — неоднозначно, і Swift увів або обговорював синтаксис, що дає змогу посилатися на метод разом із мітками, на кшталт insertSubview(_:at:).
Для нашого курсу це корисно як думка: мітки — це не «косметика», а частина імені, яка інколи справді впливає на те, чи зможете ви зручно використовувати API в більш функціональному стилі.
5. Міні‑рефакторинг LibraryCLI: робимо API «розмовним»
Зараз зведемо кілька невеликих частин в одну лінію. Ми не будуємо новий модуль і не занурюємося в архітектуру; наша мета — показати, як неймінг змінює враження від коду, хоча логіка майже та сама.
Крок 1: доменні типи зі зрозумілими іменами
struct BookID: Hashable {
let rawValue: String
}
struct Book {
let id: BookID
let title: String
}
BookID говорить сам за себе. rawValue — нормальна конвенція для «обгорток» навколо примітивів.
Крок 2: Library з API, який читається у виклику
struct Library {
private(set) var books: [Book] = []
mutating func add(_ book: Book) {
books.append(book)
}
func book(withID id: BookID) -> Book? {
books.first { $0.id == id }
}
}
Виклики виглядають природно:
library.add(book)
let found = library.book(withID: id)
Зверніть увагу: ми використовуємо withID, а не byID або id:. Це не «єдине правильне» слово, але воно добре працює як частина фрази.
Крок 3: команди CLI теж виграють від неймінгу
enum Command {
case add(title: String)
case find(id: String)
}
Так, це не мітки у виклику функції, але принцип той самий: add(title:) читається краще, ніж add(String) — особливо коли в команди є кілька параметрів.
6. Типові помилки в API-дизайні Swift
Помилка № 1: «Зроблю коротше і приберу всі мітки через _».
Спочатку здається, що без міток код компактніший. Але щойно зʼявляється два параметри одного типу, ви починаєте плутати аргументи місцями. Найнеприємніше, що компілятор часто не може вас урятувати: String і String виглядають однаково. Добрий стиль Swift якраз цінує мітки, і історично мова рухалася до уніфікації поведінки міток для всіх параметрів, включно з першим.
Помилка № 2: «Назву метод максимально докладно: addBookToLibrary».
Таке імʼя створює ілюзію зрозумілості, але насправді перетворює виклик на тавтологію: library.addBookToLibrary(book). Swift‑підхід часто протилежний: прибирати слова, що повторюють інформацію, уже відому з контексту типу. Такий стиль особливо видно на прикладах «очищення» імен під час перетворення Objective‑C API на Swift‑манеру.
Помилка № 3: булеві значення названі так, що true/false незрозумілі.
Якщо у вас flag, mode, enabled, ok — це вже привід насторожитися (вибачте за каламбур). Виклик if config.verbose { ... } ще терпимий, але коли булеве значення заховане за функцією verbose(config), читачеві доводиться розшифровувати, що саме означає true. У Swift часто використовують is/has/can, і навіть під час автоматичного перекладу API в Swift зазначали корисність префікса is для булевих властивостей.
Помилка № 4: мітки підібрані як «синоніми», а не як різні змісти.
Якщо ви робите removeBook(by:), removeBook(with:), removeBook(using:) — це не виразність, а плутанина. Перевантаження за мітками в Swift справді існують і корисні, але вони мають відображати різні смислові операції, як у класичному прикладі з одним base name і кількома варіантами поведінки, що розрізняються за мітками.
Помилка № 5: «універсальна функція» з назвою handle/process/doStuff.
Це найчастіша причина, чому проєкт за місяць стає нечитабельним: ви відкриваєте файл і бачите service.handle(...), manager.process(...), controller.do(...). Такі імена не закріплюють контракт. Набагато корисніше витратити ще 30 секунд і назвати дію за змістом: add, remove, search, parse, format. Так, це нудно. Але нудний код — часто найнадійніший (і це той вид нудьги, за який платять зарплату).
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ