Чому слід уникати операторів збільшення ("++") та скорочення ("-") в JavaScript?


357

Однією з порад щодо інструменту jslint є:

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

Я знаю, що подібні конструкції PHP $foo[$bar++]можуть легко спричинити помилки за одним, але я не міг придумати кращий спосіб керувати циклом, ніж a while( a < 10 ) do { /* foo */ a++; }або for (var i=0; i<10; i++) { /* foo */ }.

Чи підкреслює їх jslint через те, що є деякі подібні мови, яким не вистачає синтаксису " ++" і " --", або обробляти його по-різному, чи є інші обґрунтування для уникнення " ++" і " --", які мені можуть бути відсутні?


59
Тому слід робити масив [index = index + 1] замість масиву [++ index] (якщо перший навіть дозволений!). Яке навантаження копати.
— Лоуренс Дол,

34
Я не бачив Крокфорда робити показник = індекс + 1. Я бачив, як він робив індекс + = 1. Я думаю, що це розумна альтернатива. І це приємно, коли ти хочеш змінити крок на щось, окрім 1.
— Nosredna

124
Особисто я не великий фанат Крокфорда. Він, здається, вважає все, що коли-небудь спричиняло помилку в його коді, як зло.
— Паоло Бергантіно

38
У JavaScript ви повинні вважати кожну помилку якоюсь злою, оскільки немає офіційної документації, не існує постачальника сертифікатів, а також ви не навчаєтесь JS належним чином в університеті. Crockford та Firebug заповнили ці діри в освіті JavaScript.
— stevek

40
++не викликає помилок. Використання ++"хитромудрих" способів може призвести до помилок, особливо якщо кодова база підтримує більше однієї людини, але це не проблема з оператором, це проблема з програмістом. Я не навчався JS в університеті (тому що він ще не існував), але що? Я робив C, що, звичайно, мав ++спочатку, але це також отримує "так що?" Я не ходив до університету, щоб вивчити певну мову, я пішов вивчати хороші практики програмування, які я можу застосувати до будь-якої мови.
— nnnnnn

Відповіді:


408

На мій погляд, завжди використовувати ++ і - самостійно в одному рядку, як у:

i++;
array[i] = foo;

замість

array[++i] = foo;

Що-небудь поза цим може бентежити деяких програмістів і, на мою думку, просто не варто. Для циклів це виняток, оскільки використання оператора приросту є ідіоматичним і, таким чином, завжди зрозумілим.


3
Так, це я теж роблю. Важко помилитися, а іншим легше зрозуміти, що ти робиш.
— Носредна

65
Це, здається, стає в основі проблеми і моєї розгубленості. Використовується поодинці, i ++; є кристально чистим. Щоразу, коли ви додасте інший код навколо цього базового вираження, читабельність та чіткість починають страждати.
— artlung

2
Погодьтеся, але це стосується не тільки JavaScript
— Tobias

5
Ви не говорите, чи пишете ви C або C ++. Можливо, ви повинні використовувати оператор збільшення префікса в C ++, якщо iзмінна пізніше стане складовим класом, і в цьому випадку вам буде непотрібно створювати тимчасовий об'єкт. Мені здається, що оператор Postfix є більш естетично приємним.
— mc0e

2
Я віддаю перевагу 1-рядковій версії. Це нагадує операцію стека. push є масив [++ i] = foo, pop is bar = масив [i--]. (Але з масивами на основі 0, масив [i ++] = foo та bar = array [- i] виглядає все ще краще). Інша справа, я б ненавиджу це, якби хтось додав додатковий код між i ++; і масив [i] = foo ;.
— Флоріан Ф

257

Мене відверто бентежить ця порада. Частина мене замислюється, чи не пов’язано це з відсутністю досвіду (сприйманого чи фактичного) з кодерами JavaScript.

Я можу бачити, як хтось, що просто "зломує" якийсь зразок коду, може зробити невинну помилку з ++ та -, але я не бачу, чому досвідчений професіонал уникне їх.


