По мотивам обсуждения заметки Code reuse в блоге Алёна C++.
Повторное использование кода – это сложный вопрос. Он сложен даже для маленьких команд. Еще сложнее он в больших командах и больших организациях с развитой бюрократией. Но здесь у меня мало опыта. Поэтому я расскажу всего об одном простом приеме, который позволяет выявить повторно используемый код.
На мой взгляд, повторно используемый код бывает двух типов. Первый тип – это код, для которого с самого начала понятно, что он может использоваться в нескольких местах. Как правило, это библиотеки, решающие какие-то четко определенные задачи: алгоритмы сортировки, генераторы случайных чисел, регулярные выражения, средства работы с БД и даже большие фреймворки вроде MFC, Qt или wxWidgets. С этим типом кода все понятно.
Второй тип – это код, о котором изначально не думали как о повторно используемом. Но который затем распространяется между проектами посредством copy-and-paste и, тем самым, приводит к дублированию.
Такой код пишется для решения какой-то конкретной прикладной задачи. Например, в модуле приема данных по сети возникает необходимость залогировать фрагмент сбойного пакета. Для чего разработчик пишет несколько вспомогательных процедур, возможно, завязывая их на конкретный тип логгера.
Если потом в другом модуле потребуется выполнить аналогичное логирование, то другой разработчик может взять процедуры логирования, написанные первым разработчиком. В лучшем случае они заработают без изменений (все-таки в рамках одного коллектива вряд ли будут использоваться разные логгеры), в худшем – потребуют небольшой доработки напильником.
По хорошему, уже на этой стадии было бы разумным выделить скопированный из другого проекта код в повторно используемый компонент (библиотеку или сниппет). Но это в идеале, на практике все может быть сложнее. Особенно, когда ты берешь код из чужого проекта, из его зафиксированной версии.
Итак, фрагмент чужого кода взят к себе. Потенциально, он может потребоваться еще раз, но об этом пока не известно. Имеет смысл специальным образом пометить этот фрагмент. Ведь когда он потребуется еще раз, то разработчик будут знать, что код уже был скопирован ранее. Я помечаю такой код комментарием:
//FIXME: данный код взят из проекта XYZ.
...
И, когда мне приходится копировать его еще в одно место, данный комментарий напоминает мне, что однажды я уже поленился или не смог выделить его в повторно используемый компонент. Значит, если сейчас для этого есть шанс, я должен этот шанс реализовать.
Примечание 1. По хорошему, аналогичный комментарий следовало бы ставить еще и в месте, откуда фрагмент кода был взят. Но это не всегда возможно.
Примечание 2. Наверное, кроме метки FIXME, стоило бы снабжать такой код еще какой-то легко запоминающейся меткой. Например, REUSE. Но у меня эта практика не прижилась, т.к. есть привычка помечать код, подлежащий дальнейшей доработке маркером FIXME (он, кстати, выделяется в ViM-е другим цветом), а дополнительные маркеры я забываю ставить.
Примечание 3. Опыт показывает, что если код был скопирован один раз, то не обязательно он окажется повторно используемым. Зачастую бывает, что в дальнейшем как оригинальный, так и результирующий несколько раз меняется и развивается в противоположных направлениях. Поэтому лучше дождаться, пока код будет скопирован без значительных изменений в третий раз.
Примечание 4. Для того, чтобы найти исходный фрагмент кода для copy-and-paste, нужно знать, что он есть :) В собственных проектах с этим еще как-то можно разобраться. А вот с чужими как быть? Временами спасает процедура code review. После того, как выполнишь рецензию чужого кода в голове останутся воспоминания о его содержимом. В нужный момент они могут сработать.
я стараюсь таких комментариев не оставлять, а сразу выделять компоненты и делать нормальный reuse (с написанием тестов, кстати).
ОтветитьУдалитьВремя, конечно, тратится, но в результате у нас получается готовый девайс, который можно юзать без опаски.
А с твоим подходом при накоплении критической массы придется просматривать все места, прежде чем выделишь компоненту.
Более того, так как никакой синхронизации нет, код будет в каждом месте эволюционировать по-своему, что в результате может сделать слияние невозможным.
>я стараюсь таких комментариев не оставлять, а сразу выделять компоненты и делать нормальный reuse (с написанием тестов, кстати).
ОтветитьУдалитьЕсли на это есть время, то надобности в подобных комментариях, понятное дело, нету.
>Более того, так как никакой синхронизации нет, код будет в каждом месте эволюционировать по-своему, что в результате может сделать слияние невозможным.
Имхо, лучше позволить однажды скопированному коду эволюционизировать в каждом месте по своему, чем заниматься эволюцией обобщенного кода.
(Напомню, что речь идет о коде, который изначально не планировался как reusable)
Вот, например, возникла возможность логировать проблемную платежную транзакцию. Написали сначала в одном месте (модуль приема транзакций), затем использовали в другом месте (модуль отсылки транзакций партнеру). Некоторое время использовался одинаковый код и там, и там. Потом потребовалось в первом модуле добавить еще и логирование внешнего параметра A, а во втором месте -- логирование дополнительного параметра B и не логирование атрибута C. Реюзабельность здесь идет лесом, а разработчик делает двойную работу -- сначала по созданию reusable компонента, затем -- по его уничтожению.
ну, эволюция в каждом месте еще подразумевает багфикс, т.е. в одном месте найденный баг правится, а в другом(их) - продолжает жить. Примеров тому через меня прошло досаточно, чтоб бежать от копи-пейста сломя голову.
ОтветитьУдалитьА насчет расхождений в использовании - компонент просто бьется на еще более мелкие части либо обрастает опциями (тут уже по ситуации надо смотреть, что лучше подходит). Причем пару раз такое проделаешь - и замечаешь закономерность, которой раньше не видел, в результате чего подсистема слегка перепроектируется и становится более логичной и законченной.
>Примеров тому через меня прошло досаточно, чтоб бежать от копи-пейста сломя голову.
ОтветитьУдалитьМне, наверное, повезло больше.
>Причем пару раз такое проделаешь - и замечаешь закономерность, которой раньше не видел, в результате чего подсистема слегка перепроектируется и становится более логичной и законченной.
Ну да, если задача вырисовывается, то можно делать и универсальное решение. Но это бывает, обычно, при втором copy-and-paste, не раньше.