Когда допустимо НЕ починить разбитые окна?


21

В отношении разбитых окон , бывают ли моменты, когда рефакторинг лучше оставить для будущей деятельности?

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


6
Иногда босс решает, что разбитые окна не будут починены, потому что он знает, что заработает гораздо больше денег, починив весь дом позже.
Mouviciel

1
Базовое правило: Технический долг подходит для того, чтобы уложиться в срок, но в конечном итоге он должен быть компенсирован, и чем раньше, тем лучше
JF Dion

4
Поздравляем! Вы прекрасно описали «философию дизайна» PHP.
ThomasX


4
Завтра никогда не наступит ...
Эрик Кинг,

Ответы:


25

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

«Сделайте так, чтобы это работало, а затем заставьте это работать лучше» .

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

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

Вы заставляете код работать, а затем реорганизуете свой код, чтобы обеспечить наилучшую реализацию, которую вы можете использовать. Если вы делаете что-то знакомое, может случиться так, что вы впервые реализуете лучший код, однако для того, чтобы быть уверенным, нужно потратить некоторое время на то, чтобы дважды проверить свою работу. Если это выглядит так, как будто вы могли бы улучшить свой код, то вы попытаетесь провести рефакторинг, чтобы убедиться, что ваш код, по крайней мере, настолько прост и чист, насколько вы можете это сделать. Это означает, что вы сокращаете объем технического долга, который оставляете позади, и облегчаете чтение и рефакторинг в следующий раз, когда необходимо разобраться с кодом. Это основное значение, лежащее в основе мантры TDD «Red-Green-Refactor», за исключением того, что, когда в TDD вы производите рефакторинг, главным образом, для устранения дублирования, необходимо также рассмотреть другие элементы, которые могут быть реорганизованы, такие как большие классы, длинные методы,

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

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

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

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


Понравилась цитата.
Марьян Венема

1
В слишком многих местах «редкие времена» это норма.
Оз

2
Если бы только PHP-дизайнеры признали эту цитату ...
ThomasX

1
Отличная цитата, подумайте об этом - хотите ли вы, чтобы ваш маляр использовал ту же логику при ремонте своей Toyota 1999 года, и если да, готовы ли вы платить за лакокрасочное покрытие Roll Royce на автомобиле стоимостью 1000 долларов?
Mattnz

@mattnz Ваша аналогия не совсем верна для разработки программного обеспечения. Это не случай «золотого покрытия», это относительно недорогой авансовый платеж, чтобы обеспечить максимальную отдачу от ваших инвестиций. Кража вашей аналогии с живописью, это также разница между нанесением слоя краски на ваш автомобиль широкой кистью или использованием распылительной кабины, чтобы получить хороший ровный результат. Вы получаете то, за что платите, так что вы хотите заплатить по ставке профессионала и получить неполную работу? Сокращение углов может немного сэкономить в краткосрочной перспективе, но в долгосрочной перспективе приведет к увеличению технического долга.
С.Робинс

11

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

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

Если вернуться ко второй половине вашего вопроса, спринту о добавлении функций в короткие сроки, было бы неправильно говорить: «Мы не будем заниматься рефакторингом, времени нет». Также было бы неправильно говорить: «Я знаю, что прошло 6 недель, и для пользователей это выглядит точно так же, но эти разбитые окна действительно нужно исправить». Установите рефакторинг в промежутки, которые происходят в любом проекте. Не пытайтесь прибраться за счет достижения цели проекта.


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

9

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

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

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

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

Изменить: я прочитал интересную статью о графиках обслуживания критической инфраструктуры. Суть в том, что это движение далеко от рутинного обслуживания (рефакторинга), чтобы исправить при необходимости (исправить ошибки). Основное различие заключается в уровнях мониторинга - например, атомная электростанция контролирует очень подробно и исправляет, когда «сломана», но задолго до отказа. Было обнаружено, что установка схемы обслуживания не только стоит дороже, но и менее надежна из-за перебоев, вызванных программой обслуживания. Аналогичная идея требуется для программного обеспечения - рефакторинг бит, которые собираются сломать - что вы можете узнать только по измерению.


4
+1 несмотря на орфографические ошибки :); В большинстве случаев клиент не платит за чистый код - он платит за результат. Если у вас чистый код и нет результатов, вам не платят, и вы не будете проводить рефакторинг очень долго.
Джейсон

