Чи завжди слід знати, що робить API, просто дивлячись на код?


20

Нещодавно я розробляв власний API, і з цим інвестував інтерес до дизайну API, я був зацікавлений, як я можу вдосконалити дизайн API.

Один аспект, який з’явився кілька разів, - це (не користувачі мого API, а під час мого спостереження за темою): слід знати лише дивлячись на код, що викликає API, що він робить .

Наприклад, дивіться цю дискусію на GitHub для дискурсного репо, вона виглядає приблизно так:

foo.update_pinned(true, true);

Просто дивлячись на код (не знаючи назви параметрів, документації тощо), ви не можете здогадатися, що він буде робити - що означає 2-й аргумент? Запропоноване вдосконалення - мати щось на кшталт:

foo.pin()
foo.unpin()
foo.pin_globally()

І це все вияснює (я думаю, другий аргумент - чи слід чітко фіксувати глобальний процес), і я згоден у цьому випадку, пізніше це, безумовно, буде вдосконаленням.

Однак я вважаю, що можуть бути випадки, коли методи встановлення різних, але логічно пов'язаних станів було б краще піддаватися як один виклик методу, а не окремий, навіть якщо ви не знаєте, що це робить, просто подивившись на код . (Тож вам доведеться вдатися до перегляду імен параметрів та документації, щоб дізнатись - що особисто я завжди робив би незалежно від того, що, якщо я не знайомий з API).

Наприклад, я виставляю один метод SetVisibility(bool, string, bool)на FalconPeer і я визнаю, що просто дивлюся на рядок:

falconPeer.SetVisibility(true, "aerw3", true);

Ви б не мали поняття, що це робить. Це встановлення 3 різних значень, які керують "видимістю" falconPeerв логічному сенсі: приймайте запити на приєднання, лише з паролем та відповідайте на запити на відкриття. Розбиття цього на 3 виклики методу може призвести до того, що користувач API встановить один аспект "видимості", забувши встановити інших, про що я змушую їх думати, лише піддаючи одному методу встановлення всіх аспектів "видимості" . Крім того, коли користувач хоче змінити один аспект, він майже завжди захоче змінити інший аспект і тепер може це зробити за один дзвінок.


13
Саме це є причиною того, що деякі мови назвали параметри (іноді навіть примусові названі параметри). Наприклад , ви могли група багато налаштувань в простому updateметоді: foo.update(pinned=true, globally=true). Або: foo.update_pinned(true, globally=true). Таким чином, відповідь на ваше запитання повинна враховувати також мовні особливості, оскільки хороший API для мови X може не бути корисним для мови Y та viceversa.
Бакуріу

Домовились - перестаньмо використовувати булеви :)
Вересень

2
Це відоме як "булева пастка"
user11153

@Bakuriu Навіть C має перерахунки, навіть якщо вони маскуються цілими числами. Я не думаю, що існує реальна мова світу, де булеви є гарним дизайном API.
Doval

1
@Doval Я не розумію, що ти намагаєшся сказати. Я використовував булеви в тій ситуації просто тому, що ОП використовував їх, але мій пункт абсолютно не пов'язаний із значенням, що передається. Наприклад: setSize(10, 20)не читайте , як setSize(width=10, height=20)або random(distribution='gaussian', mean=0.5, deviation=1). У мовах із примусовими названими параметрами булеви можуть передавати точно такий же об'єм інформації, як і за допомогою переписок / названих констант, тому вони можуть бути хорошими в API.
Бакуріу

Відповіді:


27

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

Ви можете використовувати переліки:

falconPeer.SetVisibility(JoinRequestOptions.Accept, "aerw3", DiscoveryRequestOptions.Reply);

Або навіть перелічення прапорів (якщо ваша мова це підтримує):

falconPeer.SetVisibility(VisibilityOptions.AcceptJoinRequests | VisibilityOptions.ReplyToDiscoveryRequests, "aerw3");

Або ви можете використовувати об’єкт параметра :

var options = new VisibilityOptions();
options.AcceptJoinRequests = true;
options.ReplyToDiscoveryRequest = true;
options.Password="aerw3";
falconPeer.SetVisibility(options);

Шаблон об’єкта параметра дає вам кілька інших переваг, які можуть вам бути корисними. Це дозволяє легко обходити і серіалізувати набір параметрів, а також ви можете легко надати не визначені параметри значення за замовчуванням.

Або ви могли просто використовувати булеві параметри. Microsoft, здається, робить це весь час за допомогою .NET Framework API, тому ви можете просто знизати плечима і сказати, "якщо для них це досить добре, для мене це досить добре".


У шаблоні "Об'єкт параметрів" все ще є проблема, в якій заявлена ​​ОП: " Розбиття цього на 3 виклики методу може призвести до того, що користувач API може встановити один аспект" видимості ", забувши встановити інші ".
Лоде

Щоправда, але я думаю, що це покращує ситуацію (якщо не ідеальну). Якщо користувач змушений інстанціювати та передавати об’єкт VisibilityOptions, він може (при будь-якій удачі) нагадати їм, що на об’єкті VisibilityOptions є інші властивості, які вони можуть хотіти встановити. При підході до трьох методів викликів все, що їм потрібно нагадати, - це коментарі до методів, які вони викликають.
БенМ

6

Очевидно, що з правила завжди є винятки, але, як ви добре пояснили, у наявності читабельного API є певні переваги. Булеві аргументи особливо набридли, оскільки 1) вони не виявляють наміру і 2) вони означають, що ви викликаєте функцію, де у вас насправді повинно бути два, тому що різні речі трапляться залежно від булевого прапора.

Головне питання швидше: скільки зусиль ви хочете вкласти, щоб зробити ваш API більш зрозумілим? Чим більше це назовні, тим більше зусиль можна легко виправдати. Якщо це API, який використовується лише одним іншим підрозділом, то це не така вже й велика угода. Якщо ви говорите про API REST, де ви плануєте дозволити йому втратити весь світ, то можете також вкласти більше зусиль, щоб зробити його більш зрозумілим.

Що стосується вашого прикладу, є просте рішення: Мабуть, у вашому випадку видимість - це не просто справжня чи помилкова річ. Натомість у вас є цілий набір речей, які ви вважаєте "видимістю". Одним із варіантів рішення може бути введення чогось типу Visibilityкласу, що охоплює всі ці види видимості. Якщо ви додатково застосуєте шаблон для Builder для їх створення, ви можете створити такий код:

Visibility visibility = Visibility.builder()
  .acceptJoinRequests()
  .withPassword("aerw3")
  .replyToDiscoveryRequests()
  .build();
falconPeer.setVisibility(visibility);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.