Работа над чужим кодом [закрыто]


60

У меня едва ли годовой опыт в программировании. После того, как я начал работать, большую часть времени я работал над чужим кодом, либо добавляя новые функции поверх существующих, либо модифицируя существующие. Парень, который написал настоящий код, больше не работает в моей компании. Мне трудно понять его код и выполнять свои задачи. Всякий раз, когда я пытался изменить код, я каким-то образом путался с работающими функциями. Что все я должен иметь в виду, работая над чужим кодом?


103
Добро пожаловать в реальный мир, где код живет вечно и программисты приходят и уходят.

65
Это не чужой код. Теперь это твой код.
Буб

6
@ повторить, что это может быть связано только с неопытностью ОП и недостатком знаний. если я зайду в функцию коллеги, удалю строку необходимого кода, оставлю указанный код в живых и сломаю его, это моя халатность, а не структурно несостоятельный код
rickyduck

19
@Buhb: Но через 6 месяцев, когда вы вернетесь к нему, это будет чужой код, даже те части, которые вы написали ;-)
Jörg W Mittag

6
Будь счастлив. Вы развиваете критический навык, который будет отличать вас от людей с меньшим опытом или только академическим опытом. Это должно быть сложно. Вот почему это ценно.
Скотт С Уилсон

Ответы:


59

Есть ли в коде юнит-тесты? Если нет, я настоятельно рекомендую вам начать добавлять их. Таким образом, вы можете написать новые функции / исправления ошибок как неудачные тесты, а затем изменить код, который проходит тест. Чем больше из них вы создадите, тем больше у вас будет уверенности в том, что добавленный вами код не сломал что-то еще.

Написание модульных тестов для кода, который вы не полностью понимаете, поможет вам понять указанный код. Конечно, функциональные тесты должны быть добавлены, если они еще не существуют. У меня сложилось впечатление, что они уже существуют из вопроса ОП. Если я ошибаюсь в этом вопросе, то эти функциональные тесты должны стать вашим первым шагом.

Eagle76dk делает большой акцент на привлечении вашего менеджера для выполнения этой работы - более подробно в посте Eagle76dk.

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


12
Написание модульных тестов может быть легче сказать, чем сделать, хотя, в зависимости от кода и его зависимостей ...
Свиш

1
@Svish: Хороший вопрос. Я никогда не предполагал, что это будет легко, просто это стоит делать, даже если необходим некоторый рефакторинг, чтобы сделать код более подходящим для модульного тестирования.
Сардатрион

46
Написание модульных тестов для существующего кода является очень сложной и трудоемкой задачей, если код не предназначен для него. Для новичка сделать это с кодом, который вы не понимаете, может быть очень тяжелой работой, которая никогда не закончится. Это был бы способ начать изучение кода, но я бы не сказал, что это очевидный способ начать изучение кода.
jake_hetfield

3
Модульные тесты лучше всего написаны во время разработки. В случае, если вам нужно исправить ошибку и не понять дизайн или у вас нет спецификации, вы склонны добавлять модульные тесты, которые подтверждают существующие ошибки. Иногда ошибки - это ошибки. В связи с этим я предлагаю сначала установить функциональные тесты вместо модульных тестов в этом случае. Это означает использование примера поиска, который генерирует результаты, одобренные пользователем. Сделайте контрольные примеры, записав эти ситуации, действия и результаты полностью. Если ваши функциональные тесты охватывают все пользовательские истории и работают после ваших патчей, у вас все в порядке без юнит-тестов.
Alfe

2
Написание модульных тестов является подходом «снизу вверх» и займет огромное количество времени и поэтому часто не прагматично в больших проектах. Переписывание всего этого может быть быстрее в этом случае. Вы можете найти (и тогда вам нужно время, чтобы исправить) ошибки в модуле, которые не важны, потому что случай никогда не возникает в функциональном контексте.
Alfe

46

