Уменьшите литье и улучшите укладку - PullRequest
4 голосов
/ 31 августа 2010

Рассмотрим следующий (неприятный) код:

    /// <summary>
    /// Calls matching process error code on response.Code
    /// </summary>
    /// <param name="response">Actually will be of type Response or extend it</param>
    /// <returns>true for successful response, false otherwise</returns>
    private static bool ProcessErrorCode(object response)
    {
        bool isOkay = false;
        const string unknown = "UNKNOWN";
        string errCode = unknown;
        if (response.GetType() == typeof(Response<AResponseCode>))
        {
            AResponseCode code = ((Response<AResponseCode>)response).Code;
            isOkay = code == AResponseCode.Ok;
            errCode = code.ToString();
        }
        if (response.GetType() == typeof(Response<BResponseCode>))
        {
            BResponseCode code = ((Response<BResponseCode>)response).Code;
            isOkay = code == BResponseCode.Ok;
            errCode = code.ToString();
        }
        if (response.GetType() == typeof(DataResponse<CResponseCode,string>))
        {
            CResponseCode code = ((DataResponse<CResponseCode, string>)response).Code;
            isOkay = code == CResponseCode.Ok;
            errCode = code.ToString();
        }
        if (isOkay)
        {
            return true;
        }
        string msg = "Operation resulted in error code:" + errCode;
        LogErrorCode(msg);
        return false;
    }

Я пытаюсь найти способ уменьшить количество отливок и улучшить стиль метода. У меня нет владельца кода на Response<TResponseCode>, DataResponse<TResponseCode,string>, AResponseCode, BResponseCode, CResponseCode

Параметр ответа будет иметь тип Response<TResponseCode> или наследуется от него (DataResponse<TResponseCode,string> : Response<TResponseCode>)

Все *ResponseCode являются перечислениями, и все они имеют *ResponseCode.Ok запись

Ответы [ 10 ]

1 голос
/ 31 августа 2010

Для этого вы можете использовать стандартную подпрограмму:

    // Mocking your classes, just to check if it compiles. You don't need this.
    class Response<T> { 
        public T Code { get { return default(T); } } 
    }
    enum AResponseCode { Ok }
    enum BResponseCode { Ok }
    enum CResponseCode { Ok }
    static void LogErrorCode(string msg) { }

    private static bool ProcessErrorCode(object response)
    {
        bool isOkay;
        string errCode;

        if (!TryProcessErrorCode(response, AResponseCode.Ok, out isOkay, out errCode))
            if (!TryProcessErrorCode(response, BResponseCode.Ok, out isOkay, out errCode))
                TryProcessErrorCode(response, CResponseCode.Ok, out isOkay, out errCode);

        if (isOkay)
        {
            return true;
        }
        string msg = "Operation resulted in error code:" + errCode;
        LogErrorCode(msg);
        return false;
    }

    // TResponseCode is automatically inferred by passing the okCode
    private static bool TryProcessErrorCode<TResponseCode>(
        object response, TResponseCode okCode, 
        out bool isOkay, out string errCode)
    {
        var resp = response as Response<TResponseCode>;
        if (resp == null)
        {
            isOkay = false;
            errCode = "UNKNOWN";
            return false;
        }
        else
        {
            isOkay = okCode.Equals(resp.Code);
            errCode = resp.Code.ToString();
            return true;
        }
    }
1 голос
/ 31 августа 2010

Вы можете получить немного более чистое решение, используя дженерики:

private static void TestErrorCode<TCode>(object response, TCode ok, ref bool isOkay, ref string errCode)
{
    Response<TCode> responseTyped = response as Response<TCode>;

    if (responseTyped == null)
    {
        return;
    }

    TCode code = responseTyped.Code;
    isOkay = code.Equals(ok);
    errCode = code.ToString();
    return;
}

private static bool ProcessErrorCode(object response)
{
    bool isOkay = false;
    string errCode = "UNKNOWN";

    TestErrorCode(response, AResponseCode.Ok, ref isOkay, ref errCode);
    TestErrorCode(response, BResponseCode.Ok, ref isOkay, ref errCode);
    TestErrorCode(response, CResponseCode.Ok, ref isOkay, ref errCode);

    if (isOkay)
    {
        return true;
    }

    LogErrorCode("Operation resulted in error code:" + errCode);
    return false;
} 
1 голос
/ 31 августа 2010