7
Хороший момент Джон Б. Тому це мене так турбує. Крокфорд - це кар'єрний програміст (варті десятиліть), який розмовляє різними мовами протягом десятиліть і працює з JavaScript в основному з моменту його створення. Я не думаю, що його порада походить від "Я лінивий і неуважний недосвідчений хакер" - тому я намагаюся зрозуміти, звідки вона береться.
— artlung

14
@artlung Можливо, це більше стосується "ледачого та неуважного недосвідченого хакера", ймовірно, зіпсується з цим кодом, і я не хочу його плутати.
— Кріс Кадмор

9
Я повністю не згоден з вашою відповіддю. На мою думку, справа не в тому, "я достатньо досвідчений, щоб використовувати його?" - але "Ясніше чи ні?" Якщо всередині циклу a для циклу або поодинці це суто ясно - всі зрозуміють, що це не шкодить. Проблема виникає тоді, коли викладений ++більш згорнутий вираз, у якому ++ x відрізняється від x ++, внаслідок чого щось не легко прочитати. Крокфордська ідея не про те, "чи можу я це зробити?" йдеться про "як я можу уникнути помилок?"
— ArtoAle

1
Це особиста перевага, в якій точності ясність для вас важливіша, ніж компактність. Це виходить за межі достатнього досвіду, щоб зрозуміти код. Дивіться інші відповіді на прикладах, коли це більше клопоту, ніж варто.
— Frug

1
Я цілком погоджуюся. Навіщо ганьбити себе тим, що не використовуєте частину мови? Я знаходжу вагомі причини для використання як пост-, так і попереднього збільшення та зменшення. Для мене такий рядок зрозумілий і стислий ... if (--imagesLoading === 0) start (); Якщо ви вважаєте, що ваш код незрозумілий, використовуйте коментарі!
— xtempore

69

У C є історія таких дій, як:

while (*a++ = *b++);

скопіювати рядок, можливо, це джерело надмірної хитрості, про яку йдеться.

І завжди виникає питання про що

++i = i++;

або

i = i++ + ++i;

насправді так. Він визначений на деяких мовах, а в інших немає гарантії того, що станеться.

Ці приклади осторонь, я не думаю, що є щось ідіоматичне, ніж цикл, який використовується ++для збільшення. У деяких випадках ви можете втекти за допомогою петлі передбачення або певного циклу, який перевіряв інші умови. Але перекручувати код, щоб спробувати уникнути використання приросту, смішно.


104
i = i ++ + ++ i; змусив мене трохи боліти очі - :)
— Hugoware

3
Мій також. Навіть якщо вони не були однаковою змінною, скажіть, у мене було x = a ++ + ++ b; - це поєднання миттєво ускладнює х, а і б важче утримувати в моїй голові. тепер, якби кожен був окремими висловлюваннями в окремих рядках, як у - a ++; ++ b; x = a + b; це було б легше зрозуміти з першого погляду.
— artlung

10
На жаль, (a ++; b ++; x = a = b) не є тим самим значенням, що (a ++ + ++ b).
— Thomas L Holaday

8
@Marius: x = a+++b-> x = (a++)+b-> x = a + b; a++. Токенізер жадібний.
— Callum Rogers

14
Для додаткової безпеки роботи видаліть пробіли в останньому прикладі.
— mc0e

48

Якщо ви прочитаєте JavaScript The Good Parts, ви побачите, що заміна Крокфорда на i ++ в циклі a for є i + = 1 (не i = i + 1). Це досить чисто і читабельно, і з меншою ймовірністю перетворюється на щось "складне".

Крокфорд зробив заборону автоматичного посилення та автокредитування опцією в jsLint. Ви вибираєте, чи слід дотримуватися поради

Моє особисте правило - не робити нічого в поєднанні з автоматичним посиленням або автоматичним посиленням.

Я дізнався з багаторічного досвіду роботи в C, що я не отримую перевищення буфера (або індекс масиву поза межами), якщо я продовжую використовувати це просто. Але я виявив, що я отримую перевищення буфера, якщо потрапляю в "надмірно складну" практику робити інші речі в тому ж самому заяві.

Тож, для моїх власних правил, використання i ++ як приросту в циклі for є прекрасним.