В дополнение к другому ответу, в котором упоминались юнит-тесты, я бы посоветовал вам убедиться, что все находится под контролем версий, чтобы вы могли легко отменить изменения. И внесение небольших изменений, чтобы сделать код более понятным.


11
Хорошая точка зрения, но я вроде бы предположил, что любой, кто сейчас пользуется
деньгами,

6
Вы были бы удивлены. Я работал в качестве подрядчика в ряде компаний, где был принят только окончательный вариант кода. Честно.
5

4
К пункту 5arx: если корпоративная культура заключается только в том, чтобы предоставлять идеальный код, можно поддерживать собственный личный репозиторий Git или Mercurial. Это особенно легко, если «настоящим» контролем версий компании является SVN.
Дастин Расен

2
+1 и +1 к комментарию 5arx. Я сделал интеграционные работы в ДЕЙСТВИТЕЛЬНО крупных компаниях, где система контроля версий состоит из написания даты, вашего имени и комментария в файле. После использования с git это кажется пугающе неэффективным и подверженным ошибкам.
Лев

1
@Sardathrion Вы знаете, что происходит, когда вы "задница меня" ...
WernerCD

32

На мой взгляд, самый быстрый способ изучения чужого кода (особенно когда изменения вызывают неожиданное поведение, как вы описываете) - это пройтись по коду с помощью отладчика .

Начните с пошагового прохождения того, что кажется основным циклом / основными методами программы. Используйте функции step into и step, чтобы увидеть, что делают разные методы. Это научит вас общей структуре кода.

После этого разделяй и властвуй, проходя и изучая различные части программы на более глубоком уровне. В большинстве отладчиков вы можете изучать переменные и их текущие значения . Изучите, как они меняются и когда.

Установите точки останова на методы, которые вызывают поведение, которое вас касается. Например, если вы пытаетесь изменить текст в программе, и текст продолжает возвращаться к исходному значению, установите точки останова во всех местах, где текст был изменен, или попытайтесь переместить все эти изменения в один единственный метод. Используйте стек вызовов, чтобы увидеть, откуда вызывается этот метод и т. Д.

Если изменение строки кода вызывает неожиданные изменения в других местах, поместите точку останова в эту строку и посмотрите, что там происходит, например, проверив значения текущих переменных в области видимости, используя step into или стек вызовов, чтобы увидеть откуда звонок пришел.

Делая это много, вы начнете изучать структуру кода на удивление быстро. Я начинал, как и вы, на моих первых работах по программированию, с большим количеством кода, который был написан много лет назад и изменялся многими людьми в течение многих лет. Код был не мой, потому что там, где другие люди работали над ним одновременно. Я не мог переписать все это в тот момент. Написание тестов для всего этого кода заняло бы у меня месяцы или годы. Отладчик действительно спас меня, не знаю, как бы я узнал код без него ...


3
Я думаю, что это единственный реалистичный ответ: написание модульных тестов для огромного приложения без их непрактичного использования
CommonSenseCode

Хотелось бы, чтобы я проголосовал не раз.
user949300

30

Первое, что нужно иметь в виду, это то, что на чтение кода тратится больше времени, чем на написание кода. Потратьте время, чтобы понять, как работал другой парень - его стиль и подход к проблемам.

Постарайтесь максимально приспособить существующий стиль - иначе парень после вас будет в два раза больше приспосабливаться.

Работа с чужим кодом - это норма, а не исключение, вам нужно научиться понимать, как другой парень решил бы проблему или реализовал функцию. Как только вы это сделаете, вам будет легче иметь дело с его кодом.


21

Не спешите предполагать, что код другого парня воняет.

Но всегда будь подозрительным.

Но да, требуется время, чтобы понять код другого разработчика. Чем больше функция или объект используется несколькими частями системы, тем более осторожным вы должны быть. Если вы можете решить проблему ближе к симптому, это иногда может быть полезным. Например, нормализуйте входящие данные из другого объекта на стороне проблемного объекта после доставки данных, но прежде, чем что-либо еще произойдет.

