четверг, 26 мая 2011 г.

Синдром рефакторинга

arch2 Бытует мнение, что программные системы, будучи объектом не совсем материальным, не поддаются старению. И если говорить о старении физическом, то действительно, шансы на то, что буковка “o” в имени класса вдруг от старости ссохнется и превратится в букву “c” – действительно малы. Но вместо старения физического, программные системы стареют морально.  Со временем накапливается груз ошибок за счет неточностей в исходных требованиях, непонимания требований самим заказчиком, архитектурных ошибок или неудачных компромиссных решений; да и ошибки поменьше, типа слабопонятного кода, его высокой связности, отсутствия юнит-тестов и комментариев делают свое черное дело. Все это приводит к накоплению технического долга (о котором шла речь в прошлый раз), из-за которого при добавлении новой возможности в систему приходиться платить «проценты» в виде более высокой стоимости реализации и более низкого качества получаемого результата.

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

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

Симптом 1. Чужой код – г#$но

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

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

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

Симптом 2. Стремление к идеальному коду

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

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

Симптомы 3. «Я дерусь, потому что я дерусь»

С течением времени, с высоты своего собственного нового профессионального опыта, благодаря более четкому пониманию требований пользователя и, пониманию своей собственной системы, даже свой код начинает выглядеть ужасным. Сколько раз вы ловили себя на мысли: «Вот, блин, кто же эту ерунду написал?» и потом с удивлением узнавали, что этот кто-то – это вы.

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

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

Симптом 4 .. 1001

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

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

---------------------------------------

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

