Come spiegare le decisioni di progettazione durante il refactoring del codice

7

Sto lavorando a un collega per refactoring il seguente codice, sto cercando di trasformare questo in un'esperienza di insegnamento pure.

Codice originale

public class WidgetRepository
{
    public Widget GetWidget(int id)
    {
        Widget widget;

        if (HttpContext.Current != null)
        {
            widget = (Widget)HttpContext.Current.Cache["widget_" + id];
        }

        if (widget == null)
        {
            widget = GetWidgetImpl(id); // some db call

            if (HttpContext.Current != null)
            {
                HttpContext.Current.Cache["widget_" + id] = widget;
            }
        }           

        return widget;
    }
}

La cosa più ovvia che emerge è che il repository è a conoscenza dei concetti di ASP.NET, il che è particolarmente grave in quanto questo repository viene utilizzato dal codice non-ASP.NET in alcuni scenari.

Codice refactored

Ho deciso di rifattorizzare il codice come segue:

public class WidgetRepository
{
    private readonly ICacheProvider _cacheProvider;

    public WidgetRepository(ICacheProvider cacheProvider)
    {
        _cacheProvider = cacheProvider;
    }

    public Widget GetWidget(int id)
    {       
        var widget = _cacheProvider.Get<Widget>("widget_" + id);

        if (widget == null)
        {
            widget = GetWidgetImpl(id); // some db call

            _cacheProvider.Add("widget_" + id, widget);
        }

        return widget;
    }
}

public interface ICacheProvider 
{
    T Get<T>(string key);
    void Add<T>(string key, T item);
}

public HttpCacheProvider : ICacheProvider
{
    private readonly Cache _cache;

    public HttpCacheProvider(Cache cache)
    {
        _cache = cache;
    }

    public T Get<T>(string key)
    {
        return (T)_cache[key];
    }

    public void Add<T>(string key, T item)
    {
        _cache[key] = item;
    }       
}

public NullCacheProvider : ICacheProvider
{
    public T Get<T>(string key)
    {
        return null;
    }

    public void Add<T>(string key, T item)
    {
    }
}

La mia giustificazione

Ho spiegato i seguenti vantaggi al codice refactored:

  1. Promuove Open / Close: possiamo cambiare l'implementazione della cache se vogliamo usare qualcos'altro in futuro (memcached, ecc.)

  2. Minore complessità ciclomatica: meno istruzioni if

  3. Non mescolare la tecnologia ASP.NET con la tecnologia di accesso ai dati

Rifiuti / dubbi

Tuttavia, ho ottenuto le seguenti confutazioni:

  1. Non useremo una tecnologia di caching diversa nel prossimo futuro.

  2. Sono più linee di codice (quindi è più complesso)

  3. Devi impostare le dipendenze (la complessità viene spostata, non rimossa)

  4. Esistono già tonnellate di codice che utilizzano HttpContext.Current , quindi questo codice non segue il pattern (anti-) esistente

  5. Stiamo ancora "utilizzando" ASP.NET all'interno del codice di accesso ai dati.

Le mie domande

Personalmente, trovo che i pro prevalgano sulle confutazioni, tuttavia, mi piacerebbe essere in grado di affrontare le confutazioni per aiutarli a educarli.

Le mie domande sono:

  1. Sono giustificato nel fare il refactoring (supponendo che abbiamo tempo a disposizione per ripulire il codice)?

  2. Come rispondo alle varie confutazioni?

posta Matthew 14.05.2015 - 17:32
fonte

3 risposte

9

Am I justified in making the refactoring (assuming we have time allocated for cleaning up code)?

Stai facendo altro in questo codice?

In caso contrario, questo non sembra valere la pena. È probabile che il tuo pari non abbia intenzione di utilizzare altre cose e che tu debba mantenere le cose coerenti.

Se lo sei, pulire anche questo materiale sembra un chiaro vantaggio per il minimo sforzo / rischio (anche se potresti dover aggiungere un costruttore predefinito che utilizza HttpContext per avere un impatto minimo).