Відмінний пункт про те, що варіант "плюс" є саме цим, варіантом. Виходячи з ваших та інших відповідей, я думаю, я отримую відчуття, що ++ сама по собі або for-петля самі по собі не є заплутаними. Друге, що ви поєднуєте цей ++ в інший вислів, який ви твердите, стає складніше читати та розуміти в контексті.
— artlung

6
кожен отримує переповнення буфера. Навіть OpenSSH з їх відкритим кодом та їх кількома комітетами перегляду
— Ерік

11
@Eric Переглянувши свій коментар п'ятирічного віку: ой, як ви мали рацію!
— wchargin

27

У циклі це нешкідливо, але в заяві про призначення це може призвести до несподіваних результатів:

var x = 5;
var y = x++; // y is now 5 and x is 6
var z = ++x; // z is now 7 and x is 7

Пробіл між змінною та оператором також може призвести до несподіваних результатів:

a = b = c = 1; a ++ ; b -- ; c; console.log('a:', a, 'b:', b, 'c:', c)

На завершення, несподівані результати також можуть бути проблемою:

var foobar = function(i){var count = count || i; return function(){return count++;}}

baz = foobar(1);
baz(); //1
baz(); //2


var alphabeta = function(i){var count = count || i; return function(){return ++count;}}

omega = alphabeta(1);
omega(); //2
omega(); //3

І це запускає автоматичне вставлення крапки з комою після нового рядка:

var foo = 1, bar = 2, baz = 3, alpha = 4, beta = 5, delta = alpha
++beta; //delta is 4, alpha is 4, beta is 6

попереднє посилення / післязростання плутанини може призвести до поодиноких помилок, які вкрай важко діагностувати. На щастя, вони теж цілком непотрібні. Є кращі способи додати 1 до змінної.

Список літератури


13
Але вони не "несподівані", якщо ви розумієте оператора. Це як ніколи не використовувати, ==тому що ви не розумієте різниці між ==і ===.
— greg84

1
Саме так. Є питання C #, яке розкриває помилкові припущення.
— Пол Sweatte

Я думаю, що Крокфорд полягає в тому, що більшість програмістів не повністю «розуміють оператора», навіть якщо вони думають, що це роблять; отже, існує небезпека їх використання, оскільки потенціал непорозуміння великий. Дивіться коментар @ Павла про помилкові припущення, що підсилює аргумент, що люди взагалі неправильно розуміють, як насправді працюють оператори префікса та постфікса. З цього приводу я визнаю, що мені подобається їх використовувати, але заради інших намагаюся обмежити їх використання ідіоматичними випадками (див . Відповідь cdmckay ).
— gfullam

Здається, ваше посилання Whitespace порушено.
— Руслан

Що складного у вашому прикладі "пробілів"? Я отримую прямий вихід a: 2 b: 0 c: 1. У першому прикладі я також не бачу нічого дивного чи несподіваного ("заява про призначення").
— Кен Вільямс

17

Розглянемо наступний код

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[i++] = i++;
    a[i++] = i++;
    a[i++] = i++;

оскільки я ++ оцінюється вдвічі, то вихід (від налагоджувача vs2005)

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

Тепер розглянемо наступний код:

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = ++i;
    a[++i] = ++i;
    a[++i] = ++i;

Зауважте, що вихід однаковий. Тепер ви можете подумати, що ++ i та i ++ однакові. Вони не

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

Нарешті розглянемо цей код

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = i++;
    a[++i] = i++;
    a[++i] = i++;

Вихід зараз:

    [0] 0   int
    [1] 1   int
    [2] 0   int
    [3] 3   int
    [4] 0   int
    [5] 5   int

Тому вони не однакові, змішування обох призводить до не настільки інтуїтивної поведінки. Я думаю, що для циклів нормально ++, але слідкуйте за тим, щоб у вас було декілька символів ++ в одному рядку чи однакові інструкції


3
Дякуємо, що зробили цей експеримент. Особливо "просто виглядає" код, але моєму мозку стає важко втриматись дуже швидко. Однозначно потрапляє в категорію "хитрі".
— artlung

