Лучшие практики для передачи устаревшего кода


66

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

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

Некоторая информация о куске кода, который я унаследую

  • Он функционирует: известных ошибок нет, но, поскольку требования к производительности продолжают расти, в ближайшем будущем потребуются некоторые оптимизации.
  • Недокументированное: документации на уровне методов и классов практически нет. То, что код должен делать на более высоком уровне, тем не менее, хорошо понятно, потому что я писал против его API (как черный ящик) в течение многих лет.
  • Только высокоуровневые интеграционные тесты: есть только интеграционные тесты, проверяющие правильное взаимодействие с другими компонентами через API (опять же, черный ящик).
  • Очень низкоуровневый, оптимизированный по скорости: поскольку этот код является центральным для всей системы приложений, многие из них были оптимизированы несколько раз за последние годы и являются чрезвычайно низкоуровневыми (одна часть имеет собственный менеджер памяти для определенных структур / запись).
  • Параллельное и без блокировок: хотя я очень хорошо знаком с параллельным и безблокировочным программированием и фактически внес несколько частей в этот код, это добавляет еще один уровень сложности.
  • Большая кодовая база: этот конкретный проект содержит более десяти тысяч строк кода, поэтому я никак не смогу объяснить мне все.
  • Написано в Delphi: Я просто собираюсь изложить это, хотя я не верю, что язык уместен в этом вопросе, так как я считаю, что этот тип проблемы не зависит от языка.

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

  • Получите все для сборки на моей машине: хотя все должно быть проверено в контроле исходного кода, кто не забыл время от времени проверять файл, так что, вероятно, это должно быть первым делом.
  • Больше тестов: хотя я хотел бы больше модульных тестов на уровне класса, чтобы, когда я буду вносить изменения, любые обнаруженные ошибки можно было обнаружить на ранних этапах, а код, который есть сейчас, не тестируется (огромные классы, длинные методы, слишком много взаимозависимости).
  • Что документировать: я думаю, для начала было бы лучше сосредоточить документацию на тех областях в коде, которые в противном случае было бы трудно понять, например, из-за их низкого уровня / высоко оптимизированной природы. Я боюсь, что есть несколько вещей, которые могут выглядеть уродливыми и нуждающимися в рефакторинге / переписывании, но на самом деле это оптимизация, которая была там по уважительной причине, которую я мог бы пропустить (см. Джоэл Спольски, Вещи , которые вы должны Никогда не делай, часть I )
  • Как документировать: я думаю, что некоторые диаграммы классов архитектуры и диаграммы последовательности критических функций, сопровождаемые некоторой прозой, были бы лучшими.
  • Кто должен документировать: мне было интересно, что было бы лучше, чтобы он написал документацию или попросил его объяснить мне, чтобы я мог написать документацию. Я боюсь, что вещи, которые очевидны для него, но не для меня, иначе не были бы покрыты должным образом.
  • Рефакторинг с использованием парного программирования: это может быть невозможно из-за нехватки времени, но, возможно, я мог бы реорганизовать часть его кода, чтобы сделать его более удобным для сопровождения, пока он еще был рядом, чтобы предоставить информацию о том, почему все так, как есть.

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

Обновление: По мере того, как проект передачи закончен, я расширил этот список своим собственным опытом в ответе ниже .


2
Сосредоточьтесь на документировании почему оптимизированных функций!

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

Хороший призыв использовать «Эффективную работу Майкла Фезерса с устаревшим кодом». Безусловно, нужно начать писать те тестовые примеры, написанные вокруг модулей, которые, по вашему мнению, скорее всего нуждаются в модификации. Если вы начнете сейчас, легче будет оправдать ожидания.
Билл Липер

Перед рефакторингом есть фаза, в которой я сомневаюсь, что Интернет, кажется, плохо отвечает: что делают лучшие программисты, чтобы понять чей-то сложный и неразборчивый код?
sergiol

Ответы:


