Названі параметри роблять код простішим для читання, складніше писати
Коли я читаю фрагмент коду, названі параметри можуть ввести контекст, який полегшує розуміння коду. Розглянемо, наприклад , цей конструктор: Color(1, 102, 205, 170)
. Що це означає? Дійсно, Color(alpha: 1, red: 102, green: 205, blue: 170)
читати було б набагато простіше. Але на жаль, Укладач каже "ні" - він хоче Color(a: 1, r: 102, g: 205, b: 170)
. Під час написання коду з використанням названих параметрів ви витрачаєте зайву кількість часу на пошук точних імен - простіше забути точні назви деяких параметрів, ніж забути їх порядок.
Це колись мене покусало при використанні DateTime
API, який мав два класи братів та сестер за точками та тривалістю з майже однаковими інтерфейсами. Хоча DateTime->new(...)
прийнято second => 30
аргумент, DateTime::Duration->new(...)
розшук seconds => 30
та подібне для інших підрозділів. Так, це абсолютно має сенс, але це показало мені, що названі параметри - простота використання.
Погані імена навіть не роблять його легше читати
Інший приклад того, як названі параметри можуть бути поганими - це, мабуть, мова R. Цей фрагмент коду створює графік даних:
plot(plotdata$n, plotdata$mu, type="p", pch=17, lty=1, bty="n", ann=FALSE, axes=FALSE)
Ви бачите два позиційні аргументи для рядків даних x і y , а потім список іменованих параметрів. Існує ще багато варіантів із типовими настройками, і перераховані лише ті, які за замовчуванням я хотів змінити або явно вказати. Після того, як ми ігноруємо, що цей код використовує магічні числа, і може отримати користь від використання enums (якщо R мав!), Проблема полягає в тому, що багато з цих імен параметрів є досить нерозбірливими.
pch
насправді є сюжетним символом, гліфом, який буде намальовано для кожної точки даних. 17
це порожнє коло чи щось подібне.
lty
- тип рядка. Ось 1
суцільна лінія.
bty
тип коробки. Встановлюючи це, щоб "n"
уникнути намалювання поля навколо ділянки.
ann
контролює появу конспектів осі.
Для тих, хто не знає, що означає кожна абревіатура, ці варіанти досить заплутані. Це також виявляє, чому R використовує такі мітки: Не як код самодокументування, а (будучи динамічно набраною мовою) як ключі для відображення значень у їх правильних змінних.
Властивості параметрів та підписів
Підписи функцій можуть мати такі властивості:
- Аргументи можна впорядкувати чи не упорядкувати,
- названий або неназваний,
- обов'язкові або необов’язкові.
- Підписи також можуть бути перевантажені за розміром або типом,
- і може мати невизначений розмір з вараггами.
Різні мови приземляються за різними координатами цієї системи. В C аргументи впорядковані, безіменні, завжди потрібні і можуть бути вараггами. У Java ситуація схожа, за винятком того, що підписи можуть бути перевантажені. У Цілі C підписи впорядковують, називають, вимагають, і їх не можна перевантажувати, оскільки це лише синтаксичний цукор навколо С.
Мови, що динамічно набираються, з вараггами (інтерфейси командного рядка, Perl,…) можуть імітувати необов'язкові параметри з назвою. Мови з перевантаженням розміру підпису мають щось на зразок позиційних необов'язкових параметрів.
Як не реалізувати іменовані параметри
Мислюючи названі параметри, ми зазвичай припускаємо названі, необов'язкові, не упорядковані параметри. Реалізувати їх складно.
Необов’язкові параметри можуть мати значення за замовчуванням. Вони повинні бути визначені функцією, що викликається, і не повинні компілюватися в код виклику. В іншому випадку параметри за замовчуванням не можуть бути оновлені без перекомпіляції всього залежного коду.
Тепер важливим питанням є те, як аргументи насправді передаються функції. З упорядкованими параметрами, аргументи можна передавати в регістр або в їх притаманному порядку на стеку. Коли ми на мить виключаємо регістри, проблема полягає в тому, як поставити невпорядковані необов’язкові аргументи на стек.
Для цього нам потрібен певний порядок щодо необов'язкових аргументів. Що робити, якщо код декларації буде змінено? Оскільки порядок не має значення, переупорядкування в декларації функції не повинно змінювати положення значень на стеку. Ми також повинні врахувати, чи можливо додавання нового необов’язкового параметра. З точки зору користувачів це виглядає так, оскільки код, який раніше не використовував цей параметр, все одно повинен працювати з новим параметром. Таким чином, це виключає замовлення, як-от використання порядку в декларації або використання алфавітного порядку.
Розглянемо це також у світлі підтипу та принципу заміщення Ліскова - у складеному висновку ті ж інструкції повинні мати можливість викликати метод на підтипі з можливо новими названими параметрами та на супертипі.
Можливі втілення
Якщо ми не можемо мати остаточного порядку, то нам потрібна якась не упорядкована структура даних.
Найпростіша реалізація - це просто передати ім’я параметрів разом зі значеннями. Ось як імітуються названі парами в Perl або за допомогою інструментів командного рядка. Це вирішує всі проблеми з розширенням, згадані вище, але може бути величезною марною витратою місця - не варіантом у критичному для продуктивності коді. Крім того, опрацювати ці названі парами зараз набагато складніше, ніж просто вискакувати значення з стека.
Насправді вимоги до простору можна зменшити за допомогою об'єднання рядків, що може зменшити пізніші порівняння рядків до порівнянь вказівників (за винятком випадків, коли не можна гарантувати, що статичні рядки насправді об'єднані, і в цьому випадку два рядки доведеться порівнювати в докладно).
Натомість ми могли б також передати розумну структуру даних, яка працює як словник названих аргументів. Це з дешевого боку, тому що набір клавіш статично відомий у місці виклику. Це дозволить створити ідеальну хеш-функцію або прорахувати трійку. Службовому листу все ще доведеться перевірити наявність усіх можливих назв параметрів, що є дещо дорогим. Щось подібне використовує Python.
Тож у більшості випадків це занадто дорого
Якщо функція з названими параметрами має бути належним чином розширюється, остаточного впорядкування не можна припустити. Тож існує лише два рішення:
- Зробіть порядок названих парам частин підпису та забороніть пізніші зміни. Це корисно для самодокументування коду, але не допомагає з необов'язковими аргументами.
- Передайте структурі даних ключ-значення запитувачу, який потім повинен отримати корисну інформацію. Це дуже дорого в порівнянні, і зазвичай це спостерігається лише в мовах сценаріїв без акценту на продуктивність.
Інші підводні камені
Імена змінних у оголошенні функції зазвичай мають деяке внутрішнє значення і не є частиною інтерфейсу - навіть якщо багато інструментів документації все одно їх показуватимуть. У багатьох випадках вам потрібні різні імена для внутрішньої змінної та відповідного іменного аргументу. Мови, які не дозволяють вибирати зовні видимі імена названого параметра, не отримують великої кількості з них, якщо ім'я змінної не використовується з урахуванням контексту виклику.
Проблема з емуляціями названих аргументів - відсутність статичної перевірки на стороні виклику. Це особливо легко забути, передаючи словник аргументів (дивлячись на вас, Python). Це важливо , тому що проходження словника є загальним обхідним шляхом, наприклад , в JavaScript: foo({bar: "baz", qux: 42})
. Тут ні типи значень, ні наявність чи відсутність певних імен неможливо перевірити статично.
Емуляція іменованих параметрів (на статично типових мовах)
Просто використання рядків як ключів, а будь-який об’єкт як значення не дуже корисний при наявності перевірки статичного типу. Однак названі аргументи можуть бути імітовані структурами або об'єктними літералами:
// Java
static abstract class Arguments {
public String bar = "default";
public int qux = 0;
}
void foo(Arguments args) {
...
}
/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});