28
Я зупинився після першого прикладу. Ви сказали "Розгляньте наступний код". Ні, ніхто не пише цього коду. Це ідіот, який пише, що хибний, а не мовна конструкція.
— Сем Харвелл

4
Ви показали, що два різних фрагмента коду дають різний вихід, і це доводить, що це погано?
— nickf

7
висновок - "стежте, коли у вас є кілька символів ++ в одному рядку чи однаковій інструкції". Я не думаю, що ти це читав
— Ерік

1
"оскільки я ++ оцінюється двічі" - неправильно! Змінення змінної двічі між точками послідовності, а також її використання недійсне в C ++ і призводить до не визначеної поведінки. Тож компілятор може робити все, що завгодно. Результати, представлені тут, випадкові від реалізації компілятора.
— tbleher

16

Характери операторів приросту та скорочення "до" та "після" можуть бути заплутаними для тих, хто не знайомий з ними; це один із способів, яким вони можуть бути хитрі.


29
Домовились; Однак, досвідчений професіонал повинен не слід плутати.
— Нейт

1
Я думаю, це схоже на вказівки, щоб уникнути статичного статичного чи глобального. З цим, як з програмуванням програмування, немає нічого по суті, але необізнане використання може призвести до поганого коду.
— Джоель Мартінес

6
Питання не в тому, чи зможе хороший програміст "пропрацювати свій шлях" за логікою - хороший код в ідеалі не вимагає жодної роботи для розуміння. Я думаю, що McWafflestix полягає в тому, що ви не повинні писати код, який при короткому огляді може призвести до додавання помилок через два тижні або два роки. Я знаю, що я був винен у додаванні коду до звичайного, якого я не повністю зрозумів :)
— Майк,

1
@Mike: Так, зауважте, як я не вказав, чи можуть оператори бути складними для оригінального автора коду чи для більш пізнього обслуговування. Як правило, ми намагаємось припустити, що оригінальні кодери не використовують конструкції, з якими вони не знайомі / не зручні (на жаль, це часто неправильне припущення); однак, ця ознайомленість, безумовно, не поширюється на керівників (і, як зазначалося вище, може не поширюватися навіть на оригінального автора).
— Пол Соньє

Я намагаюся написати свій код для читання іншими програмістами, але я не намагаюся писати його для читання початківцям.
— Лука H

16

На мій погляд, "явне завжди краще, ніж неявне". Тому що в якийсь момент ви можете заплутатися в цьому твердженні про збільшення y+ = x++ + ++y. Хороший програміст завжди робить свій код більш читабельним.


1
У x ++ або ++ y немає нічого неявного.
— Флоріан F

1
Ця читабельна річ з кодом хитра ... Я думаю, що існує базовий рядок того, наскільки простим повинен бути ваш код, і ми - як громада - повинні процвітати і вміти читати те, що зараз не читається, наприклад, однолінійні з вкладеними тернеріями, щоб дозволити більше запускати речі.
— Хамза Уагад

14

Найважливіше обґрунтування уникнення ++ або - полягає в тому, що оператори повертають значення і викликають побічні ефекти одночасно, що ускладнює міркування про код.

З метою ефективності я віддаю перевагу:

  • ++ i, коли не використовується повернене значення (без тимчасового)
  • i ++ при використанні зворотного значення (без зупинки трубопроводу)

Я фанат містера Крокфорда, але в цьому випадку мені доводиться не погоджуватися. ++iце на 25% менше тексту для розбору, i+=1 і, певно, ясніше.


13

Я дивився відео Дугласа Крокфорда про це, і його пояснення щодо використання приросту та зменшення - це те

  1. У минулому він використовувався в інших мовах, щоб порушити межі масивів і викликати всі способи поганості і
  2. Що це більш заплутано, і недосвідчені розробники JS не знають, що саме це робить.

По-перше, масиви в JavaScript мають динамічний розмір, і, вибачте, якщо я помиляюсь, неможливо порушити межі масиву та отримати доступ до даних, до яких не можна отримати доступ за допомогою цього методу в JavaScript.