Это плохой знак, когда изменение одной вещи неожиданно ломает другую. Если у вас есть другие опытные разработчики, на которых вы можете положиться за помощью, я бы порекомендовал им взглянуть на вещи, вызывающие у вас проблемы. По крайней мере, вы можете подобрать несколько вещей, наблюдая за их отладкой.


9
+1. Не поддавайтесь искушению переписать блоки, которые вы не понимаете - вы почти наверняка внесете новые ошибки, делающие это. Вместо этого медленно и методично перемещайтесь по коду, внося изменения только в тех случаях, когда фактически требуется новая функциональность (или исправление ошибок).
Скотт С. Уилсон,

1
Я бы сказал, несколько раз в год я ошибаюсь. Просто сделал это сегодня и понял, что каждый из 5 пунктов, которые я считал проблемными, был там по причине. Он / они могли бы оставить их более четко обозначенными, но я мог бы потратить впустую меньше времени, предполагая, что он / они не пошел туда по уважительной причине.
Эрик Реппен

14

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

Тем не менее, первое, что вы узнаете, мы не живем в идеальном мире!

Многие разработчики не документируют свой код должным образом, если они вообще смешивают бизнес-логику с несвязанным кодом, и единственный тест, который они проводят, - это быстрый прогон того, что они ожидают от нормального варианта использования.

При работе с кодом, подобным этому, первое, что вам нужно сделать, это установить, что он должен делать. Если есть комментарии, они могут дать вам подсказки, но не рассчитывайте на это. По моему опыту, многие программисты не умеют объяснять себя, и даже если они оставляют комментарии, они могут быть бессмысленными. Однако, если вы не единственный программист в компании, кто-то наверняка должен иметь хотя бы базовое представление о том, для чего предназначен код и для чего он предназначен. Поспрашивать!

Если у вас есть юнит-тесты, они сделают вашу жизнь намного проще. Если вы этого не сделаете, то часть изучения кодовой базы может включать написание модульных тестов для кода, который уже существует. Обычно это не считается хорошей практикой, потому что если вы пишете модульные тесты, чтобы соответствовать существующему коду, вы в конечном итоге получите модульные тесты, которые думают, что код работает как есть (они будут написаны, чтобы предположить, что поведение, которое на самом деле является ошибкой) правильно), но по крайней мере это дает вам базовый уровень. Если позже вы обнаружите, что какое-то поведение, которое, по вашему мнению, было правильным, на самом деле неверно, вы можете изменить модульный тест, чтобы проверить, что является ожидаемым результатом, а не результатом, который дает код. После того, как вы пройдете модульное тестирование, вы можете вносить изменения и оценивать, какие побочные эффекты имеют любые сделанные вами изменения.

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

Помните, что даже хорошо написанный и хорошо документированный код может быть сложным для постороннего понимания. Код по сути является выражением того, как человек, который написал его, думал в то время, и у каждого свой уникальный мыслительный процесс. Вам нужно научиться быть немного терпеливым и быть детективом. Трудно войти в мыслительный процесс другого человека, но это важный навык для программиста, выполняющего обслуживание существующего кода. Поскольку большая часть кода (около 70%) связана с поддержанием существующего кода, это важный навык, который необходимо освоить.

О, и теперь, когда вы увидели боль, которую может причинить плохо документированный, непроверенный и беспорядочный код, вы не будете делать это со следующим бедным разработчиком, верно? :) Учитесь на ошибках вашего предшественника, хорошо комментируйте свой код, убедитесь, что у каждого модуля есть четко определенная ответственность, которой он придерживается, и убедитесь, что у вас есть полный набор модульных тестов, которые вы либо пишете первыми (для методологий TDD), либо по крайней мере, наряду с разрабатываемым кодом.


13

Имейте в виду, что умение читать код, который вы не написали, является очень ценным навыком, вероятно, более ценным, чем написание кода. К сожалению, это широко недооценивают и недостаточно учат в школах.

