È valido aspettarsi / lanciare un'eccezione usando un sistema di caching personalizzato?

1

Immagina un codice come il seguente:

class Cache {
    private Map<String, String> values = new HashMap<String, String>();

    public String getFromCache(String key) {
        if (!values.containsKey(key)) {
            throw new NoEntryException();
        }
        return values.get(key);
    }

    public void setInCache(String key, String value) {
        this.values.put(key, value);
    }
}

class Foo {

    private Cache cache = new Cache();

    public String bla(String foo) {
        try {
            return cache.getFromCache(foo);
        } catch (NoEntryException ex) {
            String result = doSomeHeavyOperation(foo);
            cache.setInCache(foo, result);
        }
    }

    public String doSomeHeavyOperation(String something) {
        // heavy operation
        return something;
    }
}

Questo è un caso d'uso valido per un'eccezione? Da un lato, potrebbe essere più lento a livello di calcolo lanciare l'eccezione, piuttosto che eseguire un metodo "cacheContains (String)" o verificare null (sebbene in tal caso null significhi "contains no element" o "always null for doSomeHeavyOperation"? ). D'altra parte influisce sul flusso di controllo, ma non sembra farlo in un modo che mi rende meno chiaro. Direi anche che per me sembra più pulito che controllare frontalmente usando un cacheContains (String).

I valori nella cache sono sempre gli stessi, per semplicità (anche se puoi aspettarti una invalidazione della cache dopo 30 minuti).

Ovviamente la cache usata qui è abbastanza semplice, ma in seguito sarà un wrapper attorno a un sistema più complicato, come un'integrazione Redis.

Modifica:

Ecco alcuni esempi di codice su come eseguire la risposta accettata in Java (è necessario un miglioramento con generici, più argomenti, ecc.):

abstract class MissingResultCaller {
abstract String doCall(String argument);
}

class Cache {
private Map<String, String> values = new HashMap<String, String>();

public String getFromCache(String key, MissingResultCaller missingResultCaller) {
    if (!values.containsKey(key)) {
        String value = missingResultCaller.doCall(key);
        setInCache(key, value);
        return value;
    }
    return values.get(key);
}

public void setInCache(String key, String value) {
    this.values.put(key, value);
}
}

class Foo {

private Cache cache = new Cache();

public String bla(String foo) {
    return cache.getFromCache(foo, new MissingResultCaller() {
        @Override
        String doCall(String argument) {
            return doSomeHeavyOperation(argument);
        }
    });
}

public String doSomeHeavyOperation(String something) {
    System.out.println("Did heavy operation");
    return "heavy operation return";
}
}
    
posta Kristof 27.07.2015 - 07:41
fonte

2 risposte

4

Dovresti preferire controlli programmatici alle eccezioni ogni volta che puoi. Un'eccezione è eccezionale, dovresti solo lanciarle dove si verifica una condizione che non puoi correggere. E per favore, non usarli per il controllo del flusso.

Va bene lanciare un'eccezione nella tua cache GET, ma SOLO se fornisci anche il codice chiamante con un modo per controllare in anticipo se l'eccezione dovrebbe essere lanciata. Dai un'occhiata alla maggior parte delle implementazioni di un capo lettore. Genererà un'eccezione se chiederai di leggere oltre la fine di un file, ma ti darà anche un mezzo per determinare se hai raggiunto la fine di detto file.

I controlli programmatici sono più chiari, più facili da leggere e non uccidono le tue prestazioni. Mi vergogno dell'idea di progettare uno strato di cache (inteso a migliorare le prestazioni) ma allo stesso tempo utilizzare le eccezioni in quel livello per il normale flusso di controllo.

Se non vuoi restituire NULL, considera di restituire un oggetto intermedio. È possibile restituire un oggetto CacheResult dalle proprie chiamate di cache che ha una proprietà "CacheStatus" e "Result". CacheStatus avrebbe un valore di 'Hit' o 'Miss', e nel caso di un 'Hit' si potrebbe trovare l'oggetto cache nella proprietà Result. In questo modo puoi ovviare alla necessità di un controllo "nullo" se ti infastidisce.

In alternativa, è possibile passare una funzione al metodo cache per consentire la creazione dell'oggetto se non esiste. Non è sicuro se questo può essere fatto in tutte le lingue, C # sarebbe un po 'come questo

private ResultType GetFromCacheOrCreate<ResultType>(string key, Func<ResultType> createFunction)
{
    var result = _myCache.Get<ResultType>(key);
    if(result == null)
        return createFunction();
    else
        return result;
}

private string doSomeHeavyOperation()
{
    //do something here
}

var myValue = GetFromCacheOrCreate<string>("mycachedValue", () => { return doSomeHeavyOperation(); });
    
risposta data 27.07.2015 - 08:45
fonte
0

In generale, un'eccezione viene definita un'eccezione perché dovrebbe essere un'eccezione .

Lanciare un'eccezione (per quanto ne so, correggermi se ho torto) invalida l'intera linea di comando e quindi è da considerarsi molto lento . Tuttavia, le eccezioni ti daranno il vantaggio di non dover eseguire controlli nulli. E ti consentono di separare la gestione degli errori dal codice effettivo.

Ora torniamo al tuo problema specifico: poiché la tua cache è valida per 30 minuti , le prestazioni non sono un problema. La domanda che dovresti porci è quindi:

Il codice è leggibile? - Mi sta bene.

    
risposta data 27.07.2015 - 08:41
fonte

Leggi altre domande sui tag