25

Поскольку у вас есть доступ к разработчику, вы спрашиваете код:

  • Какие модули были наиболее сложными для кодирования / реализации. Какие были проблемы и как они были преодолены.

  • Какие модули породили больше всего ошибок.

  • Какие модули привели к наиболее трудным для устранения ошибок.

  • Какими битами кода он больше всего гордится.

  • Какие биты кода он действительно хотел бы реорганизовать, но не успел.

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


Мне нравится идея выбора частей, которые вы упомянули. Интуитивно я бы следовал нисходящему принципу, но таким образом самые неприятные части, скрытые глубоко в коде, могли появиться не слишком поздно, возможно, слишком поздно в процессе. Я думаю, твой путь имеет больше смысла. Есть ли у вас какие-либо предложения для части "как документировать"? UML? Текст?
PersonalNexus

@PersonalNexus. Вы также можете перенести этот подход в документацию. Спросите, какие документы наиболее полезны, а какие документы ненадежны или устарели (поверьте, 95% документации относится к последней категории!).
Джеймс Андерсон

17

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

  • Получите все под контролем версий: Убедившись, что все, что мне нужно для сборки, находилось под контролем версий, я также поискал на жестком диске старого разработчика, чтобы найти дополнительные скрипты или утилиты, которые были бы полезны для развертывания и / или тестирования приложения, но не были не проверено.
  • Сверху вниз: я хотел бы начать с высокоуровневого взгляда на основные классы и экскурсии со старым разработчиком основных областей. Тогда я бы копал глубже все остальное, отмечая //TODOмаркерами вещи, которые для меня не имели смысла .
  • Пишу всю документацию сам: пока старый разработчик просматривал мои записи, чтобы убедиться, что я все понял правильно, я настоял на том, чтобы написать все сам. Таким образом, я был бы уверен, что написанное имело смысл для меня, а не только для старого разработчика.
  • Комментарии везде: я добавил резюме документации XML для каждого класса и каждого метода. Таким образом я удостоверился, что я, по крайней мере, посмотрел на каждый фрагмент кода и имел достаточно понимания, чтобы подвести итог тому, что он сделал в предложении. Это также облегчило понимание методов, использующих методы / классы суммирования, поскольку IntelliSense собирает эту информацию. Я также мог бы легко определить области кода, которые мне еще предстояло рассмотреть.
  • Документ, близкий к источнику: чтобы облегчить связь между исходным кодом и документацией, я поместил большую часть своей документации прямо в исходный код. Для высокоуровневой документации, которая описывает взаимодействие между различными подсистемами, я использовал вики, так как размещение этой информации в одном месте кода не работает. Вся документация должна быть электронно-полнотекстовой с возможностью поиска.
  • Диаграммы: для базового обзора я использовал диаграммы классов различной степени детализации для разных подсистем. Для параллельных частей диаграммы объектов и взаимодействий были действительно полезны; см. также мой другой вопрос по теме .
  • Рефакторинг в паре: В то время как я сделал некоторый рефакторинг со старым разработчиком , чтобы получить чувство для кода и сделать вещи более ремонтопригодны, это было много времени , а также рискованный процесс, из - за отсутствием хороших инструментов рефакторинга и кучу противного зависимости между различными частями. Эффективная работа Майкла Фезерса с Legacy Code действительно помогает в этом, хотя рефакторинг без надлежащей поддержки инструмента все еще болезнен. Во время рефакторинга я позволил ему управлять мышью и клавиатурой, так как это было более забавно для него (см. Также мой последний пункт), и я был свободен записать то, что я изучал.
  • Отдельные проверки для комментариев и изменений. После того, как я случайно представил ошибку, написав комментарий поверх комментария override, я старался вносить комментарии и изменения в отдельные проверки. Я использовал небольшую утилиту, чтобы убрать все комментарии из исходного кода перед тем, как что-то проверять, поэтому различие регистрации только для комментариев показало бы 0 отличий. Все изменения (например, удаление неиспользуемых полей) были тщательно проверены старым разработчиком, чтобы убедиться, что я не удаляю вещи, которые все еще были необходимы.
  • Пошаговое прохождение критических отрывков: для наиболее оптимизированных / сложных отрывков я бы построчно перебирал код со старым разработчиком, а иногда даже с третьим коллегой. Таким образом, я получил полное представление о коде, и по мере того, как все больше людей проверяли код, мы фактически выявили несколько ошибок, а также некоторые вещи, которые можно было бы дополнительно оптимизировать.
  • Будьте быстры и держите старого разработчика мотивированным: я заметил, что старый разработчик был все менее и менее заинтересован, поскольку его последний день приближался (неудивительно). Поэтому я бы позаботился о том, чтобы самые важные части были переданы в первую очередь, а остальное мне нужно выяснить самостоятельно, если это необходимо. Я также попытался оставить ему более забавные вещи (например, управление клавиатурой при парном программировании) и заняться скучными делами, такими как написание документации сам.
  • Выявление запросов на функции. Было полезно попросить старого разработчика предоставить список функций, которые люди запрашивали, но еще не добавили. Было несколько вещей, которые мне показалось просто добавить, но там, где была веская причина, они не были добавлены, так как они сломали бы другие вещи, если бы реализовались так, как я думал сначала.