14 комментариев:

  1. Прямо вижу некоторых коллег - бывших и нынешних :)

    ОтветитьУдалить
  2. Спасибо Вам за статьи и интересные темы.
    Попробую повозражать.

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

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

    Симптом 3. Это да. Цель оправдывает средства. Но далеко не любые средства.

    Симптом 4. "Код и архитектура должны быть хорошими и гибкими ровно настолько, насколько это нужно....". Это тупиковый путь. Это в конце концов просто скучно:) Давайте всё же не "ровно настолько", а позволим себе разумную слабость "чуть-чуть лучше, чем это нужно...".

    ОтветитьУдалить
  3. симптом 1 наиболее частый. :))) Сам порой такое делаю...к сожалению.

    ОтветитьУдалить
  4. Есть еще момент, который мне кажется надо отметить. Утверждение гуру, о необходимости непрерывного рефакторинга приводят иногда к странным поступкам. Разработчик начинает искать проблемы там, где их нет. Пример, есть какая-то часть приложения, которая работает хорошо и багов от пользователей системы на нее нет(да, такое тоже бывает :)). Но некоторые ретивые разработчики считают, что код в этом месте написан неоптимально (формально - это тех. долг). Однако, есть другие задачи, которые делать необходимо здесь и сейчас. И тут возникает дилемма - что делать? Решать насущные задачи или заняться любимым делом - рефакторингом. ИМХО, только тим-лид может разрулить данную ситуацию, потому как его спрашивают и он отвечает за весь проект в целом. В зависимости от близости релиза, стратегия должна отличаться. Ближе к выпуску - она должна становиться более консервативной. В начале разработки фичи можно и куража набраться.

    P.S. Речь идет о работе в команде конечно.

    ОтветитьУдалить
  5. Поймал себя на мысли, что за всю свою карьеру (долгую и всегда в IT компаниях) ни разу не видел команды разработчиков. Фирмы специализируются на бизнес-софте (докоборот, crm/erp). Всегда получается что есть один "вол", что тянет весь проект и помощники (полезные и вредные). Этот "вол" выполняет в итоге 80% программы и таким образом помощники ему и не нужны. Только время тратить на постановку задач и контроль.

    ОтветитьУдалить
  6. To Anton Zubarev: Я не знаю, что за команды в которых вы работали. Команда - это коллектив, где сотрудники работают над проектом, где есть совместное владение кодом, есть code-review. Есть в конце концов технология разработки. Похоже, Вам не повезло и Вы не работали в компаниях где все это есть. По поводу 80% - вообще простите, смешно. Очень хочется посмотреть на того "вола" который написал 80% Visual Studio. Серьезные, крупные проекты всегда делаются командами. У одного человека банально не хватит ни времени, ни сил.

    ОтветитьУдалить
  7. 2 eugene
    Так ведь я написал, что речь идет о бизнес-проектах (докоборот, crm/erp), то есть разработку в готовой среде, например, MS CRM, Axapta, 1C, SharePoint. В таком разрезе сам программный код весьма прост. Сложность заключается в том, как выполнить противоречивые бизнес-требования...
    С другой стороны, вы ведь вряд ли пишите VS, SQL Server или компилятор.

    ОтветитьУдалить
  8. @Aquary: Я вот до сих пор помню ситуацию, когда один из моих коллег раза четыре рефакторил свой кусок программы, при этом не улучшая ее качество ни на грош.

    @makajda: Я бы не относился столь серьвезно к первому симпому. Намомню, что его корни лежат в другом очень известном симптоме Not Inveted Here, который зарыт в глубины подсознания большинства программистов.

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

    ОтветитьУдалить
  9. @eugene (по поводу первого твоего комментария): речь о рефакторинге обычно заходит в тот момент, когда в какой-то конкретный кусок системы нужно внести изменения. И тогда появляется выбор, "день потерять, а потом за пять минут долететь", т.е. вначале отрефакторить, а потом внести изменения или вносить изменения в систему как есть только увеличив при этом технический долг. И ты безусловно прав, риск у подхода с рефакторингом все же выше (ибо расхерячить можно ой-ой-ой как), поэтому перед релизом разумно пойти по более протоптанной дорожке.

    ОтветитьУдалить
  10. @Anton: А вот я с вашим заявлением полностью согласен. Более того, моя практика показывает, что довольно часто в команде таки присутствует человек, которые делает большую часть работы. Правило 80/20 никто не отменял: 80 процентов работы выполняется 20-ю процентрами разработчиков. А если команда всего состоит из 5 человек, то вот и получится, что один человек выполняет 80% работы.

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

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

    ОтветитьУдалить
  11. @сергей: Как был пост, про ОДНОГО вола - так я на него и ответил:). Я согласен, что в команде разные сотрудники работают с разной производительностью. Но тут надо учитывать и какие при этом они задачи решают. Я так понимаю, что различные технологические приемы разработки ПО и были придуманы, чтобы пресловутые 80/20 стремились играть на понижение. Об этом собственно и написал. В моем случае, возможно повезло с тим лидом. Он реально эксперт в предметной области, но он грамотно распределяет задачи на команду. Поэтому можно сказать, что он вол, тянущий команду, но он тянет ее на другом уровне.

    P.S. Я работаю в www.itv.ru. У нас команда из 8 человек, плюс отдельный ресурс тестировщики и локализаторы. Разрабатываем мы не VS конечно, но продукты, которые один человек вряд ли потянул при всем желании.

    ОтветитьУдалить
  12. "Правило 80/20 никто не отменял" - это не правило, а миф. У нас команда работает тройками: верстальщик, серверный программист и тот, кто собирает все это в кучу. Кто там делает 80% работы?

    ОтветитьУдалить
  13. @elecutree:
    Правило 80/20 является эмпирическим и основано оно прежде всего на наблюдениях и нормальном законе распределения (помните такой колокольчик?). Так вот, нормальный закон распределения говорит лишь о том, что для большей части данных некоторое правило является истинным, но это не значит, что оно выполняется для всех.

    Кроме того, правило 80/20 в данном контексте применяется прежде всего к командам, в которых есть более 1 человека, выполняющего однотипную работу (т.е. более одного разработчика). Говорить об эффективности работника тяжело даже тогда, когда каждый из них них занимается более или менее одним делом (т.е. все - программеры), когда же каждый человек делает что-то совершенно отличное от других, то процесс измерения их эффективности вообще теряет смысл. А когда их трое, и каждый делает свое дело - то естественно, что это правило применяться не будет. Да и вообще, вы же наверняка хорошо сработанная команда, в которой четко распределены как формальные, так и неформальные роли и четко знаете, что делаете и к чему стремитесь? Так вот, открою секрет, это далеко не всегда так. Более того, зачастую это вообще нифига не так.

    Но вот делать обобщенные выводы исходя из того, что лично в вашей команде чего-то нет или что-то делается не так, как у большинства, ИМХО, весьма поспешное решение. Я основывался как на своем собственном скромном опыте, и исследованиях разных умных дядек, в частности на Демарко с Листером, которые не однократно приводили примеры исследований в которых говорилось, что эффективность труда программистов в одной компании может отличаться на порядок.

    ОтветитьУдалить