Нужны идеи рефакторинга для Arrow Anti-Pattern - PullRequest
12 голосов
/ 20 сентября 2008

Я унаследовал монстра.

Он маскируется под приложение .NET 1.1, обрабатывающее текстовые файлы, которые соответствуют стандартам Healthcare Claim Payment (ANSI 835), но это чудовище. Обрабатываемая информация относится к заявкам на медицинское обслуживание, EOB и возмещению. Эти файлы состоят из записей, которые имеют идентификатор в первых нескольких позициях, и полей данных, отформатированных в соответствии со спецификациями для этого типа записи. Некоторые идентификаторы записей - это идентификаторы контрольного сегмента, которые разграничивают группы записей, относящихся к конкретному типу транзакции.

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

Этот вложенный if имеет длину 715 строк. Да это правильно. Семьсот и пятнадцать подростков. Я не эксперт по анализу кода, поэтому я скачал несколько бесплатных инструментов для анализа и получил оценку McCabe Cyclomatic Complexity, равную 49. Они говорят, что это довольно большое число. Высокий, как в подсчете пыльцы в Атланте, где 100 является стандартом для высоких, а в новостях говорится: «Сегодня количество пыльцы составляет 1523». Это один из лучших примеров анти-паттерна Arrow, который я когда-либо имел честь видеть. На самом высоком уровне отступ составляет 15 вкладок.

Мой вопрос: какие методы вы бы предложили для рефакторинга или реструктуризации такой вещи?

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

Возможно, существует шаблон проектирования, который может быть полезен. Будет ли Chain of Command подходить к этому? Имейте в виду, что он должен оставаться в .NET 1.1.

Спасибо за любые идеи.

Ответы [ 7 ]

20 голосов
/ 20 сентября 2008

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

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

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

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

Теперь Рефакторинг!

Несколько вещей, которые я сделал, которые я нашел полезными:

Инвертировать if операторов

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

if (someCondition)
{
  100+ lines of code
  {
    ...
  }
}
else
{
  simple statement here
}

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

Метод извлечения

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

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

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

Рефакторинг к шаблонам - отличная книга для объяснения шаблонов, которые полезны в этих ситуациях.

Вы пытаетесь съесть слона, и нет другого способа сделать это, кроме одного укуса за раз. Удачи.

2 голосов
/ 20 сентября 2008

Я бы начал с неограниченного использования метода извлечения. Если у вас его нет в вашей текущей Visual Studio IDE, вы можете получить стороннее дополнение или загрузить свой проект в более новую VS. (Он попытается обновить ваш проект, но вы будете осторожно игнорировать эти изменения вместо того, чтобы регистрировать их.)

Вы сказали, что у вас есть код с отступом 15 уровней. Начните примерно с 1/2 выхода, и метод извлечения. Если вы можете придумать хорошее имя, используйте его, но если вы не можете, извлеките все равно. Опять пополам. Вы не идете за идеальной структурой здесь; Вы пытаетесь разбить код на части, которые поместятся в вашем мозгу. Мой мозг не очень большой, поэтому я буду продолжать ломать и ломать, пока он больше не будет болеть.

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

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

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

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

Здесь вам поможет партнер по сопряжению.

2 голосов
/ 20 сентября 2008

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

2 голосов
/ 20 сентября 2008

A конечный автомат кажется логичным местом для начала, и использование WF, если вы можете качать его (звучит так, как будто вы не можете).

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

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

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

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

1 голос
/ 20 сентября 2008

Иногда я комбинирую шаблон состояний со стеком.

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

Он отлично работает для обработки XML с помощью парсера SAX (обработчик содержимого просто выдвигает и выводит состояния, чтобы изменить свое поведение при вводе и выходе элементов). ЭОД также должен подходить для этого подхода.

1 голос
/ 20 сентября 2008

На Coding Horror был довольно хороший пост в блоге . Я только однажды сталкивался с этим анти-паттерном и просто следовал его шагам.

1 голос
/ 20 сентября 2008

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

...