Как стать программистомМоим ученикамПереводы

Секрет того, как стать отличным разработчиком: читайте код!

Rating: 4.9/5. From 9 votes.
Please wait...

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

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

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

One secret to becoming a great software engineer: read code

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

  • Много пишите
  • Читайте ещё больше

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

Читайте много и часто. Вероятно, именно здесь кроется разница между хорошим и великим инженерами.

Почему я должен читать код?

Великие писатели унаследовали много от тех авторов, которых они читали. Можно даже назвать их «производными». Вспомните Джоан Дидион, которая в 16 лет перепечатывала предложение за предложением в книгах Хэмингуэя, учась тому, как он их использует. Или о Аврааме Линкольне, чья поздняя лирика берёт начало из его любимой Библии короля Джеймса.

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

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

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

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

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

Как говорил Дональд Кнут: “[чтение кода] действительно ценно для того, что оно выстраивает в Вашем сознании. Чем больше Вы изучаете чужие конструкции, тем способнее Вы к созданию своих собственных”.

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

Читая код…

Как я должен читать код?

Работа с кодовой базой как с книгой — то есть чтение её от корки до корки — прямой путь к провалу (что забавно, именно так читают код компьютеры).

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

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

Я использую подход из четырёх частей для работы со сложными кодовыми базами (RSDW):

  1. «Запуск» [(R)un]: компилируйте, запускайте и понимайте, что должен делать код с точки зрения пользователя.
  2. «Изучение структуры» [Examine (S)tructure]: Изучайте высокоуровневую структуру и ключевые интеграционные тесты.
  3. «Погружение» [(D)ive in]: Следуйте по пути основных логических потоков и знакомься с ключевыми структурами данных.
  4. «Написание кода» [(W)rite code]: Пишите код, тесты, приоритезируя простые улучшения кода и исправления багов.

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

1. «Запуск» [(R)un]

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

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

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

2. «Изучение структуры» [Examine (S)tructure]

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

Начните с понимания структуры кода. Как минимум, запустите пару инструментов (например tree или cloc), чтобы понять, какие языки используются в проекте, а также какова иерархия файлов в проекте.

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

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

3. «Погружение» [(D)ive in]

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

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

Для начала выберите 3-5 важных потоков, которые Вы можете взять из изученных ранее интеграционных тестов или Ваших изысканий по коду. Именно в их направлении производите погружение. Начинайте с вершины конкретного действия и отслеживайте его выполнение (вызываемые методы, состояние) до тех пор, пока не дойдёте до последнего вызова. Некоторые разработчики полагаются на дебаггеры и трассировщики (например, Xdebug в PHP), другие предпочитают строить UML или «пламенные» диаграммы.

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

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

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

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

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

4. «Написание кода» [(W)rite code]

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

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

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

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

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

Небольшие подсказки

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

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

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

При том, что важно развивать навыки чтения, также важно быть внимательным к тому, что вы читаете.

Какой код стоит читать?

В самом начале карьере по моему мнению 60% Вашего рабочего времени должно уходить на чтение кода. Около трети его должно уходить на код, с которым Вы не работаете напрямую в своём проекте. Это довольно много времени! Так на что же его тратить продуктивно?

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

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

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

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

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

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

Еще один хороший подход к чтению кода — это чтение и переписывание кода разработчиков, которых Вы уважаете. Там, где молодая Дидион перепечатала Хемингуэя или Хантер Томпсон напечатал «Великого Гэтсби», напишите код Антиреса, Гаэрона или Мрдуба, начиная с простой библиотеки. Прочитайте их другие кодовые базы. Будьте в курсе их последних работ.

Стивен Кинг предупредил авторов: «Если у Вас нет времени на чтение, у Вас нет времени (или инструментов) для авторства. Все просто». Аналогичным образом, для разработчиков программного обеспечения написание свежего кода может быть самым интересным, но (активное) чтение кода — это то, что выделит Вас.

Комментарий к статье

Как отмечает и сам автор статьи, не всё нужно безоговорочно принимать к действию. Статья описывает довольно идеальную ситуацию работы с проектом, у которого есть и документация, и знающие люди, и нормальное версионирование. В реальном мире разработки, многого может, к сожалению, и не встретиться. Тем не менее, статья предоставляет множество инструментов и на такой случай. Те же дебаггеры и точки останова уж точно можно применить на любом проекте. А если документации нет, то изучение — самое время её создать!

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

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

В остальном считаю статью крайне полезной в работе. А Вы? 😉