How do I respond to the various refutations?

  1. Sì, ma non c'è motivo di accoppiare qualcosa inutilmente quando è così facile fare le cose meglio.

  2. Più codice non è necessariamente più complesso. In questo caso, è più complesso, ma banalmente così. In cambio, rende più semplice il test delle unità e il codice più flessibile.

  3. Certo, sta solo spostando il problema, ma sta spostando il problema in una forma più disaccoppiata. Ti consente di affrontare le parti del problema in isolamento piuttosto che di avere un problema ancora più spiacevole.

  4. "Questo è ciò che abbiamo sempre fatto" non è una scusa per perpetuare codice / design / qualcosa di brutto. Sì, dovresti mirare a mantenere il codice coerente, ma in questo caso dovresti spingere per coerenza con il modello disaccoppiato piuttosto che con l'accesso diretto HttpContext . Sì, è più lavoro, ma è la cosa giusta da fare. Se il tuo pari spinge che non c'è tempo per tutto questo ora (e ha un'alternativa che è meglio), allora forse accetti di farlo più tardi (e aggiungi un ticket / bug / storia a in realtà fallo).

  5. Vedi # 4.

risposta data 14.05.2015 - 17:47
fonte
1

C'è più di un modo per scuoiare un gatto. Riprova finché non trovi una soluzione che risolva le preoccupazioni di tutti. Parte del problema è che non hai davvero infranto l'accoppiamento sul codice di cache fino in fondo. Vorrei iniziare con qualcosa di simile:

public class WidgetRepository
{
  public Widget GetWidget(int id)
  {
    return Cache.GetOrCreate<Widget>("widget_" + id, () => GetWidgetImpl(id));
  }
}

public static class Cache
{
  public static T GetOrCreate<T>(string key, Func<T> createFunc)
  {
    if (HttpContext.Current == null)
    {
      return createFunc();
    }

    T result = (T)HttpContext.Current.Cache[key];

    if (result == null)
    {
      result = createFunc();
      HttpContext.Current.Cache[key] = result;
    }           

    return result;
  }
}

Non conosco C #, quindi potrebbe esserci un modo più pulito per realizzare questa stessa idea (e per favore ignora i problemi di sintassi), ma ottieni il punto. Qui le linee di codice sono paragonabili, ma ho separato la preoccupazione per la cache in una chiamata di funzione, una che dovrebbe essere altamente riutilizzabile se questo schema si ripete tutte le volte che hai inteso. Ne consegue che YAGNI ha sempre avuto bisogno di un diverso tipo di cache, eppure fornisce un luogo separato per fare quel cambiamento.

Se hai davvero bisogno di un repository separato senza alcuna connessione alla cache, puoi renderlo una classe genitore di WidgetRepository . Se hai bisogno di supportare più tipi di cache simultaneamente, allora puoi fare l'iniezione invece di usare una classe statica. Non c'è bisogno di fare impazzire tutta l'interfaccia finché non ne hai davvero bisogno.

Ancora una volta questo è solo un punto di partenza. Il mio punto è che dovresti essere in grado di trovare un modo per rendere tutti felici.

    
risposta data 14.05.2015 - 20:44
fonte
0

La tua classe è chiaramente superiore perché puoi iniettare un fornitore di cache fittizia.

Sebbene il controllo NULL HttpContext.Current fermi un'eccezione generata quando vengono testati il repository o le classi che lo utilizzano. Quindi non stai offrendo alcun vantaggio concreto.

Non tenterei di giustificare il tuo codice o rispondere alle confutazioni. Hai passato del tempo a ri-factoring, i tuoi cambiamenti sono chiaramente in sintonia con le pratiche accettate, il ri-factoring è una buona cosa. Di che lamentarsi?

Dato che hai dovuto giustificare questo e la natura delle confutazioni penso sia chiaro che in realtà non c'è tempo per il refactoring. Questo è il classico 'renderlo veloce' E 'renderlo buono' E 'non spendere più denaro' tricotomia.

    
risposta data 14.05.2015 - 18:09
fonte

Leggi altre domande sui tag