Progettazione di una funzione multi-test per convalidare una stringa

4

Supponiamo che io voglia eseguire un'azione su una stringa (ad esempio, stampare la stringa) se, e solo se, la stringa prima supera diversi test. I test sono diversi (e possono anche essere funzioni complesse) e "passare" un test può significare cose diverse a seconda del test specifico. Ad esempio, alcuni test possono essere discretizzati per ottenere True / False. Altri test sono considerati "passati" se la funzione sottostante non genera un'eccezione.

Inoltre, supponiamo che io desideri avere tutti i test (ce ne possono essere decine)! combinati in una singola funzione personalizzata (ad esempio validate_string ).

Il mio obiettivo è scrivere codice (cioè una combinazione di validate_string() e main() ) che funziona fuori test dopo test e stampa solo la stringa se nessuno dei test in validate_string() fallisce.

Si potrebbe ingenuamente immaginare di utilizzare una funzione personalizzata che impiega la gestione delle eccezioni per interrompere semplicemente lo script se uno qualsiasi dei test nella funzione validate_string() fallisce. In questo modo il codice esce prima che venga eseguito il comando di stampa in main() . Tuttavia, per me un tale design fa cattivo uso della gestione delle eccezioni e preferirei un design diverso.

def validate_string(my_string, required_end):
    try:
        if not my_string[-1] == required_end:
            raise ValueError
    except ValueError:
        exit('ERROR: String '%s' does not end with '%s'.' % (my_string, required_end))
    try:
        complex_function(my_string)
    except:
        exit('ERROR: String '%s' could not be processed.' % (my_string))

def main(my_string):
    validate_string(my_string, "o")
    print my_string

> main("foo bar")
ERROR: String 'foo bar' does not end with 'o'.

Qualcuno può raccomandare una progettazione generale migliore per questo tipo di problema?

    
posta Michael Gruenstaeudl 14.07.2017 - 13:07
fonte

2 risposte

2

Indipendentemente da come viene implementato, non sei stato molto chiaro sul comportamento che desideri.

My objective is to write code (i.e., a combination of validate_string() and main()) that works off test after test and only prints the string if none of the tests in validate_string() fails.

Questo è abbastanza semplice per essere raggiunto. Ma cosa dovrebbe accadere quando un test fallisce? Vuoi sapere perché non ha stampato? Se no, non è una convalida. Questo è un filtro. L'80% del lavoro qui non avviene quando questo passa. È quando non lo fa.

One could naively imagine utilizing a custom function that employs exception handling to simply break the script if any of the tests in function validate_string() fails. That way the code exits before the print command in main() is executed. However, to me such a design misuses exception handling and I would prefer a different design.

No, non è un errore nella gestione delle eccezioni. Questo non sta gestendo le eccezioni.

La gestione delle eccezioni è quando rilevi un'eccezione e recuperi da essa. Chiudi tutte le risorse aperte e rimetti il sistema in uno stato utilizzabile. Probabilmente vorrai registrare l'eccezione in modo che le persone possano eseguire il debug del sistema. Potrebbe anche essere necessario informare l'utente di ciò che è accaduto in modo che smettano di aspettare che accada qualcosa.

Non gestire un'eccezione significa che il programma sta per terminare. Anche quello potrebbe essere quello che vuoi. Terminare quando si verifica un'eccezione assicura che il sistema non vada in uno stato strano e inizi a distruggere le cose che ti interessano.

Devi decidere cosa dovrebbe accadere quando la stringa non è valida.

Chiamando il tuo metodo validate_string() fai sembrare che qualcosa di brutto sia successo quando fallisce. Se tutto quello che vuoi è che il sistema non stampi la stringa "non valida" e poi esegui il resto del suo business come se nulla fosse mai sbagliato, allora la tua stringa non è "non valida", è filtrata. filter_string() sarebbe un nome migliore.

The tests are diverse (and may even be complex functions themselves), and "passing" a test may mean different things depending on the specific test. For example, some tests can be discretized to result in True/False. Other tests are considered "passed" if the underlying function doesn't raise an exception.

I test possono essere diversi ma dobbiamo renderli coerenti in un modo o nell'altro per lavorare con loro. Puoi farlo riscrivendo i test o avvolgendo i test che ne hanno bisogno in qualcosa che li renda coerenti.

Se facciamo in modo che tutti restituiscano un bool quando l'esecuzione è qualcosa che vogliamo recuperare da questo lavoro:

def printValidString(str, predicate_list):
  if all(f(str) for f in predicate_list):
    print str

Questo verrà stampato solo se passano tutti i controlli. Qualsiasi evasione non gestita si estinguerà da questo e ci farà terminare. Qualsiasi eccezione gestita può essere trasformata in falsa e sopprimere la stampa.

def isSunny(mayThrow, args):
  try:
    mayThrow(args)
    return True
  except ExceptionTypeWeHandle:
    return False

Perché questo è un design ok? Perché il divieto di utilizzare le eccezioni alla succursale è circa se altro è meglio. Se altrimenti non funziona quando ci si muove attraverso i contesti.

È un design fantastico? No. Sarebbe molto meglio avere controlli consistenti sin dall'inizio e usarli, beh, in modo coerente. Ma presumendo che sei bloccato con controlli inconsistenti, avvolgendoli per renderli lavori costanti.

Sotto questo design ci sono tre risultati possibili.

  1. Ogni controllo passa, la stringa viene stampata e l'esecuzione continua
  2. Un controllo fallisce in modo prevedibile, la stringa non viene stampata e l'esecuzione continua
    1. un assegno ha restituito false su proprio
    2. isSunny() ha eseguito un controllo, gestito un'eccezione prevista e restituito falso
  3. Un controllo fallisce in modo inaspettato, la stringa non viene stampata e l'esecuzione viene interrotta dopo che l'eccezione imprevista è esplosa nello stack di chiamate

È qualcosa del genere quello che avevi in mente?

    
risposta data 20.07.2017 - 21:42
fonte
2

Hai provato ad accodare una versione modificata del pattern builder?

Non ho molta esperienza in Python, ma penso che potrebbe essere molto riutilizzabile se usato come:

assert StringValidator.target(myString).notEndsWith('hello').isLowercase().customTest(lambda x: x.hasString('a')).test() is 'passed'

Con la possibilità di cambiare l'ordine in qualsiasi cosa tu voglia ottenere lo stesso risultato finale

assert StringValidator.target(myString).customTest(lambda x: x.isUpperCase()).notEndsWith('hello').test() is 'passed'

E la tua implementazione potrebbe essere qualcosa del genere (ma fatta correttamente):

class StringValidator:->

    builder:StringValidationBuilder = new StringValidationBuilder


    static target(myString:String):->
      return builder.setTarget(myString)

    private static class StringValidationBuilder:->
        constructor()
        setTarget(target:String):->
           this.target=target
           return this
        test(builder:StringValidationBuilder):->
            return "passed" if target is not null and (if isEmptyTestedFlag and isEmpty or not isEmtyTested) .... otherwise "not passed because of #{failureReason}".
    
risposta data 18.07.2017 - 19:15
fonte