Макроподобные структуры в C # == OR == Как выделить функцию, содержащую return - PullRequest
3 голосов
/ 23 июня 2010

Я никогда не думал, что мне понадобится что-то подобное (поскольку макросы плохие, верно?), Но, оказывается, я это делаю.

Я повторяю следующую модель снова и снова вмой код, десятки, если не сотни раз:

object Function() {
    this.Blah = this.Blah.Resolve(...);
    if(this.Blah == null)
        return null;
    if(this.Blah.SomeFlag)
        return this.OtherFunction();

    // otherwise, continue processing...
}

Я хотел бы написать что-то вроде этого:

object Function() {
    this.Blah = this.Blah.Resolve(...);
    VerifyResolve(this.Blah);

    // continue processing...
}

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

Как я могу избежать ненужного повторения и строить будущее?легче изменить этот шаблон?

Ответы [ 7 ]

3 голосов
/ 23 июня 2010

Если вам часто приходится сравнивать с null, вы можете использовать шаблон нулевых объектов .По сути, вы создаете объект, который представляет нулевой результат.Затем вы можете вернуть этот объект вместо null.Затем вам придется адаптировать остальную часть вашего кода, чтобы включить поведение для нулевого объекта.Возможно, что-то в этом роде:

MyClass Function() 
{  
  this.Blah = this.Blah.Resolve(...); 
  VerifyResolve(this.Blah);

  // continue processing...  
}  

MyClass VerifyResolve(MyClass rc) 
{
  // ...
  return rc.Blah.SomeFlag ? rc.OtherFunction() : rc;
} 

NullMyClass : MyClass {
  public override bool SomeFlag { get { return false; } }
}
1 голос
/ 23 июня 2010

Без обид, но есть пара запахов кода:

this.Blah = this.Blah.Resolve(...);

В частности, эта строка заставит меня начать процесс переосмысления.

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

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

EDIT

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

class ExampleExpr{
    StatefulData data ... // some variables that contain the state data
    BladhyBlah Blah { get; set; }

    object Function(params) {
        this.Blah = this.Blah.Resolve(params);
        ....
    }
}

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

class ExampleExpr{
    StatefulData data ... // some variables that contain the state data

    object Function(params) {
        BlahdyBlah blah = BlahdyBlah.Resolve(params, statefulData);
    }
}

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

Это означает, что мы можем тиражировать любую функциональность при тестировании, просто вызывая функцию (параметры) с определенной функцией Setup () для создания statefulData и определенного набора параметров.

Теоретически это менее эффективно (так как новый BlahdyBlah создается при каждом вызове фабрики), но возможно кэшировать экземпляры BlahdyBlah с конкретными данными и делиться ими между вызовами фабрики (при условии, что у них нет других методов, которые влияют на их внутреннее состояние). Тем не менее, он НАМНОГО более дружественен к обслуживанию и с точки зрения тестирования полностью выводит из строя данные о состоянии.

Это также помогает устранить вашу исходную проблему, так как, когда мы не полагаемся на внутреннюю переменную экземпляра, мы все можем Разрешить (params, statefulData) извне из Function (params) и просто не вызывать Function (params), если любой бла == null или blah.SomeFlag == SomeFlag. Что угодно. Таким образом, перемещая это за пределы метода, нам больше не нужно беспокоиться о возвратах.

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

1 голос
/ 23 июня 2010

Может быть, лучше:

if (!TryResolve(this.Blah)) return this.Blah;

, где TryResolve устанавливает значение this.Blah в значение null или this.OtherFunction и возвращает соответствующее значение bool

1 голос
/ 23 июня 2010

Вы могли бы сказать что-то вроде:

object Function()
{ 
    this.Blah = this.Blah.Resolve(...);
    object rc;
    if (!VerifyResolve(out rc)
       return rc;

    // continue processing... 
} 

bool VerifyResolve(out object rc)
{
    if(this.Blah == null)      
    {
        rc = null;
        return true;
    }
    if(this.Blah.SomeFlag)      
    {
        rc = this.OtherFunction();
        return true;
    }
    rc = null;
    return false;
}
0 голосов
/ 05 июля 2010

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

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

struct Rc
{
  internal object object;
  internal Rc(object object) { this.object = object; }
}

object Function() 
{  
    this.Blah = this.Blah.Resolve(...); 
    Rc? rc = VerifyResolve();
    if (rc.HasValue)
       return rc.Value.object; 

    // continue processing...  
}  

Rc? VerifyResolve() 
{ 
    if(this.Blah == null)       
    { 
        return new Rc(null); 
    } 
    if(this.Blah.SomeFlag)       
    { 
        return new Rc(this.OtherFunction()); 
    } 
    return null; 
} 
0 голосов
/ 23 июня 2010
object Function() { 
    this.Blah = this.Blah.Resolve(...);
    object result;
    if (VerifyResolve(this.Blah, out result))
        return result;

    // continue processing... 
} 
0 голосов
/ 23 июня 2010

Лучше расширить класс Blah с помощью метода расширения VerifyResolve.Если у вас есть источник Blah, то может быть лучше создать какой-нибудь интерфейс IVerifyResolveble и заставить Blah реализовать его.

...