По-друге, чи слід уникати складних речей, безумовно, проблема полягає не в тому, що у нас є цей інструмент, але проблема полягає в тому, що там є розробники, які претендують на JavaScript, але не знають, як працюють ці оператори ?? Це досить просто. значення ++, дайте мені поточне значення і після виразу додайте до нього значення ++, збільште значення перед тим, як надати мені.

Вирази, такі як ++ + ++ b, легко розробити, якщо ви просто пам'ятаєте вище.

var a = 1, b = 1, c;
c = a ++ + ++ b;
// c = 1 + 2 = 3; 
// a = 2 (equals two after the expression is finished);
// b = 2;

Я думаю, ви щойно повинні запам'ятати, хто повинен читати код, якщо у вас є команда, яка знає JS зсередини, то вам не потрібно турбуватися. Якщо ні, то прокоментуйте це, напишіть інакше і т. Д. Робіть те, що вам потрібно зробити. Я не думаю, що приріст і зменшення по суті є поганими, або генеруються помилки, або створюється вразливість, можливо, менше читається залежно від вашої аудиторії.

До речі, я думаю, що Дуглас Крокфорд так чи інакше є легендою, але я думаю, що він викликав багато лякань за оператора, який цього не заслужив.

Я живу, щоб бути неправдивим, хоча ...


10

Інший приклад, більш простий, ніж деякі інші, з простим поверненням збільшення вартості:

function testIncrement1(x) {
    return x++;
}

function testIncrement2(x) {
    return ++x;
}

function testIncrement3(x) {
    return x += 1;
}

console.log(testIncrement1(0)); // 0
console.log(testIncrement2(0)); // 1
console.log(testIncrement3(0)); // 1

Як бачимо, жоден пост-приріст / декремент не повинен використовуватися при операторі return, якщо ви хочете, щоб цей оператор впливав на результат. Але повернення не "вловлює" операторів після збільшення / зменшення:

function closureIncrementTest() {
    var x = 0;

    function postIncrementX() {
        return x++;
    }

    var y = postIncrementX();

    console.log(x); // 1
}

Лише одна з ваших функцій отримує x як параметр
— Calimero100582

8

Я думаю, що програмісти повинні бути компетентними мовою, якою вони користуються; використовувати його чітко; і добре використовувати його. Я не думаю, що вони повинні штучно калічити мову, якою вони користуються. Я кажу з досвіду. Я колись працював буквально по сусідству з магазином Cobol, де вони не використовували ELSE "тому що це було занадто складно". Reductio ad absurdam.


@downvoter Захоплююче. Ви не вважаєте, що програмісти повинні володіти мовою? Ви думаєте, що вони повинні штучно калічити мову? Що це таке? Тут немає жодного третього вибору.
— Маркіз Лорн

1
і підсумок цього прикладу COBOL змушує мене ще більше радіти, що COBOL здебільшого вимер, перш ніж я потрапив у кодування
— Марк К Коуан

1
@MarkCowan Я не розумію, чому. Моя думка про свавільне обмеження, а не про мову. Анекдот не вказує на щось погане з Коболом. Що потрібно було вимерти, це магазин програмування, і це було. Відвідайте нутри будь-якого великого банку, і ви побачите, що Кобол живий і здоровий.
— Маркіз Лорн

2

Як вже згадувалося в деяких існуючих відповідях (що дратує я не можу коментувати), проблема полягає в тому, що x ++ ++ x оцінюють за різними значеннями (до vs після збільшення), що не очевидно і може бути дуже заплутано - якщо це значення використовується. cdmckay пропонує досить розумно дозволити використовувати оператор приросту, але лише таким чином, що повернене значення не використовується, наприклад, у його власному рядку. Я також включав би стандартне використання в циклі for (але тільки в третьому операторі, повернене значення якого не використовується). Я не можу придумати іншого прикладу. Будучи «спалений», я би рекомендував те саме керівництво і для інших мов.

Я не погоджуюся з твердженням, що ця надмірна суворість пояснюється тим, що багато програмістів JS є недосвідченими. Це саме той тип письма, який типовий для «надмірно розумних» програмістів, і я впевнений, що це набагато частіше зустрічається у більш традиційних мовах та у розробників JS, які мають досвід роботи з такими мовами.