Секрет того, как стать отличным разработчиком: читайте код!: 21 комментарий

  1. Спасибо за Ваш труд. Весьма полезная статья.

    Rating: 5.0/5. From 2 votes.
    Please wait...
    1. Рад, что труд оценён 🙂 Спасибо!

      No votes yet.
      Please wait...
  2. спасибо за классную статью!
    мои пирамидки IF-ов в коде КПВТ будут еще выше и круче!

    Rating: 5.0/5. From 1 vote.
    Please wait...
    1. Их можно будет выделять в микросервисы 🙂

      No votes yet.
      Please wait...
  3. Спасибо большое за перевод, Александр. Как раз сейчас изучаю микросервисы. Можете поделиться своим опытом — рассказать о каком-нибудь кейсе из вашего опыта использования микросервисов?

    No votes yet.
    Please wait...
    1. Я тут, конечно, в рамках шутки их вспомнил. Но спасибо за пожелание к будущим статьям!
      Есть ли более конкретное понимание того, о чём хотелось бы почитать?

      No votes yet.
      Please wait...
  4. Судя по вашему профилю и вашим статьям, вы сейчас где-то рядом с «центром инноваций в ИТ». Можете рассказать, куда по вашему мнению все движется и про ваш стек технологий для микросервисов?

    Rating: 5.0/5. From 1 vote.
    Please wait...
    1. Нет, с центром инноваций я не связан. Просто интересуюсь тенденциями и обучением.

      >> Куда всё движется
      Если мы про IT, то вектор движения имеет обыкновение меняться. Хайп тех же микросервисов сейчас идёт на спад, что вполне нормально. Те, кто применял микросервисы ради микросервисов убедились, что это не серебряная пуля.

      В общих трендах, из более приземлённых вещей — IoT, ML. Но кто знает, на что они сменятся, когда общедоступными станут квантовые вычисления?

      >> про ваш стек технологий для микросервисов
      Микросервисы — они скорее не про стек, а про подход. Но если уж на то пошло, то в основном это Java Spring и Zend Expressive. Конечно же, можно писать на том же Go.
      Надо понимать, что есть целый набор языков, способный решить задачу построения микросервисной архитектуры. Выбор конкретного языка зависит от команды и бюджетов. Другое дело в том, что не все до конца понимают, что же такое эта самая микросервисная архитектура, ошибочно считая набор API полноценной SOA. Примерно такая же ситуация была и есть с DevOps. Все любят вворачивать это в свои описания и резюме, но не все понимают, про что это.

      No votes yet.
      Please wait...
  5. Александр, полностью согласен, что DEVOPS это хайп,
    самые правильные бест практики в нашем банке — это SRE
    что скажете про SRE ?

    No votes yet.
    Please wait...
    1. Андрей, я бы не относился к DevOps отрицательно. Более того, в правильном понимании и подходе, DevOps и SRE следуют схожим практикам.
      SRE представляет намного больше требований к оценке и критериям качества в совместной работе разработчиков и инженеров. Можно сказать, что SRE поясняет, как правильно готовить DevOps 🙂

      No votes yet.
      Please wait...
  6. Александр, великолепный перевод. Насчёт микросервисов — подпишусь под каждым словом. Был небольшой опыт с микросервисами, скорее негативный. Микросервисы хороши для соцсетей, например, а для бизнес-задач приходится решать кучу проблем, что не вариант в условиях когда у архитекторов проекта нет опыта создания сложных HA app, а бизнес приходит каждый день с новыми тасками. Из того что я вижу что если человек, работая архитектором, только разговаривает, то через несколько лет он уже и не может ничего больше, более того меняя работу практически всегда приходишь на какой-то увал, а не проект, если на нем есть архитектор, то можно поговорить и понять что все решения, которые он принимал не подходили к проекту, просто он слышал что такая связка как он выбрал хорошо работает в вакууме. Возможно я не прав, но проектировать систему должен разработчик, а не какой-то архитектор. Теория это одно, а применить все премудрости на практике совсем другое. Как вы считаете?

    Rating: 5.0/5. From 1 vote.
    Please wait...
    1. Я бы не был так критичен к архитекторам 🙂 Скорее, это плохой архитектор, если решения, принимаемые им, не доказаны и никто не может объяснить из происхождения. Я работал со многими талантливыми людьми, занимающимися архитектурой (один из них, кстати, выше откомментил). И не считаю их лишними, наоборот.

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

      No votes yet.
      Please wait...
  7. Классная статья, в большей части поддерживаю. Почитала комменты — хорошо написано, жаль только что далеко от реальности. Ну по крайней мере, всегда думала, что нескольких лет проектов и базовых знаний по языку, пару фреймворков, и SQL достаточно на архитектора. Меня в свое время тоже не брали на архитектора очень много раз, был плохой английский, не очень знания и завышенные ожидания. Не брали и всё. Я просто выучила то что нужно и тут же взяли на работу. Видимо по тому, что с нужными родственниками/знакомыми не слишком повезло. Грустненько, но что поделаешь.

    No votes yet.
    Please wait...
    1. Всё очень сильно зависит от компании и локальных особенностей. Но рад, что удалось достичь нужной высоты!

      No votes yet.
      Please wait...
  8. Спасибо за статью. Помню себя начинающим админом. Был таким же как написано. Но потом научился всем хитростям. Кхм… на счёт девопс, микросервисов, облаков и прочего — хайп согласен. Может быть это хорошо работает для программеров, но в моей, сисадминской практике, моя «сеньорита» поставила докер не послушав меня, мега-джуна по их понятиям — я только пришёл, но таки где-то уже среднего мидла по знаниям, угробила RAID с пачкой AD профилей. Капитан был крайне не рад, хочу я вам заметить, посреди-то оперейшина на другом конце Европы от офиса. Кстати в вопросе восстановления она тоже налажала, но там я уж даже и не лез даже с советами, всё равно не слушали.
    Так что нет, спросить может и не нужно, но позволить себе сделать предложение всё таки стоит, а дальше уже пусть «сеньор куратор» сам своей головой думает. А вы как относитесь к контейнерам в продакшн?

    No votes yet.
    Please wait...
    1. >> но там я уж даже и не лез даже с советами, всё равно не слушали.
      Многие забывают, что DevOps — это не про «прогеры придумали — админ красиво настроил». Это философия, которая как раз склоняет к диаметрально противоположному. Не стоит судить об одной методологии только по одному неудачному опыту. Но тут явно поведение некорректное со стороны разработчиков. И это не DevOps уж точно.

      >> А вы как относитесь к контейнерам в продакшн?
      Это один из инструментов, который в определённых обстоятельствах может как принести пользу, так и навредить. И вред, как правило, идёт от непонимания того, зачем вообще нужны контейнеры.
      В них удобно разворачивать атомарные сервисы с чётким перечнем ответственностей. Идеальный вариант для небольшого API. Но точно для разворачивания большого монолита — это отлично делается на тех же виртуалках (при наличии) и SCM (мы Ansible, к примеру, используем).

      No votes yet.
      Please wait...
      1. Интересно — что можно делать с ансиблом на пхп ?

        No votes yet.
        Please wait...
        1. Ансиблом его можно, например, поставить на свежей виртуалке. Ну, или конфиг обновить.

          No votes yet.
          Please wait...
          1. Прекрасная иллюстрация к свежей статье про инфраструктуру

            No votes yet.
            Please wait...
  9. Спасибо за хороший перевод на мегаактальную тему. С комментариями пожалуй тоже соглашусь. Правда жизни в монолитах и виртуалках, а микросервисы и докер помрут так же, как и микроядро. Это хипстерщина для тех кому не нравится работать, но нравится страдать хернёй и считать себя архитектами. А идёт все из «топ» компаний, набирают хипстеров, финансируют проект — а потом выкидывают годы хипстерской работы и самих хипстеров на помойку. Мол, пошли не тем путём, бывает. Я работал одно время на такую крупную европейскую контору, которая платит очень солидные гонорары, но в их так называемом «IT отделе» типы не могли дату отформатировать бригадой в 15+ человек.
    Потому что «ИТ-отделах» европейских контор — протирают штаны «ИТ-специалисты», переученные из географов на 3-х месячных курсах. И ничего хорошего такие специалисты по определению не предложат. Собственно, я даже пойду далее и напишу, что если у чела в резюме хипстерщина типа функциональщины, докеров и прочих микросервисов — на проект не брать. Т.к. чел 1) хипстер, работающий не за деньги, а для развлечения/удовольствия 2) напихает для развлечения/удовольствия в проект хипстерищны 3) будет заниматься тем, что нравится, а не тем, что требудется 4) в один прекрасный момент покинет проект, т.к. «надоело» или «не дают развиваться».
    А его хипстерщину потом — попробуй разгреби. И многие меня поддержат. Кто сталкивался?

    Rating: 5.0/5. From 1 vote.
    Please wait...
    1. Повторюсь, я бы не был так категоричен. Неприязнь к новым решениям и технологиям как правило обоснована тем, что неопытные команды стремятся применять эти самые новые решение без полного понимания их назначения. Поэтому и получается иллюзия того, что контейнеры не нужны.

      Интересным фактом является то, что тот же Docker является развитием уже давно имеющейся в Linux технологии Cgroups, которая позволяет создавать ограниченные по ресурсам области внутри системы. И контейнеризация существовала задолго до появления Docker. Тут по стечению обстоятельств она «выстрелила». Ровно также микросервисы являются развитием DDD, который применяется и в монолитных системах, но является развитием для создания сети API. Вот только не все компании понимают, что не всё подряд можно и нужно делить на микросервисы, а также для такого деления нужно сильное экспертное понимание со стороны бизнеса.

      Поэтому вся «хипстерщина» — в непонимании принципов работы применяемой технологии, а не в самой технологии.

      Rating: 5.0/5. From 1 vote.
      Please wait...

Добавить комментарий для Владимир Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *