Плохие привычки программистов

Original author: Krzysztof Zabłocki
  • Translation


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

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

Бросаем неиспользуемый код


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

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

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

У вас же есть система контроля версий для хранения старого кода, не бойтесь его удалять! Сделайте это прямо сейчас!

Чрезмерно обобщаем


Многие программисты пытаются писать код, который будет способен обработать все, что вы ему скормите, и начинают применять этот подход в каждом проекте. Я создал много компонентов, фреймворков, свой игровой 3D-движок, чтобы достаточно разбираться в написании абстрактного кода. Для 90% приложений в таком подходе нет необходимости.

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

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

Зацикливаемся на ООП


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

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

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

«Я могу реализовать это лучше»


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

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

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

- (void)dealloc {
   [A release];
   [B release];
   [A dealloc];
   [B dealloc];
   A = nil;
   B = nil;
   [self release];
   [super dealloc];
}


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

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

А если вы не верите, что можете писать плохой код, просто откройте какой-то проект, который написали пару лет назад. Уверен, что вам этот код покажется ПЛОХИМ.

Боимся вспомогательных инструментов (писать меньше кода)


Люди противятся изменениям. У нас есть привычки, которые мы считаем правильными и необходимы определенные усилия, чтобы выйти из зоны комфорта и попробовать что-то новое.

Многие программисты, которых я встречал, считают, что использовать Interface Builder — плохо. После первого знакомства с IB, который мне откровенно не понравился, я в течение 2х лет создавал элементы UI через код. Однако затем я преодолел свою зону комфорта и дал ему второй шанс. За это время Interface Builder был существенно улучшен, появились приятные вещи по типу Storyboard, и он стал превращаться во все более полезный инструмент. В итоге IB оказался мне полезным в определенных случаях и не только для простых интерфейсов — с его помощью можно делать довольно продвинутые вещи.

Будьте прагматичны и выбирайте наиболее подходящий инструмент для своей работы. Иногда лучше реализовывать интерфейс через код (например свой UITableViewCell для быстрой отрисовки), но в большинстве случаев Interface Builder прекрасно решает поставленные задачи.

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

Заключение


Наверняка существует еще много плохих привычек, о которых вам известно (можно даже написать об этом книгу). Приведенные здесь примеры могут быть не самыми очевидными, но я сталкивался с ними довольно часто, работая с разными людьми. И самое главное — если кто-то совершает ошибки, то это вовсе не значит, что он плохой программист, ведь у каждого из нас есть плохие привычки и главная задача состоит в том, чтобы избавиться от них. Посмотрите на код, который вы написали 3 года назад и скажите, что он вам нравится сейчас.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 124

    +8
    «Я могу реализовать это лучше» — как же это актуально. Очень отвлекает на работе. Постоянно забываю, что программист не может закончить проект, может заморозить :)
      +58
      заморозить — вторая по сложности задача для программиста
      первая — придумывать названия переменных
        +5
        Я конечно сейчас балансирую на грани начала очередного холивара, но вам не кажется, что если вы знаете, что должно храниться в переменной, то придумать ей название не так уж и сложно?
          +11
          сложности начинаются когда несколько похожих сущностей
          и это бывает частенько

          ЗЫ сейчас работаю с довольно большим проектом, над которым вразное время работало какоето количество людей
          местами названия попадаются читаемые, но совершенно не понятно почему так называется ибо ничего общего с названием нет. как говорится — исторически сложилось
            +1
            ок. Убедили: )
              +3
              алгоритм для меня простой. Да, выбор имени — чуть ли не самая важная часть кодирования. Но. Код должен читаться легко и отражать смысл задачи. Поэтому если имя сложно придумать, первая мысль: я не очень-то хорошо понял задачу (не могу выразить мысль в коде). Далее, если выбор между коротким именем, но не очень понятным, и длинным, но понятным — приоритет длинному. Т.е думаю о другом программисте, который читает код. Пусть даже кусок предложения будет в имени.
              Далее, если все же очень неудобные имена — вторая мысль: стоит расширить глосарий в проекте. Нужен какой-то термин.
                0
                Подпишусь под каждым словом.
                Либо имена переменных превращаются во что то невероятно длинное и еще более нечитаемое
                +5
                что если вы знаете, что должно храниться в переменной, то придумать ей название не так уж и сложно?

                Сложность не в том, чтобы придумать название, а придумать ёмкое и короткое — соблюсти идеальный баланс. Когда надо назвать метод "getElementsByTagName()", а когда — просто "byTag()"?
                  +3
                  Когда надо назвать метод «getElementsByTagName()», а когда — просто «byTag()»?

                  Изначально я конечно хотел не холиварить только про названия переменных, но можно и аккуратно пройтись по методам =)

                  byTag() — имхо, очень плохо по сравнению с getElementsByTagName(). По крайней мере по двум причинам —

                  1. метод с точки зрения семантики это скорее глагол, чем наречие. То есть get в названии метода собственно и характеризует действие, заложенное в реализации данной функции.
                  2. в слове Elements, точнее сказать, в последней букве, на мой взгляд, есть важная семантическая нагрузка названия метода. Множественное число дает понять, что метод вернет коллекцию (в достаточно широком смысле этого слова).

                  Итоги : в названии метода должен быть глагол, название не должно порождать неоднозначности в трактовке выходных параметров.
                  Мой выбор в таком случае getElemsByTag()

                    +12
                    Вот созздатели Prototype и MooTools тоже так думали. А вот создатель jQuery решил и иначе, там есть методы «after» вместо «insertContentAtEnd», «attr» вместо «getAttribute»+«setAttribute», «not» вместо «removeElementsBySelector» и т.д.
                    И jQuery — прекрасная библиотека!
                      +2
                      Не могу с Вами не согласиться в вопросе о полезности jQuery, хочу лишь заметить, что Вы скорее всего сначала читали документацию по jQuery, а лишь затем начали использовать методы, упомянутые выше.
                      То есть, иными словами, вы заранее знали, что делает метод attr, до того как встретили его в коде. И даже если бы Вы сначала с ним встретились, Вам все равно пришлось бы заглянуть в доки, чтобы удостовериться, что вы правильно поняли сферу его применимости.

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

                      Есть всяческие phpjavaDocs, подгружение описаний методов в разных IDE, и тому подобные штуки, которые как раз и помогают находить баланс между длиной имени и описанием в комментах, но кажется, к этой священной войне они не имеют отношения ;)
                        +5
                        Ну дык о том же и речь! Я не говорю, что названия должны быть до пяти букв, но соблюсти правильный и изящный баланс наименований — искуство)
                          0
                          целых пять букв!
                          не помню уже в каком языке было два или три символа
                          толи какойто из диалектов бейсика, толи фортрана

                          холивар таки завязался ;)

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

                            J и K к вашим услугам ))
                              0
                              А если класс внутри другого класса?

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

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

                              Студия и так неплохо находит нужные классы, не обязательно отражать в файловой структуре
                    +1
                    Не холивара ради попробую раскрыть проблему со своей колокольни: название должно объяснять назначение переменной (функции, класса и т.д.) не только в текущем контексте. Переключение контекстов — дорогая операция не только для микропроцессора, но и для мозга, особенно, если решается сложная задача. Поэтому, придумать название, логичное название, правильное название — не сложно. Сложно это сделать так, чтобы оно сохранило это своё качество при потере текущего контекста.
                      +1
                      Здесь и есть проблема — «вы знаете». А человек, пришедший через год на проект, который вы писали потратит огромное время чтобы разобраться что обозначает этот метод, ему нужно будет спрашивать вас (а если вы уже и забыли про это часть кода, то еще сложнее, а если уже и не работаете, то …) что он делает, смотреть в код и анализировать. Это трата времени, которое можно потратить на разработку.

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

                      Когда я вижу методы типа isLoadImage (помните про How much watch?), мне хочется стукнуть человека, который такое создает.
                      +1
                      Нет. Первая придумать название проекта. Иногда это стоит дороже иконки.
                        +1
                        Заметил странную вещь в своих хобби-проектах. После подбора иконки и выбора названия как то снижается мотивация по написанию проектика. С тех пор лично у меня иконка и название идут заключительным пунктом.
                          0
                          Может это ваше призвание, а вы его игнорируете, ставя на последнее место :)
                            –1
                            У меня итерационно. Помогает отвлечься от кода.
                          0
                          There are only two hard things in Computer Science: cache invalidation and naming things. © Phil Karlton
                        +3
                        Из какого фильма чувак на картинке?
                          +2
                          «Вилли Вонка и шоколадная фабрика».
                            0
                            Надо же — а я всегда думал что это какой-то старый Доктор Кто.
                        • UFO just landed and posted this here
                            +1
                            В статье говорится о крайностях. Само собой разумеется, что реализация должна соответствовать хорошо продуманной архитектуре в каждом конкретном случае.
                              –2
                              я вот тоже ничего плохого не вижу в отдельном классе на любой, с ненулевой вероятностью переиспользуемый код. или даже, заведомо не переиспользуемый, но имеющий легко объяснимое логическое значение.

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

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

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

                                а так лежат классики, еще лучше в отдельном неймспейсе, и в общем-то никому не мешают, и юнит тесты на них тянутся, и когда появляется необходимость вернуть функционал — это становится плевым делом, нежели проискивать всю историю ревизий и проверять, совместим ли старый код с обновившимся.
                              +17
                              Код, который я писал 3 года назад — полный отстой!
                                +11
                                Я могу реализовать это лучше)
                                  0
                                  главное не уйти в рекурсию)
                                    0
                                    Не страшно в нее уйти, срашно не найти выхода. И тогда все, конец! Недопитое пиво,
                                    Недобитый комар,
                                    Недосмотренный сон,
                                    Незаметный удар…
                                  +7
                                  Отличное название для книжки.
                                    +1
                                    А 3 месяца назад?
                                      +5
                                      у меня и с тремя часами такое случается. особенно если кодить и параллельно читать мануал «как надо»
                                        +1
                                        Или читать «Совершенный код» в промежутках между кодированием. Сразу же чешется всё поменять )
                                    +1
                                    И чем больше программистов в проекте, тем больше отражений этих самых вредных привычек в коде.
                                    К счастью, правильно организованный сode review делает мир лучше.
                                      +1
                                      «А если вы не верите, что можете писать плохой код, просто откройте какой-то проект, который написали пару лет назад. Уверен, что вам этот код покажется ПЛОХИМ.»

                                      Золотые слова! Однако, это хотя бы означает, что я не стою на месте. Надеюсь…
                                        +1
                                        Самое печальное в том, что есть такие кадры, которые могут декаду наблюдать свой код и видеть только чудеса инженерной мысли.
                                        0
                                        >У вас же есть система контроля версий для хранения старого кода, не бойтесь его удалять! Сделайте это прямо сейчас!
                                        Никто не смотрит, что там было сто коммитов назад.
                                        Бизнес-логика имеет свойство часто меняться: сегодня сказали сделать так, во время разработки перепробовали несколько способов, потом решили эдак, тесты показали, что лучше сделать иным способом, реальные пользователи потребовали вдруг самый первый способ назад, а через месяц чуть-чуть изменился бизнес-процесс и пришлось перейти снова на третий метод. Или пятый. А между изменениями алгоритма постоянная отладка и оптимизация текущего, добавление новых фич. И когда вдруг потребуется переключиться на ранее отброшенный способ, никто не полезет искать эти десять-двадцать строчек — их напишут заново и заново будут отлаживать.

                                        Другой случай, bleeding edge: вы решили использовать самую клёвую на данный момент библиотеку, начали её изучать, купили лицензию, а тут как раз выходит новая версия, которая круче существующей и подпадает под лицензию, так что вы решаете писать сразу на новой версии, и не беда, что она пока ещё только preview, через месяц обещают бету, а через три — релиз, и вы как раз через т ри месяца закончите с архитектурой и прототипами, вот вы начинаете писать, релиз задерживается, используете бету, постоянно натыкаетесь на баги, некоторые патчите сами, другие патчат разработчики библиотеки, каждый месяц выходит новая бета, а затем и релиз, но и в релизе баги, и снова пишете патчи и воркэраунды, выходит версия 4.0.1, половину багов исправляет, половина ваших патчей ломается, их закоментируете, пишете новые патчи, находите новые баги, выходит 4.0.2, ситуация повторяется, раскоментируем старые патчи, закоментируем текущие, пишем снова патчи, снова баги, ну почему я не взял старую проверенную библиотеку конкурентов, ну и пусть она выглядит не так красиво, зато работает, и снова патчипатчи 4.0.3,… 4.0.7, релиз продукта, багов в библиотеке полноааАААААААААА!!!
                                          +4
                                          А ветки и таги в системе контроля версий уже отменили?
                                            0
                                            На каждый удалённый участок кода делать таги и ветки? Заранее ведь не знаешь, что понадобится.
                                              0
                                              Нет, лишь на каждый полезный.

                                              Вы ведь тоже предлагаете комментировать лишь потенциально полезные участки, а не абсолютно всё.
                                                0
                                                Я — не diamant.
                                                Вот вы так делаете?
                                                  +1
                                                  Простите, у вас аватарки схожи. :)

                                                  У меня нету таких особенностей работы (use cases).
                                                  Но иногда да — я ставлю таги на подобие «invoice-tax-alternative-calculation», если чуствую, что заказчик не уверен в требованиях.
                                                    0
                                                    Не думаю, что у вас прям уж таки не бывает, что вы удаляете код, который потом оказывается нужным. Просто теоретическая возможность средствами VCS сталкивается с практическим порогом трудоёмкости, помноженным на индивидуальный коэффициент лени программиста )
                                                      0
                                                      Не бывает.
                                                      Старый код не бывает лучше или элегантней, лишь бизнес-логика может менятся.

                                                      И где там трудоёмкость?
                                                      git log -Sfoo file.js
                                                        0
                                                        Не верю (с)
                                                        Трудоёмкость добавления тега/бранча, придумывание и ввод осмысленного комментария — несколько выше чем у простого выделения и удаления/комментирования. Один раз не обломишься, а если делать это постоянно — уже совсем другой разговор. Впрочем, раз вы говорите, что у вас такого не бывает, то откуда вам знать? )
                                                          +1
                                                          Собственно, и git add; git commit; git pull --rebase; git push тоже очень много работы, которую делать сложнее, чем… не делать.

                                                          Убедили, бросаю Git, буду таскать папки: Новая папка, Новая папка (копия), Новая папка (копия) (2).

                                                          ^_^
                                                0
                                                >>Заранее ведь не знаешь, что понадобится.

                                                ну почему же. если долго работаешь с заказчиком и знаешь его, или если ведешь сходные проекты у нескольких заказчиков — можно с довольно большой вероятностью прогнозировать, что именно из того, что сегодня заказчик хочет убрать, завтра он в том или ином виде захочет вернуть
                                                  0
                                                  Любой заказчик имеет свойство удивлять неожиданными поворотами время от времени.
                                                  +1
                                                  YAGNI

                                                  Постоянный рефакторинг и соответствие только текущим требованиям (всё остальное удаляется), позволяет держать код в относительной чистоте. Делать из файлов, где код, систему контроля версий — уже какая нечистоплотность с наглостью )))
                                                  У нас в проекте куча кода осталось в наследство. В БД где-то 1 к 6-ти кода который используется и кода, который вообще никому не известно что должен делать и используется ли. При этом разработка велась: авось понадобится. Если что-то не понадобилось, не удалялось. Если делали новую версию процедуры, старой добавляли в название Old

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

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

                                                  Прослеживается аналогия с мусором и квартирой? Код не должен быть идеальным. Не должен быть абсолютно гибким. Код должен быть простым, соответствовать текущим требованиям. И быть в относительной чистоте, чтобы всегда была возможность перестроить под новые требования. И дополнительный функционал, который вдруг может понадобиться — не делает код более перестраиваемым. Наоборот. Дополнительный функционал — это дополнительные потенциальные баги. Это дополнительные издержки на поддержку и изменение тестов на него. И это дает гибкость только в предугаданном направлении, а в остальных сковывает
                                                +2
                                                Не соглашусь с первой частью. Описанную вами проблему легко решить, заменив закомментированные блоки кода на короткие комментарии вида «старая реализация такая-то удалена после ревизии x». При необходимости программист просто достанет старую реализацию из этой ревизии и вернет ее на место.

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

                                                Оба этих варианта гораздо лучше, чем создание больших кусков закомментированного кода. Маленькие фрагменты, конечно, целесообразнее оставить их в виде комментариев, пока не станет ясно, что они не нужны.
                                                  0
                                                  Это всё хорошо рассказывать, сидя дома в уютном кресле за чашкой чая по отношению к чужому проекту. А вот автора вышеописанного текста я понимаю и всей душой жалею — он сидит на раскаленной сковороде требований и сроков, ему в 2 часа ночи перед дедлайном не до этих ваших имиджевых заморочек.

                                                  Вот вам еще пример: есть некий внешний сервис, который не имеет API и есть требование написать всё-таки к нему клиента (через веб или через реверс-инжиниринг родного клиента). При этом сервис знает о таком вашем желании и всеми средствами этому мешает: на протокол вешается шифрование, API меняется, методы\поля появляются\пропадают буквально каждый день. Сегодня поле «а» означало версию, а завтра дату, а послезавтра его нет, а через 2 дня если его нет — то клиента банят. Таких полей 500 штук, заворачиваются они в XML, но только для Европы, а для Америки — в JSON. В этом месяце. А в следующем — наоборот…

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

                                                    Второй пример хороший, но в большинстве случаев требования к программе меняются не настолько часто.
                                                  +3
                                                  Да, те кто пишут код так как говорят в бреду, сплошным потоком сознания, то да — те обычно на старые коммиты не смотрят. Но это быстрый, незатратный способ что-то «наваять», а когда надо работать над проектом которому уже более года да еще впереди предвидится 2-3 года работы (с промежуточными версиями, глобальными изменениями и т.д.) то кидать старые функции в комментарии это глупо. Комментарии созданы для того чтобы комментировать код, а не для того чтобы дизейблить функции. Чтобы описывать неявную функциональность кода, а не для того чтобы организовывать через жопу версионность. А нужна история кода, версионность? Вот для этого и есть VCS. Если стоит вопрос мол когда-то уже было сделано у нас нечто, как нам это воспроизвести? — то вот для этого VCS и весь комплекс методов для того чтобы систематизировать изменения, сохранить целостность системы после изменений, предотвратить нежелательные эффекты — вот для этого системы контроля версий.

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

                                                    +1
                                                    VCS для каталогизации точно недостаточно — потому что для того чтобы искать что-то надо знать, что это «что-то» есть. А с того момента, как кусок кода прибили, сменилась половина разработчиков, написана гора нового кода и на вопрос «а как у нас реализована эта подсистема» ВООБЩЕ НИКТО не может ответить — потому что пол-года там ничего не надо было менять, и у всех из головы она вылетела (это если её автор вообще доступен). А вот какие инструменты для этого применять нужно — вопрос интересный. Как ни странно, закомментированный код (+ IDE, которая большие комментарии по умолчанию сворачивает) работает весьма неплохо, но наверняка же есть какие-то специальные средства для этого?

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

                                                    В итоге поиск + отладка действительно часто менее выгодны чем написание нового куска.
                                                      0
                                                      Наверно, всё-таки стоит отметить, что закомментированный фрагмент может содержать не просто реализацию, но и ценные идеи этой реализации, поэтому я не согласен с автором статьи, что вот так просто нужно брать и удалять всё, что перестало использоваться. Видимо здесь тоже нужно найти золотую середину. В конце концов мы проводим глоабльные ревизии кода, рефакторинги и прочия священнодействия. Что же нам мешает провести ревизию комментариев — извлечь из них важную суть и лишний код таки прибить, вынеся полезную информацию во внутреннюю документацию проекта/модуля/класса. А искать что-то в археологически ценных слоях VCS — я бы решился только в расчёте на сокровища древних ))
                                                        0
                                                        Было бы неплохо если бы средствами IDE можно было бы хранить какую-то информацию дополнительно к коду, но чтобы она не мешалась (комментарии, даже с фолдингом всё-таки отвлекают внимание). Туда можно было бы сложить старые ревизии, выдержки из документации и т.п.
                                                          0
                                                          Возможно, это было бы удобно. И наверно, было бы неплохо, если бы кто-то взялся реализовать и сделал это действительно блестяще. Но тут есть и обратная сторона медали, которую хочется, чтобы он учёл.
                                                          Чем сложнее система, тем она менее надёжна. Системы контроля версий (предложенные как универсальное хранилище) иногда рушатся, погребая под собой всю историю проекта и выясняется, что автоматический бэкап уже три года как не работает (не будем останавливаться на причинах, просто так бывает). Таскать ассоциированную с кодом метаинформацию — достаточно логически сложно (к файлу — ещё куда ни шло). Отвалившийся плагин («эти криворукие обезьяны!») оставляет с носом и в лучшем случае с человеко-читаемыми файлами, которые удаётся найти. Потому, собственно, и придумали JavaDoc — для создания документации сразу в коде, не отходя от кассы, не синхронизируя и не надеясь на IDE.
                                                          IDE. Как много в этом слове… Они бывают разные и соответствующая функциональность необходима сразу во всех, используемых разработчиками проекта, что может наложить лишние ограничения.
                                                          В общем — и неплохо было бы куда-то вынести, но расчитывать на то, что это будет сделано удобным и надёжным способом — сложно. Как по мне, самый надёжный способ сейчас — это не забывать оставлять ремарки что за закомментированный код в файле валяется, каковы условия его раскомментирования или удаления и кого, если что, спрашивать. Это вообще как бы хороший стиль совместной разработки, не только по случаю старого кода. Да и разрабатывая в одиночку тоже неплохо придерживаться такого правила — бывает, хорошо экономит время.
                                                            0
                                                            IDE от JetBrains и так хранят историю локальных правок параллельно с VCS, так что при желании могли бы реализовать подобное.
                                                              0
                                                              Не только от JetBrains. Но ключевое слово «локальных». То есть они уже будут недоступны на другой моей машине, не только другим членам команды. Так что, это лишь небольшое подспорье для себя, особенно с медлительными VCS. Но в отличие от коммитов в VCS — историю даже не покомментируешь (хотя может я что-то пропустил? с JetBrains работаю недавно).
                                                                0
                                                                А ну да, это я со своей колокольни. Я ей особо не пользуюсь, если только надо посмотреть мелкую историю внутри одного сеанса/коммита. Просто я к тому, что не всё так печально, умеют люди делать сложные и надёжные системы.
                                                          +1
                                                          Странно. Это просто рассуждения, что закомментированный код теоретически может когда-то помочь. На практике — никогда. Если вы прямо сейчас отлаживаете программу, то возможно, комментируете вызов одной функции, заменяете на другой. Это в течении дня. Если задачу (кусок задачи) сделали — то код нужно чистить, потому что он не помогает. Код старых версий значительно ухудшает восприятие. Если вы сейчас смотрите на то, что сделано и на текущий функционал, то вникание в то, как было в прошлом или как кто-то когда-то хотел сделать — требует значительно больше усилий. Потом закомментированный код связан и возможно с отброшенными требованиями. Которые не всегда по коду читаются. И с другим кодом, который уже изменен.

                                                          И читать неудобно и раскомментировать очень опасно. Доверия коду, который месяц не компилировался — никакого. Проще действительно с нуля.
                                                            0
                                                            Доверия — никакого, ровно так же как и извлечённому из VCS. А вот посмотреть «как оно делалось» очень даже полезно — внезапно обнаруживается два десятка нюансов, которые не учтены. Или ещё лучше — когда видишь, что твоя новая блестящая идея уже когда-то была реализована и отброшена. Вот тогда — самое время лезть в VCS, выяснять, когда именно была отброшена, поднимать соответствующие тикеты и т.п. Но для начала надо обнаружить, что такая реализация вообще была.
                                                          0
                                                          VCS для каталогизации точно недостаточно — потому что для того чтобы искать что-то надо знать, что это «что-то» есть.

                                                          Как вариант — помечать «прибивающие» коммиты специальным тэгом, чтобы можно было посмотреть все такие «прибивания». Уже лучше чем ничего.
                                                            0
                                                            Тогда надо ещё оставлять вместо прибитого комментария метку «здесь был прибитый, но возможно интересный код, см. до такой-то ревизии». Тогда много больше шансов что очередной разработчик, взявшийся за этот кусок, посмотрит, что было до него.
                                                    • UFO just landed and posted this here
                                                        0
                                                        У вас так же было? А я себя винил, что лень лишний класс создать…
                                                          +8
                                                          объектные вундервафли [x]
                                                            0
                                                            [объектные_вундервафли release];
                                                            [объектные_вундервафли dealloc];
                                                            объектные_вундервафли = nil;
                                                            0
                                                            Везде нужен баланс. Но классы в одну функцию обычно — зло, которые создают проблемы понимания кода там, где его могло вообще не быть. Было бы достаточно одного хорошего названия функции.
                                                            Да и вообще замечено, что параноики ООП, практически перестают создавать отдельные функции, при необходимости таковой, они либо считают правильным запихнуть ее в уже имеющийся класс, либо создать новый, в котором будет эта функция.
                                                              +1
                                                              Некоторые языки просто не позволяют создавать функции вне классов, а привычки перетягиваются и в другие. В других языках нет (или не было раньше) нормальной поддержки модульности и/или пространств имён и без запихивания функций в классы типа Utils все они будут глобальными, что напрягает хотя бы при автодополнении, не говоря о том, что может провоцировать использовать глобальные переменные.
                                                                0
                                                                Эх, боюсь даже представить место в аду, отводимое людям, создававшим классы без единого метода ))
                                                                  0
                                                                  Ну почему же. Для простейших структур данных вполне подойдет и класс без методов:

                                                                  class Point {
                                                                  public:
                                                                    Point(int p_x, int p_y): x(p_x), y(p_y) {}
                                                                    int x, y; 
                                                                  };
                                                                  
                                                                    0
                                                                    Хорошо, уточним до «класс без членов» (подозреваю, не все ООП языки позволяют подобное). Впрочем, похоже, место в аду мне уже начали готовить за само поминание еретиков )))
                                                                      0
                                                                      Нет такого извращения, которому не было бы полезного применения, главное — чтобы контекст был подходящий :-)

                                                                      В метапрограммировании на плюсах/D такое используется сплошь и рядом — потому что там класс — это значение. И если нам нужно уникальное значение — создаём новый пустой класс…
                                                                      0
                                                                      Вместо этого можно было бы использовать анонимный класс.
                                                                      Хотя они иногда бывают запрещены политикой кода в команде или не реализованные в языке.
                                                                      0
                                                                      Иерархия классов исключений — хороший пример полезных классов без единого метода :)
                                                                      Есть глобальный класс исключения для проекта, от него наследуются все остальные: исключения для баз данных, для файловой системы, сети, пользовательского ввода и т.п. Они уже конкретизируются: к базе данных может не быть коннекта — это аппаратная ошибка, может отсутствовать таблица, может не хватить прав доступа — это низкоуровневые программные ошибки. Ошибка при инсёрте — уровень повыше.
                                                                      И отлавливать их потом удобней цепочкой кэтчей на разных уровнях — какие-то, типа «нарушение ключа» можно обработать сразу в датамэппере, что-то нужно перехватывать уровнем повыше — в модели, чем-то займётся контроллер и выдаст юзверю непонятное сообщение об ошибке.
                                                                      +1
                                                                      Ваш пост оскорбляет Java разработчиков :)
                                                                        0
                                                                        интересно было бы услышать ваше мнение о паттерне «команда»
                                                                          0
                                                                          Ага, я вас понял, вы хотите поймать меня на моей однобокости? :)). Но это зря, т.к. если вы обратите внимание, когда я писал предыдущее сообщение я использовал слово «обычно», именно для случаев, когда создание класса все-таки оправдано. Лично я где-то на интуитивном уровне руководствуюсь правилом — если функция не использует члены и методы класса, если она не изменяет его состояния, то вероятно этой функции не место в этом классе.
                                                                            0
                                                                            вы молодец. на счёт интуитивного правила — тут я с вами согласен, это правило я видел формализованным в «Совершенном коде» Макконелла
                                                                              0
                                                                              ааа :)) так вот откуда оно у меня, а думал пишу свои мысли. Просто я давным давно тоже читал эту отличную книгу. Но за комплимент спасибо :)).
                                                                      +3
                                                                      У меня есть код, которому более 10 лет. И мне он нравится. Это не значит, что я остался на том же уровне, что и много лет тому, это значит, что код прекрасно работает даже сейчас и не требует переделок.
                                                                        0
                                                                        В механике есть такое понятие (аксиома))) — «не мешайте механизму работать». И это не шутка.
                                                                        +3
                                                                        Метод dealloc убил ) два в тело и один контрольный в голову для каждого объекта!
                                                                          +1
                                                                          То есть, по-сути, как всегда. Самое большое зло — недоархитектура и оверкодинг.
                                                                            +3
                                                                            переархитектура ровно такое же зло)
                                                                              0
                                                                              Много большее. Недоархитектуру можно хоть рефакторить по частям, а если у тебя монстр с двумя дестяками уровней абстракции и десятком больших внешних библиотек, который в голове вообще никак не помещается — хочется залезть под стол с испугу. Или переписать — разобраться в таком занимает не меньше времени.
                                                                            +1
                                                                            Не вижу ничего плохого в классах с одним методом. Количество методов не очень важно (если их не 100). Важно, чтобы у класса была одна ответственность.

                                                                            Плохо — когда при добавлении новой фичи изменяют существующий класс (подобные классы часто называются *Manager, *Controller, *Service и т.д.).

                                                                            Существует набор принципов SOLID, следуя которому вы получите множество небольших классов, которые очень удобно интегрировать. При добавлении новых фич вы будете создавать новые классы, расширяющие функционал существующих, не меняя их. Таким образом вы сильно уменьшите шанс внесения регрессионного бага в код.
                                                                              0
                                                                              >Плохо — когда при добавлении новой фичи изменяют существующий класс (подобные классы часто называются *Manager, *Controller, *Service и т.д.).

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

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

                                                                                Еще проблема возникает на клиенте: он зависит от большого интерфейса, хотя реально требует только один метод из него. Потому Фаулер рекомендует использовать Role Interface вместо Header Interface.
                                                                                  0
                                                                                  >, хотя реально требует только один метод из него

                                                                                  Вот это дейтсвительно весомый аргумент. Спасибо!
                                                                              +1
                                                                              У нас было 3-4 успешных проекта, пока не пришли дипломированные программисты, и не сделали современный рефакторинг (слово-то какое). Вывод- они полезны, но держите их в черном теле, не давайте садиться на голову в угоду все учитывающим архитектурам.
                                                                                –4
                                                                                Для кого эти статьи, с пеной у рта доказывающие, что ваш подход отвратителен, делайте так, а не эдак? Больше похоже на типичные американские агитки, чем на осмысленный материал (в т.ч. выделение жирным основных мыслей для самых маленьких ). Есть же книги, вроде Чистого кода, где прекрасно объясняется как делать надо, причем на гораздо более высоком уровне и без громких фраз.
                                                                                  +5
                                                                                  Думал что-то стоящее — а тут ерунда.

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

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

                                                                                  Ну и в третьих, что касается утверждений в тексте. Например, вот это красиво сказано:
                                                                                  А если вы не верите, что можете писать плохой код, просто откройте какой-то проект, который написали пару лет назад. Уверен, что вам этот код покажется ПЛОХИМ.


                                                                                  Вот это только не потому, что код 2-3 летней давности «полное г». Просто я расту профессионально, кроме того выходят новые версии инструментов разработки, языков и библиотек. Разумеется, все это вместе зачастую позволяет написать старый код более эффективно, надежнее и быстрее. Странно если было бы все наоборот. В общем, тут явная подмена понятий.
                                                                                    –1
                                                                                    Когда прочитал, такие же мысли пришли в голову.
                                                                                    Эта статья — источник дешевой популярности.
                                                                                    1. Отвлекает закомментированный код? Может вся проблема в редакторе, который ты используешь?
                                                                                    Во-вторых, иногда полезно знать, от каких решений пришлось отказаться именно в этом участке кода.
                                                                                    2. «Привлекая нового разработчика к проекту будьте уверены — он потратит время на их анализ». Это высосано из пальца. Закомментированный код нужно изучать, если ты собираешься внести изменения в этот участок кода.
                                                                                    У нас есть система версий? Не думаю, что она сильно поможет подглядывать в исправленный код.
                                                                                    3. «Едва ли вы будете использовать свой код в других проектах.» А почему нельзя обобщать, чтобы использовать один и тот же код в одном проекте?
                                                                                    4. «Разработка с нуля полезна только в целях обучения, но никак не в проекте, который надо сдавать через 2 недели.» В хорошей статье такие ограничения должны быть оговорены в начале главы.
                                                                                    5. «он ужасен! я могу сделать это нааамного лучше!» и «будет намного лучше, если я напишу это с нуля». А почему бы нет? Если у меня буду все основания это сделать, я это сделаю. Другое дело, что «оставьте эго дома» — это не тот критерий, который надо применять при рефакторинге или при анализе кода.
                                                                                    О чем эта статья, друзья?
                                                                                      +2
                                                                                      Вот тут не соглашусь.
                                                                                      > У нас есть система версий?
                                                                                      > Не думаю, что она сильно поможет подглядывать в исправленный код.

                                                                                      Удаленный код в комментах все же мешает. А вот контроль версий помогает, вернее утилиты сравнения исходных файлов. Как минимум снимает вопросы — закомментированное было выкинуто за раз или это разные правки, когда и какой код был вставлен и вместо чего, кто это сделал. Все это можно конечно писать в комментах, но тут уже влазит человеческий фактор, да и опять же получается много текста.
                                                                                    +1
                                                                                    Смотрю на свой код более, чем пятилетней давности и он хорош. Хотя 5% кода можно отрефакторить получше, с учетом новых возможностей языка.

                                                                                    Он вполне успешно используется и в новых проектах, составляя где то 30% кода.
                                                                                    Сторонние компоненты — это еще 40% кода.
                                                                                    И 30% кода — это новый код — логика приложения.

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

                                                                                    Конечно, много зависит и от языка (не уверен, что код на Objective C легко закомпилится эдак через пять лет), и от культуры написания кода, и от документирования, и от достаточного количества примеров использования, которые помогают разобраться с кодом новым разработчикам.
                                                                                      0
                                                                                      А что может случиться с Objective-C за пять лет?
                                                                                        0
                                                                                        Часто методы становятся deprecated в новых SDK.
                                                                                        Появление ARC тоже прошло не особо гладко — нельзя просто подключить юнит без ARC в проект с включенным ARC. Приходится подключать как отдельно собираемую либу.

                                                                                        А что появится через пять лет — я не берусь предсказать. Наверно допилят работу с памятью, может упростят слои CF NS до какого нибудь упрощенного варианта с синтаксическим сахарком в языке.
                                                                                          0
                                                                                          Ну вроде migration tool вышел с xcode 4.3.3
                                                                                      0
                                                                                      Спасибо, Кэп! :)
                                                                                        0
                                                                                        Есть еще одна ужасная привычка — оптимизировать код.
                                                                                        Все мы на этапе обучения прочитали, что быстый код — это лучше, чем медленный. И многие искренне верят, что «если я напишу этот кусочек так, чтобы он выполнялся на 30% быстрее, и тот тоже, и тот, то моя программа будет работать на 30% быстрее.»
                                                                                        На практике получается, что тормозят всего пару бутылочных горлышек. Часто не в разрабатываемом коде. Изза кривой архитектуры. А проект нужно сдавать и переделывать уже некогда. Потому что время потратили на оптимизацию того, что не тормозит, и кода наворотили такого, что разобраться в нем уже сложно.
                                                                                          0
                                                                                          Преждевременная оптимизация — да, может нанести урон архитектуре.
                                                                                          Но оптимизация в целом — как же без нее? Порой без нее просто невозможно.
                                                                                            0
                                                                                            оптимизация — вообще процесс итерационный. серия доработок — анализ, что теперь можно оптимизировать. программист всегда думает, как можно написать красивее, ускорить, уменьшить и пр. конечно, если надо что-то сдать вчера, то сначала надо делать необходимое, а потом уже доводить до совершенства.
                                                                                              0
                                                                                              Возможно я не ясно выразился. Я именно про преждевременную оптимизацию, а не про оптимизацию по результатам измерений.
                                                                                              0
                                                                                              Тут есть один чудесный нюанс, который мало где учитывают. Если пройти этап «оптимизации всего и вся» то многие паттерны оптимального кода становятся привычными, и впоследствии их использование вообще никаких усилий не требует — на автопилоте подбираешь нужный контейнер под алгоритмику и тому подобное. То есть оптимизация во многом получается дармовой.
                                                                                              +1
                                                                                              Старый код комментируется и вместо него пишется новый

                                                                                              довелось работать в одной достаточно крупной компании, где код в комментариях не удаляли, поскольку это было частью корпоративной политики. рядом с каждым закомментированым кусочком кода писался номер таски, в рамках которой этот код закоментили, и несколько предложений мотивации, почему это сделано. и всё это коммитилось в ClearCase! таким образом, файл main.jsp там вырос до десятков килобайт и содержал практически геологический срез истории компании, хотя рабочего кода там было строк пять-десять. как я счастлив, что там больше не работаю)
                                                                                                +2
                                                                                                я в конторе, где 60 программистов для множества заказчиков правят одни и те же пакеты и библиотеки, целые войны правок в комментариях видела.

                                                                                                // дата — ФИО — номер таска — фрагмент закомментирован — цитата из таска, на основании которой это сделано
                                                                                                // дата — ФИО — номер таска — фрагмент раскомментирован — цитата из таска, на основании которой это сделано

                                                                                                или что-то типа

                                                                                                // поставил заглушку, потому что Лена не хочет переписывать свою библиотеку

                                                                                                там что-то типа твиттеров в итоге образовалось в часто используемых местах. иногда зачитаешься и забываешь, что делать-то хотела.
                                                                                                  0
                                                                                                  возьмусь предположить, что это из-за нарушения принципа SRP из упомянутого выше SOLID
                                                                                                +5
                                                                                                А мне нравится моя студенческий (и даже школьный) код. Я тогда ничего не знал про ООП и просто писал программы :)
                                                                                                  0
                                                                                                  Считаю что хорошему коду, ну а также и правильной архитектуре всей системы способствует применение таких инструментов как UML. При его использовании — мы можем взглянуть на архитектуру/код с высоты птичьего полета и увидеть многие недостатки (если таковы имеются). Это я обнаружил на собственном примере, когда взглянул на код проекта, который я реализовывал 3 года назад без применения UML. Когда пишешь код, постепенно углубляешься все больше и больше в реализацию, теряя при этом свежий взгляд на систему в целом, что может привести ко многим концептуальным ошибкам.
                                                                                                    0
                                                                                                    да, согласен с вами. но тут я обнаружил проблему — документация устаревает намного быстрее кода, будь то текст или диаграммы. нормального способа актуализировать uml при изменении кода я не нашел. может быть, вы подскажете?
                                                                                                      0
                                                                                                      Честно говоря, пока что, я тоже не нашел нормального метода, я просто стараюсь перед каждым комитом (в котором есть значительные изменения) править uml. Мне тоже было бы интересно узнать метод получше)
                                                                                                    +1
                                                                                                    Срочная ссылка на рудники за такой -dealloc
                                                                                                      0
                                                                                                      Статья как для меня написана, как раз преодолеваю барьер с IB. Всё ещё его ненавижу и создаю через код… но может уже и не стоит?
                                                                                                        0
                                                                                                        А преодолеть барьер 100500-CGRectMake'ов было проще?
                                                                                                          0
                                                                                                          Как-то уже не задумываюсь, особенно если есть PSD. Всё равно придётся из фотошопа в IB копипастить точные X, Y — так какая разница?
                                                                                                        +2
                                                                                                        Плохие привычки программистов:
                                                                                                        * Читать хабр вместо того, что бы работать.
                                                                                                        * Перед самым концом рабочего дня задеплоить результаты труда в продакшн и не проверить
                                                                                                        * С недоверием относиться к высказываниям пользователей относительно функционирования программ
                                                                                                          0
                                                                                                          «чем меньше кода вы напишете, тем меньше багов придется отлавливать»

                                                                                                          Хорошо сказано, смачно!
                                                                                                            0
                                                                                                            В меме ошибка, порядок слово неверный, должно быть tell me how many times you are going to reuse it. Потому что reported question.

                                                                                                            Only users with full accounts can post comments. Log in, please.