Методы упрощения некрасивых вложенных деревьев if-else в C # - PullRequest
5 голосов
/ 24 октября 2009

Иногда я пишу некрасивые операторы if-else в C # 3.5; Мне известны некоторые разные подходы к упрощению этого с помощью табличной разработки, иерархии классов, анонимных методов и некоторых других. Проблема в том, что альтернативы все еще менее распространены, чем написание традиционных уродливых операторов if-else, потому что для этого нет соглашения.

Какая глубина вложенного if-else является нормальной для C # 3.5? Какие методы вы ожидаете увидеть вместо вложенных if-else первым? второй?

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

Это все еще смесь таблиц, управляемых данными dev. идеи и сопоставление с образцом.

то, что я ищу, это расширение для C # таких подходов, как это для сценариев (C # 3.5 скорее похож на сценарии) http://blogs.msdn.com/ericlippert/archive/2004/02/24/79292.aspx

Ответы [ 11 ]

8 голосов
/ 25 октября 2009

Хороший вопрос. «Условная сложность» - это кодовый запах. Полиморфизм твой друг.

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

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

double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
};  

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

double disabilityAmount() {
    if (isNotEligableForDisability()) return 0;
    // compute the disability amount

Другие ценные методы рефакторинга , связанные с условными выражениями, включают Разобрать условное , Заменить условное с посетителем , Шаблон спецификации и Обратное Условие .

4 голосов
/ 25 октября 2009

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

http://en.wikipedia.org/wiki/Decision_table

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

Предложение Мириам (см. Выше) увлекательно, даже изящно, как «концептуальное искусство»; и я на самом деле собираюсь попробовать это, пытаясь «заключить в скобки» мое подозрение, что это приведет к тому, что код сложнее поддерживать.

Моя прагматичная сторона говорит, что здесь нет ответа "один размер подходит всем" в отсутствие довольно специфического примера кода и полного описания условий и их взаимодействий.

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

лучший, Билл

4 голосов
/ 24 октября 2009

Simple. Возьми тело if и сделай из него метод.

Это работает, потому что большинство операторов if имеют форму:

if (condition):
   action()

В других случаях, более конкретно:

if (condition1):
   if (condition2):
      action()

упростить до:

if (condition1 && condition2):
   action()
2 голосов
/ 25 октября 2009

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

   switch (testValue)
   {
      case = 1:
         // do something
         break;
      case = 2:
         // do something else
         break;
      case = 3:
         // do something more
         break;
      case = 4
         // do what?
         break;
      default:
         throw new Exception("I didn't do anything");
   }

Если вы вкладываете, если операторы глубже 3, то вы, вероятно, должны принять это как признак того, что есть лучший способ. Вероятно, как предложил Avirdlg, разделив вложенные операторы if на 1 или более методов. Если вы чувствуете, что абсолютно застряли с несколькими операторами if-else, то я бы обернул все операторы if-else в один метод, чтобы он не испортил другой код.

2 голосов
/ 25 октября 2009

Я знаю, что это не тот ответ, который вы ищете, но без контекста очень сложно ответить на ваши вопросы. Проблема в том, что способ рефакторинга такой вещи действительно зависит от вашего кода, что он делает и чего вы пытаетесь достичь. Если бы вы сказали, что проверяете тип объекта в этих условных выражениях, мы могли бы выбросить ответ типа «использовать полиморфизм», но иногда вам действительно просто нужны некоторые операторы if, а иногда эти операторы могут быть преобразован в нечто более простое. Без примера кода трудно сказать, к какой категории вы принадлежите.

2 голосов
/ 24 октября 2009

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

foobarString = (foo == bar) ? "foo equals bar" : "foo does not equal bar";

Попробуйте эту статью для получения дополнительной информации.

Это не решит всех ваших проблем, но это очень экономично.

1 голос
/ 26 октября 2009

Не ответ C #, но вы, вероятно, хотели бы сопоставления с образцом. При сопоставлении с образцом вы можете использовать несколько входов и выполнять одновременное сопоставление для всех из них. Например (F #):

let x=
  match cond1, cond2, name with
  | _, _, "Bob"     -> 9000 // Bob gets 9000, regardless of cond1 or 2
  | false, false, _ -> 0 
  | true, false, _  -> 1
  | false, true, _  -> 2
  | true, true, ""  -> 0 // Both conds but no name gets 0
  | true, true, _   -> 3 // Cond1&2 give 3

Вы можете выразить любую комбинацию, чтобы создать совпадение (это просто царапает поверхность). Тем не менее, C # не поддерживает это, и я сомневаюсь, что это произойдет в ближайшее время. Между тем, есть некоторые попытки попробовать это в C #, например: возможно, один подойдет вам.

1 голос
/ 25 октября 2009

Иногда я обнаруживаю, что переворачиваю условие, за которым следует return; несколько таких тестов подряд могут помочь уменьшить вложенность if и else.

1 голос
/ 24 октября 2009

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

Если предложения If Else выполняют отдельные фрагменты функциональности. и условия являются сложными, упростите путем создания временных логических переменных для хранения значения true / false сложных логических выражений. Эти переменные должны быть соответствующим образом названы, чтобы представлять деловой смысл того, что вычисляет сложное выражение. Затем используйте логические переменные в синтаксисе If else вместо сложных логических выражений.

0 голосов
/ 25 октября 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...