Я пытаюсь сказать, что это нормально, что вы не всегда понимаете код при первом чтении (как обычно, когда вы не пишете идеальный код в первый раз). Если вы согласны с тем, что для получения иностранного кода требуется время, вы не будете возражать, если приложите дополнительные усилия. Небольшое резюме:

  • Модульные тесты были бы идеальными, но не всегда реалистичными; особенно если вы работаете в крупной организации с тяжелой бюрократией.

  • Научитесь правильно пользоваться системой контроля версий; Вы никогда не сломаете существующее (не совсем никогда , но это хорошая сеть безопасности).

  • Не думайте, что это плохо просто потому, что вы не понимаете это мгновенно. Не думайте, что это хорошо просто потому, что это работает. Важно, чтобы вы понимали стиль кода предыдущего сопровождающего и адаптировали добавленные строки к его стилю. Ответственный за вас, сопровождающий, поблагодарит вас за это.

  • К сожалению, в некоторых компаниях сложность чтения кода может быть недооценена. Это распространено в крупных корпорациях с жесткими процессами. Они часто (неявно) предпочитают использовать push-код, который работает быстро, а не тратить время на написание чего-то чистого. Я оставлю на ваше усмотрение решение, где ваша команда стоит в этом вопросе.

  • Наконец, никогда не забывайте, что чтение кода - это навык . Чем больше вы это сделаете, тем лучше вы добьетесь этого. Еще один способ сказать, что единственный способ добиться этого - практиковать это много раз. Как упоминалось выше, чтение кода является и будет гораздо большей частью вашей работы, чем написание.


11

Судя по вашим проблемам с непреднамеренным взломом, я предполагаю, что код не покрыт автоматическими тестами. Шаг № 0 состоит в том, чтобы немедленно заказать и прочитать « Эффективная работа с устаревшим кодом » Майкла Фезерса. Это просто бесценно.

Основные шаги, которые я бы предложил:

  • Покройте код тестами, охватывающими текущую функциональность.
  • Рефакторинг пока не понятно.
  • Написать тест на новую или измененную функциональность.
  • Реализуйте новый функционал.
  • Рефакторинг до удовлетворения.

Я намеренно пропускаю указание вида тестов (модуль, интеграция, ...) - просто получаю какое-то автоматическое покрытие тестами.

(и, да, придерживайтесь стиля кодирования с точки зрения разметки и именования)


10

Как уже упоминалось ранее: добро пожаловать в реальный мир. Я могу только согласиться с более ранними ответами. Я только хотел бы расширить ответ с моим опытом работы об оценках времени.

Хорошее предложение, чтобы прояснить ваш босс, потребуется время, чтобы узнать, как думают другие разработчики. Обычно вы сталкиваетесь с тем, что текущее решение часто зависит от возраста и опыта разработчика.

Если вам повезет, нужно проанализировать задачу, и понимание документации поможет вам (но часто это не так).

Мой опыт показывает, что при изменении чужого кода старайтесь не изменять код, который не связан с вашей текущей задачей. Возможно, вы знаете лучшее решение или оно может быть написано более интуитивно, но его изменение часто приводит к таким проблемам, как:

  • Задача займет больше времени, и ваш начальник не поймет этого.
  • Код, который вы меняете, должен быть протестирован, и он стоит. Текущее решение было проверено и одобрено.
  • Будет трудно увидеть, какие изменения решают текущую задачу, а какие являются «просто» исправлениями.

Но не стесняйтесь сказать своему боссу, если вы видите что-то, что, по вашему мнению, должно быть другим (это просто показывает, что вы можете думать).

Наконец, убедитесь, что у вас есть достаточно времени, чтобы принять решение. Более быстрое решение приходит с опытом. Но редко бывает быстрое решение, так как это первая / главная причина ошибок и неисправимого кода.


5

Думайте об этом как о выполнении операции на человеке.