14

Находясь в аналогичной ситуации, я полагаю, что стоит также рассмотреть следующее:

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

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

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

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

Я бы поставил написание тестов как более высокий приоритет, чем написание документации лично, поскольку тесты, вероятно, дадут вам больше - или более прочное - понимание.

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


+1 больше тестов. никогда не бывает достаточно тестов.
Сардатрион

10

+1 за ответы, которые у вас уже есть на ваш вопрос!

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

Увеличение, уменьшение
. Преимущество этого состоит в том, что, хотя он объясняет это вам, у него почти наверняка будут моменты «о, да, это тоже так», которых он мог бы не иметь, если бы он просто пытался документировать это. сам по себе. И ваши вопросы помогут сосредоточиться на очевидных для него, но не для кого-то еще битах. Такое взаимодействие увеличения / уменьшения возможно только один на один, пытаться написать или прочитать что-то подобное громоздко.

Документация
Я думаю, что вы оба должны независимо документировать вещи - он должен начинать снизу (в случае, если у вас нет времени, чтобы собраться вместе), и вам следует начинать сверху, исходя из того, что вы поняли его экскурсия и, как если бы это было для кого-то другого [на предыдущей работе я унаследовал множество «устаревшего» кода, и у меня было только время, чтобы документировать его, прежде чем уйти из себя :)].

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

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


+1 за попытку самостоятельно исправить некоторые старые ошибки, чтобы проверить моё понимание кода
PersonalNexus

1
«Неважно, если он в конечном итоге ненавидит тебя» - осторожно, «это маленький мир»;)
отступить

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

7

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

Рассматривайте передачу как проект и создайте план, вовлекая руководство.

0 - убедитесь, что вы знаете, как использовать систему.

1 - Проведите четкую инвентаризацию компонентов раствора, источника каждого и где он находится (в разных репозиториях)

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

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

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

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

6 - Получить список запланированных событий (ежемесячные запуски заданий, еженедельные запуски заданий), на которые должно реагировать программное обеспечение

7 - Изучите процедуры резервного копирования / восстановления

8 - Понять основы, используемые при создании приложения

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

10. Убедитесь, что среда тестирования и разработки очень похожи.

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

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

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

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

15 - Понять, как управлять безопасностью пользователя для приложения

16 - Получить журнал ошибок и попытаться понять действия и как действие повлияло на более старые данные (если применимо)

17 - Знать процессы, которые занимают слишком много времени и что вам нужно отслеживать (например, необычные размеры файлов, ftp дубликатов файлов и т. Д.), Где это применимо.