В идеале вы бы дали Response<TResponseCode> общий интерфейс с функцией «ОК».Видя, что вы не можете, ваше решение будет выглядеть немного хакерским.

Учитывая это ограничение, я бы выделил несколько методов - static bool IsResponseOk(object response) и static string GetResponseError(object response) - которые позволили бы легчечитать код, но все еще не блестяще.

0 голосов
/ 31 августа 2010

Я был бы склонен попытаться получить основную часть кода довольно просто, как это:

/// <summary>
/// Calls matching process error code on response.Code
/// </summary>
/// <param name="response">Actually will be of type Response or extend it</param>
/// <returns>true for successful response, false otherwise</returns>
private static bool ProcessErrorCode(object response)
{
    Func<Type, Func<object, string>> process = ...;
    var errCode = process(response.GetType())(response);
    if (errCode != "Ok")
    {
        LogErrorCode("Operation resulted in error code:" + errCode);
    }
    return errCode == "Ok";
}

Тогда это просто становится вопросом определения Func<Type, Func<object, string>>.Это можно сделать с помощью отдельного метода или путем внедрения зависимости.

Отдельный метод будет выглядеть следующим образом:

private static Func<Type, Func<object, string>> _process = null;
private static Func<Type, Func<object, string>> GetProcessFunc()
{
    if (_process == null)
    {
        var d = new Dictionary<Type, Func<object, string>>()
        {
            { typeof(Response<AResponseCode>), r => ((Response<AResponseCode>)r).Code.ToString() },
            { typeof(Response<BResponseCode>), r => ((Response<BResponseCode>)r).Code.ToString() },
            { typeof(DataResponse<CResponseCode,string>), r => ((DataResponse<CResponseCode,string>)r).Code.ToString() },
        };
        _process = t =>
        {
            if (d.Contains(t))
            {
                return o => d[t];
            }
            return o => "UNKNOWN";
        };
    }
    return _process;
}

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

0 голосов
/ 31 августа 2010

Вот решение с dynamic (может быть отредактировано для использования отражения), не проверено и не рекомендуется, но коротко.

Это должно работать идентично вашей первоначальной функции:

  • Поскольку вы полагаетесь на response.GetType() == typeof(XXX), который отличается от response is XXX (наследование исключено), types.Contains(response.GetType()) эквивалентно.
  • Поскольку вы гарантируете, что все эти типы имеют свойство Code, которое является (предположительно) типом enum, содержащим значение Ok, часть dynamic всегда должна выполняться.
  • Тот факт, что включены только подходящие типы, означает, что «совпадений» не может быть.

Минусы:

  • Производительность
  • Если базовые типы изменятся, вы не будете защищены во время компиляции.

private static bool ProcessErrorCode(object response)
{
    var types = new[] { typeof(Response<AResponseCode>), typeof(Response<BResponseCode>), typeof(DataResponse<CResponseCode, string>)};
    var errCode = !types.Contains(response.GetType())
                  ?"UNKNOWN"
                  :(string)(((dynamic)response).Code.ToString());

    if(errCode == "Ok") return true;

    LogErrorCode("Operation resulted in error code:" + errCode);
    return false;
}
0 голосов
/ 31 августа 2010

Я не могу себе представить, чтобы что-то сделать с этим, не найдя человека, ответственного за написание этого XResponseZZZ. Может быть, другие умнее меня. Но я убежден, что вы должны найти этого парня и убедить его, что ответственность XResponse должна быть известна, если указанный код в порядке и какое сообщение об ошибке выдается. Это стиль C-кода, и он плохой.

Я посмотрел на общие решения Heinzi или Daniel, и они мне нравятся больше всего.

0 голосов
/ 31 августа 2010

Рефакторинг таким образом, и я был бы быстрее, если бы IE не испугался и перезагрузил страницу самопроизвольно.: (

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

  bool isOkay = false; 
  string errCode; // you don't need to default this to anything because it won't be empty if you need to use it

  if (response is Response<AResponseCode>) // "is" is a faster and more expressive test
  { 
    var code = (response as Response<AResponseCode>).Code; // "as" is a faster conversion
    isOkay = (code == AResponseCode.Ok); // Readability is awesome!
    errCode = code.ToString(); // This still sucks
  } 
  else if (response is Response<BResponseCode>) // elsif logically exclusive tests
  { 
    var code = (response as Response<BResponseCode>).Code; 
    isOkay = code == BResponseCode.Ok; 
    errCode = code.ToString(); 
  } 
  else if (response is DataResponse<CResponseCode,string>) 
  { 
    var code = (response as DataResponse<CResponseCode, string>).Code; 
    isOkay = (code == CResponseCode.Ok); 
    errCode = code.ToString(); 
  } 

  // Test what you mean to express, i.e. if this isn't ok I need to log it
  if (!isOkay) 
  { 
    // you don't need a temp variable here...
    // and why aren't you just throwing an exception here? Is not being ok a regular unexceptional event, but one you still need to log? Really?
    LogErrorCode("Operation resulted in error code:" + errCode);
  } 

  // And try not to return bool literals, it's again unexpressive
  return isOkay;
0 голосов
/ 31 августа 2010

С точки зрения производительности, я бы заменил .GetType() == typeof() на оператор is, например:

if( response is DataResponse<CResponseCode,string> )

Следующий тест показывает улучшение производительности примерно в 3 раза на моей машине:

var f = new List<string>();
var dummy = 0;

Stopwatch sw = new Stopwatch();

sw.Start();
for(int i=0; i< REPS; i++)
    if(f.GetType() == typeof( List<string> )) dummy++;
sw.Stop();
Console.WriteLine(sw.Elapsed);

sw.Reset();

sw.Start();
for(int i=0; i< REPS; i++)
    if(f is List<string> )  dummy++;
sw.Stop();

Console.WriteLine(sw.Elapsed);

// Outputs
00:00:00.0750046
00:00:00.0261598
0 голосов
/ 31 августа 2010

Это заменяет мой старый ответ, поскольку он явно бомбит :) (это перерывы)

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

Основная идея состоит в том, чтобы определить тип результата, который содержит значения IsOkay и ErrCode, а затем определить словарь делегатов (определяемый типом объекта, с которым они работают), к которому вы обращаетесь.Чтобы добавить новые обработчики, вы просто добавляете в словарь еще одного делегата.

public class ResponseResult{
  public bool IsOkay;
  public string ErrCode;
}

public static class ResponseExtracter
{
  //STARTING OFF HERE WITH THE NEW VERSION OF YOUR METHOD
  public static bool ProcessErrorCode(object response)
  {
    Func<object, ResponseResult> processor = null;
    ResponseResult result = new ResponseResult() 
    { 
      IsOkay = false, ErrCode = "UNKNOWN"
    };

    //try to get processor based on object's type
    //then invoke it if we find one.
    if (_processors.TryGetValue(response.GetType(), out processor))
      result = processor(response);

    if (result.IsOkay)
      return true;
    string msg = "Operation resulted in error code:" + result.ErrCode;
    LogErrorCode(msg);
    return false;
  }
  //A lot better no?

  //NOW FOR THE INFRASTRUCTURE
  static Dictionary<Type, Func<object, ResponseResult>> _processors 
    = new Dictionary<Type, Func<object, ResponseResult>>();

  static ResponseExtracter()
  {
    //this can be replaced with self-reflection over methods
    //with attributes that reference the supported type for
    //each method.
    _processors.Add(typeof(Response<AResponseCode>), (o) =>
    {
      AResponseCode code = ((Response<AResponseCode>)o).Code;
      return new ResponseResult
      {
        IsOkay = code == AResponseCode.Ok,
        ErrCode = code.ToString()
      };
    });

    _processors.Add(typeof(Response<BResponseCode>), (o) =>
    {
      BResponseCode code = ((Response<BResponseCode>)o).Code;
      return new ResponseResult
      {
        IsOkay = code == BResponseCode.Ok,
        ErrCode = code.ToString()
      };
    });

    _processors.Add(typeof(DataResponse<CResponseCode, string>),
      (o) =>
      {
        CResponseCode code = ((DataResponse<CResponseCode, string>)o).Code;
        return new ResponseResult
        {
          IsOkay = code == CResponseCode.Ok,
          ErrCode = code.ToString()
        };
      });
  }
}

С точки зрения производительности это лучше, чем выглядит, потому что Dictionary<Type, TValue> фактически хэширует целочисленное значение, которое лежит в основе TypeHandle типа.Фактически это Int64, поэтому используются только первые 32 бита, но на практике маловероятно, что два типа совместно используют одни и те же первые 32 бита своей ручки.

0 голосов
/ 31 августа 2010

Я бы поменял оператор switch для всех ifs.

...