Вы заглядываете внутрь, чтобы найти проблему, которую нужно исправить, и замечаете, что большинство артерий и т. Д. Не настроены так, как вы это делаете, - поэтому вы обрезаете и рубите их, пока они не будут смотреться вам правильно, а затем решите проблему.

Удивительно, но ваш пациент умирает почти сразу.

Устаревшие приложения одинаковы. У них уже есть способ работы - вам нужно понять различные компоненты программного обеспечения и то, как они связаны друг с другом, а затем внести изменения, чтобы они работали одинаково. Это не так увлекательно, как позволить вашему творчеству взбеситься, но вы можете сделать это в личных проектах.

Я бы попросил старшего инженера сидеть с тобой около часа каждый понедельник и объяснять другой аспект системы. Сделайте заметки о том, что он говорит, и отправьте по электронной почте заметки ему и вашему менеджеру, чтобы узнать, есть ли у вашего менеджера что-нибудь добавить. Таким образом, вы должны быстро набрать скорость.

Что касается того, как не сломать вещи, прежде всего убедитесь, что вы понимаете, что делает система. Проверяйте раньше - вносите изменения - проверяйте потом. Там нет магических формул; по мере того, как вы набираетесь опыта, вы становитесь лучше - или, я думаю, вас уволят!


3

Одна вещь, которую я действительно не видел, затронута здесь - не работать на острове.

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

Задавать вопросы. Много их.

Не беспокойтесь о том, чтобы «раздражать» кого-то другого (в пределах разумного) - я бы предпочел, чтобы кто-то перебил меня вопросом или двумя во время обычного цикла разработки, чем необходимость тушить пожар в производственной среде позже.

Когда вы будете готовы что-то проверить, рассмотрите это со своим наставником. Они должны быть в состоянии сказать вам не только, если что-то сломает что-то другое, но, что более важно, почему. Пересмотр кода также сделает наставника лучшим программистом, дав ему / ей представление о системе, на которое они иначе не могли бы взглянуть так часто.

Помните - вы не только изучаете систему, как любой новый сотрудник, но вы также учитесь, как стать программистом.

И пять лет спустя поощряйте следующего Нового парня использовать вас в качестве наставника.


2

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

  • Я просто недостаточно умен, чтобы понять, как работает этот код

  • Парень, который написал этот код, понятия не имел, что он делал

  • Волшебство вовлечено: очень черная магия

Это все формы сдачи. Противоядие - всегда помнить, что компьютеры являются детерминированными: всегда есть причина для того, что они делают. Код может пахнуть отливом на рыбном консервном заводе и походить на гигантскую миску лингвина, но , будучи неуклонно рациональным и непредвзятым, вы поймете это .


1

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

Если код работает в основном - звучит так, как он работает - тогда я бы сохранил стиль форматирования кода для этого модуля, будь то ваш стиль или нет. Это держит вещи единообразно. Тем не менее, хорошие комментарии никогда не выходят из моды.

Я советую тестовую систему и тестовую платформу, где вы можете модифицировать и тестировать этот код, не нарушая производство.

Если вы можете удалить элементы кода в библиотеку, я бы сделал это, если вы не работаете над библиотекой.

Со временем, когда вы поймете логику, вы сможете переписать и протестировать.

Этот совет основывается на языке, который вы используете, способности получить тестовую площадку и других имеющихся у вас ограничениях.


«Хорошие комментарии никогда не выходят из моды» ... если код не изменяется. Хотя комментарии иногда могут быть полезны, всегда принимайте их с кучей соли - вы должны будете убедиться, что код действительно выполняет то, что говорится в комментарии. Слишком часто кто-то меняет строку кода, но оставляет в существующем - и теперь неактуальном - комментарии.
dj18

1
@ dj18 Согласен, и очистка старых комментариев является частью написания кода. Я говорю, чтобы сохранить формат - если это возможно - для единообразия, но комментирование не плохая вещь.
Осьминога

1

Попробуйте использовать некоторые инструменты для анализа кода, чтобы найти неиспользуемый код, который можно удалить - так что, по крайней мере, вам не нужно беспокоиться об этом коде.