18 - Проверьте рабочие часы сервера

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

20 - Получить контактную информацию этого парня

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

22 - Оцените свое понимание за 1 неделю до его отъезда и сообщите о любых проблемах, которые вы считаете опасными

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

Удачи.


@SSamra: Спасибо за комментарий. Мне нужно это. :)
NoChance

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

5

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

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

Затем запишите все, что осталось.


5

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

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

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

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

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

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

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


как вы должны это документировать? простой текст ? вики? комментарии в исходном коде?
c69

Все, что позволяет восстановить то же понимание кода, которое вы имели при написании документов.
Treecoder

5

Я чувствую к тебе.

Несколько предложений:

  1. Записывайте все ваши разговоры с уходящим программистом!
  2. Спросите о мотивации "больших" проблем. Хорошо, что вы понимаете API, но покопайтесь во внутренних решениях - почему код был разбит на части как есть? каковы обязанности.
  3. Постарайтесь действительно изучить код. Когда вы берете на себя обязанности по обслуживанию и поддержке, иногда возникает необходимость «изучить код во время работы». Сопротивляйтесь, если можете, и действительно изучайте код.
  4. Ищите сценарии. Вы знаете API - посмотрите, как ведет себя код. На ум приходит пример модуля Fax. Как пользователь API, вы должны были подготовить изображение страницы и отправить код команде для передачи страницы. Попросите уходящего программиста проследить за вами в коде, чтобы увидеть, как проходит этот сценарий. Затем, конечно, перейдите к сценарию «получение страницы».
  5. 80/20 - попробуйте сначала охватить наиболее распространенные сценарии.
  6. Рассмотрим переписать. Если код старый и интерфейсы хорошо определены, возможно, технология изменилась достаточно, чтобы оправдать его.
  7. Ненавижу это говорить, но думаю о поиске новой работы.

Мне нравится идея записывать все разговоры, чтобы я мог вернуться к его первоначальным словам после того, как он ушел. Предложение № 7, однако, не вариант ;-)
PersonalNexus

3

Если вы хотите приличную документацию разумно безболезненно купить копию Pascal Analyzer (PAL), я использовал ее в проектах Delphi, и это было здорово - они, возможно, теперь разделили функциональность документации на продукт, с которым я не знаком (браузер Pascal) поэтому вам, возможно, придется купить оба (<300 долларов США), но PAL был отличным инструментом для понимания, где используются переменные, откуда вызывались функции и т. д., и выявлял всевозможные потенциальные проблемы с кодом.

Используйте PAL, чтобы получить представление о том, как структурирован код, а также, возможно, список из примерно 1000 предложенных улучшений, если я что-то продолжу. Работа с этим списком улучшит качество кода, значительно упростит его и облегчит вашу жизнь в будущем. Сам Delphi поддерживает рефакторинг в последних версиях (последние 5 лет или около того). Вы действительно должны были включить все в файл dpr, чтобы он действительно работал должным образом, когда я делал это, так что имейте это в виду.

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


2

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

  1. Получите документированную модель данных, особенно столбцы и PK-FK
  2. Настройте трассировку SQL и запишите все запросы, которые запускаются при использовании приложения. Порядок выполнения запросов даст вам хорошее представление о ходе работы приложения, а также поможет в отладке.

Хороший вопрос в целом, но в моем конкретном случае нет базы данных.
PersonalNexus

1
может быть, это поможет кому-то еще
NRS

2

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

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

1) (Техническое лицо) Контактные данные клиентов, с которыми он имеет дело.

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

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

4) Документы / шаги он используется для депонирования исходного кода программного обеспечения депозитных компаний, например, Escrow.

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

Также я не знаю, впервые ли это для вас. Для меня я работал с 5/6 работодателями и всегда унаследовал код с плохой документацией или вообще без документации. Так что вместе со всей документацией просто будьте позитивны :)

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