1
Дякую за відповідь. Для того, щоб коментувати, вам знадобиться значення репутації 50 або більше. Див: stackoverflow.com/faq
— artlung

1
@artlung: Я знаю це і обґрунтування цього. Я просто говорив, що це дратує :)
— Близько Привмана

2

З мого досвіду, ++ i або i ++ ніколи не викликав плутанини, окрім як уперше дізнавшись про те, як працює оператор. Це важливо для найосновніших циклів і циклів, які викладаються будь-яким курсом середньої школи або коледжу, що викладається мовами, де можна використовувати оператора. Я особисто вважаю, що роблю щось на зразок того, що нижче, щоб шукати та читати краще, ніж щось із знаком ++, який знаходиться на окремому рядку.

while ( a < 10 ){
    array[a++] = val
}

Зрештою, це налаштування стилю, а не більше того, що важливіше - коли ви робите це у своєму коді, ви залишаєтесь послідовними, щоб інші, що працюють над тим самим кодом, могли слідувати, і не потрібно обробляти однакові функції по-різному .

Крім того, Крокфорд, здається, використовує i- = 1, який мені здається важче прочитати, ніж --i або i--


2

Мої 2 центи - це те, що їх слід уникати у двох випадках:

1) Коли у вас є змінна, яка використовується в багатьох рядках, і ви збільшуєте / зменшуєте її на першому операторі, який використовує її (або останньому, або, що ще гірше, в середині):

// It's Java, but applies to Js too
vi = list.get ( ++i );
vi1 = list.get ( i + 1 )
out.println ( "Processing values: " + vi + ", " + vi1 )
if ( i < list.size () - 1 ) ...

У подібних прикладах ви можете легко пропустити, що змінна автоматично збільшується / зменшується або навіть видаляється перше твердження. Іншими словами, використовуйте його лише в дуже коротких блоках або там, де змінна з’являється в блоці лише на кількох близьких висловлюваннях.

2) У випадку множини ++ і - приблизно однакова змінна в тому самому операторі. Дуже важко згадати, що відбувається у таких випадках:

result = ( ++x - --x ) * x++;

Екзамени та професійні тести задають подібні приклади, і справді я натрапив на це питання, шукаючи документацію про один з них, але в реальному житті не варто змушувати так багато думати про один рядок коду.


1

Фортран - мова, схожа на С? Він не має ані ++, ані -. Ось як ви пишете цикл :

     integer i, n, sum

      sum = 0
      do 10 i = 1, n
         sum = sum + i
         write(*,*) 'i =', i
         write(*,*) 'sum =', sum
  10  continue

Елемент індексу i збільшується правилами мови кожен раз через цикл. Якщо ви хочете збільшити щось інше, ніж 1, порахуйте, наприклад, два, наприклад, синтаксис ...

      integer i

      do 20 i = 10, 1, -2
         write(*,*) 'i =', i
  20  continue

Чи схожий на Python C? Він використовує розуміння діапазону та списку та інші синтаксиси, щоб обійти необхідність збільшення індексу:

print range(10,1,-2) # prints [10,8.6.4.2]
[x*x for x in range(1,10)] # returns [1,4,9,16 ... ]

Отже, грунтуючись на цьому рудиментарному дослідженні рівно двох альтернатив, мовні дизайнери можуть уникати ++ і - передбачуючи випадки використання та надаючи альтернативний синтаксис.

Чи відрізняються Fortran і Python від магніту-помилки, ніж процедурні мови, які мають ++ та -? Я не маю доказів.

Я стверджую, що Fortran і Python схожі на C, тому що я ніколи не зустрічав когось, хто вільно володів C, хто не міг би з 90% точністю відгадати правильність намірів непритомного Fortran чи Python.


1
Так, IFortran - з 1950-х, тоді як C - з 1970-х, тому Фортран може бути ні, як C. Цікавий приклад Python, якого не вистачає на зразок оператора plusplus. Ітерація над структурами даних простіше обробляється в Python, Python - це "інакше" об'єктно-орієнтований JavaScript. Можливо, порада Крокфорда стосується використання більше синтаксису, подібного до ОО, для ітерації.
— artlung
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.