1

Выше было отмечено, что вам нужно понимать назначение системы, а не только детали кода. Программист, обладающий достаточным опытом для написания системы ввода заказов, обычно чувствует себя уверенно в части «продвижение вперед», которая включает в себя выбор продуктов, форматирование счета и обработку платежа. Они застревают, когда пользователь решает «неважно» и начинает отказываться от транзакций, или когда он совершает ошибку при обработке платежа и нажимает кнопку «назад». В этот момент многие программисты сбиты с толку, потому что они видят код, «появляющийся из ниоткуда» и не могут понять, почему он там есть.

Короче говоря, вы должны понимать не только «нормальный поток», но и все отступления, которые необходимы, если кто-то совершит ошибку или передумает. Это становится еще хуже с переопределениями супервизора, где некоторый код может быть запущен только с определенными привилегиями учетной записи.

Если кто-то пишет 10 000 строк кода в год, а приложение имеет 10-летнюю «жизнь», то программисту, отвечающему за выбор чужой работы, возможно, придется понимать 100 000 строк кода. Разделите это на 50 строк на странице, это 2000 страниц. Если программа написана по шаблону проектирования, программист обнаружит, что понимание одного «блока» приводит, по крайней мере, к общему пониманию большинства остальных. Если нет, то необходимо прочитать каждую чертову строку.

Некоторые программисты «просто делают то, что им говорят» и пишут спагетти. Они никогда не понимают «общую картину» - они просто вносят исправления, когда пользователи жалуются. В таких обстоятельствах хорошей идеей будет начать миграцию всего, что вы можете, в соответствующий шаблон. В конце концов это может означать перекодирование материала, который не «сломан». Не беспокойтесь об этом, просто убедитесь, что, пока это ваш проект, он становится все более обслуживаемым.


0

Здесь есть несколько действительно хороших ответов. Но я думаю, что также стоит упомянуть, насколько полезно знакомство с хорошими шаблонами проектирования , для чтения (хорошо написанного) существующего кода и для написания читаемого кода.

Конечно, это может быть очень запутанным, когда вы сталкиваетесь с SomethingFactoryсуществующим кодом, который на самом деле не следует фабричному шаблону . Но насколько позволяет ваша команда и структура, может быть полезно свести такие случаи к минимуму.

Следование шаблонам проектирования (где это позволяют бизнес-потребности) также может значительно уменьшить дублирование кода, что, в свою очередь, уменьшает количество ошибок при будущих модификациях.

Некоторые хорошие источники по шаблонам дизайна

http://sourcemaking.com/design_patterns

http://www.oodesign.com/

и конечно же книга

http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612


0

Отслеживание потока контроля между методами очень важно при разработке ментальной карты бизнес-логики.

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

Генерация модели данных рабочего процесса - лучший способ проанализировать все пути кода:

На практике проверка кода становится громоздкой в ​​устаревших научных процессах. Происхождение таких рабочих процессов означает, что методы разработки программного обеспечения, возможно, не применялись во время разработки, что приводило к созданию базы кода, которая эффективно запутывается. Хотя в статическом анализе хорошо разработаны методы анализа потоков данных, они не поддерживают поведение в реальных рабочих процессах. Например, когда необходимо загрузить файл конфигурации, чтобы определить, как должны обрабатываться данные, или когда используется динамическая оценка кода.

И визуализация рабочего процесса идеальна:

Распространенным мотивом, поддающимся визуальному развитию, является представление рабочего процесса в виде графика. Это защищает пользователя от основной сложности ресурсов и выполнения

Рекомендации


-1

Убедитесь, что вы используете программу, которая поможет вам найти материал в текущем файле. Нет ничего хуже, чем знать, что вы ищете в текущем файле, но вы прокручиваете и прокручиваете и не можете найти его. Контурное представление в инструменте, который вы используете для редактирования кода, действительно помогает с этой проблемой.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.