2
Я просто хочу отметить, что довольно часто Менеджер лучше понимает бизнес в целом. К сожалению, у них часто ОЧЕНЬ ХОРОШЕЕ представление о том, сколько будет стоить плохой код в будущем, поэтому убедитесь, что у вашего менеджера есть разумные данные о стоимости, с которыми вы можете работать, если вы собираетесь доверять менеджеру, который примет это решение.
Майкл Кон

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

1
@ S.Robins Мы говорим о рефакторинге, а не о плохом факторинге кода (на мой взгляд, второе - это проблема, первое приятно иметь).
Джейсон

5

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

Ситуации, когда это происходит, могут быть:

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

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

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

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

И если нет альтернативы, всегда хорошо, но когда мы это исправим?

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


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

3

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


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

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

Вот почему в нашей организации нет менеджеров: Open-Org.com;)
David

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

1

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

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

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


0

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

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

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


0

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

Стоит ли это того, зависит от вашего менеджера.


1
-1; Именно такой подход заставляет программные системы разлагаться, потому что рефакторинг рассматривается как «время, которое можно потратить на новые вещи».
Уэйн Молина

1
Рефакторинг - это время, не потраченное на новые вещи.
Питер Б

@Wayne M: Дело в том, что разработчики программного обеспечения обычно не решают, что делать, бизнес и менеджеры. Конечно, каждый хотел бы заниматься рефакторингом, когда бы он ни захотел, но это на самом деле не реальность ... по крайней мере, за мой 7-летний опыт работы в этой профессии.
Джеймс

+1 Такое отношение - это то, что приносит ценность тому, кто платит тебе зарплату. Без этого вся ваша работа может быть передана на аутсорсинг.
MarkJ

0

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

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

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


0

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

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

Однако, если вы говорите о рефакторинге рабочего кода, я бы подумал о следующем:

  • Если бы это зависело от наших старших разработчиков или технического директора, мы бы никогда не выпустили программное обеспечение. Мы всегда перефакторили и стремились к перфекционизму.

  • Если рефакторинг окажет негативное влияние на прибыльность бизнеса, то это следует перенести на график.

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

  • Вы всегда найдете лучший способ что-то сделать. Это естественный процесс, и очень важно уметь рисовать линии.


Было бы неплохо получить некоторую обратную связь по
отрицательным

2
Согласитесь, с таким количеством минус голосов, похоже, что у кого-то был просто плохой день, и он пробил кучу отрицательных голосов.
Сэм Голдберг

-1

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

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

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


В чем причина отрицательного голосования? Что-то сказано здесь не правильно? Или просто злопамятные редакторы?
Сэм Голдберг

-1

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

Но действительно ли это сломано? Вы уверены, что вы не просто приравниваете «не очень» с разбитым.

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

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

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

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

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


Обычно «не очень» идет рука об руку с «сломанной». Код, который написан неправильно, сложнее поддерживать и добавлять новые функции, поэтому в конечном итоге вам придется переписать этот код, так как вы по ходу пренебрегли рефакторингом.
Уэйн Молина

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

Я не согласен. Код может работать, но хлопотно поддерживать и добавлять функции; такой код не работает, потому что он жесткий и «вонючий». Код, который написан правильно, часто красив и проверен, а главное, его легко поддерживать и улучшать.
Уэйн Молина

@ Уэйн М: Ух ты, Уэйн, ты действительно понятия не имеешь. Если вы когда-нибудь доберетесь до PM, ваши разработчики будут любить вас, но ваша карьера, вероятно, не будет долгой. Вы должны нарисовать линию в какой-то момент. Я так понимаю, это ты обижал всех, кто не согласен с твоими мечтами?
Джеймс

1
@WayneM - так что вы бы подняли «дефект», потому что вам не нравится, как выглядит код. Код написан для выполнения работы - если он выполняет свою работу, то работает. Расходы на техническое обслуживание могут быть высокими, но это должно быть дешевле, чем переписать.
Джеймс Андерсон

-2

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

Мы столкнулись с множеством моментов WTF, когда смотрели на код, который мы унаследовали, и который явно нужно было изменить. Однако, поскольку этот код работал «почти» (асинхронность плохо понимается), у руководства не было стимула фактически разрешить рефакторинг, пока выполнялись невыполненные обязательства. Только когда в дикой природе была обнаружена ошибка, нам позволили что-либо изменить, даже если мы могли бы легко ее устранить самостоятельно через непонятные крайние случаи. Однажды я рефакторил около 10 тысяч строк кода в свое время и в итоге мне пришлось выбросить его. Время, необходимое для тестирования и других проверок, не может быть оправдано.


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