È considerata buona pratica avere sempre metodi che restituiscono un valore?

4

Ci scusiamo per il terribile titolo, ma spero che questi frammenti ti diano il succo.

Metodo 1:

class Person:
    def __init__(self, name):
        self.name = name

    def set_name(self, new_name):
        self.name = ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))


def __main__():
    me = Person('Foo')
    me.set_name('foo bar')

Metodo 2:

class Person:
    def __init__(self, name):
        self.name = name

    def set_name(self, new_name):
        return ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))


def __main__():
    me = Person('Foo')
    me.name = me.set_name('foo bar')

Nell'esempio relativamente semplice, entrambi i metodi dovrebbero ottenere lo stesso risultato. La mia domanda è quale metodo sarebbe considerato una pratica migliore? C'è un motivo di leggibilità o prestazioni o è semplicemente una preferenza personale?

    
posta Tochi Obudulu 25.02.2016 - 20:53
fonte

3 risposte

8

Una delle prime lingue che ho imparato è Pascal. Aveva un'importante distinzione che era stata fatta. C'erano procedure , che non avevano alcun ritorno, e funzioni che sempre ha avuto un tipo di ritorno.

Questi sono due costrutti separati che separano le cose.

Funzioni restituiscono valori. Sempre.

Ora che questo è fuori strada, un metodo (sia esso funzionale o procedurale) dovrebbe sempre restituire qualcosa che sia significativo se deve restituire qualcosa. Restituire valori che sono scartati al 99% o al 100% delle volte non è utile - è uno spreco. Un buon compilatore potrebbe riconoscerlo e potrebbe rendere il suo minimo se il ritorno non costa nulla ... ma lo spreco è nel tempo umano per la persona che legge il codice. Cosa sta restituendo? Perché sta tornando?

Molti strumenti di analisi statica avvertiranno (correttamente) il programmatore circa la possibilità di ignorare il valore di ritorno di una chiamata di funzione. Ad esempio FindBugs

This method ignores the return value of one of the variants of java.io.InputStream.read() which can return multiple bytes. If the return value is not checked, the caller will not be able to correctly handle the case where fewer bytes were read than the caller requested. This is a particularly insidious kind of bug, because in many programs, reads from input streams usually do read the full amount of data requested, causing the program to fail only sporadically.

Oppure gli avvisi in g ++ (# 534)

Ignoring return value of function 'Symbol' (compare with Location)

A function that returns a value is called just for side effects as, for example, in a statement by itself or the left-hand side of a comma operator. Try: (void) function( ); to call a function and ignore its return value. See also the fvr, fvo and fdr flags.

E quindi, c'è l'aspettativa che dovrebbe stare guardando ai valori di ritorno che vengono restituiti. Se non ha significato, non dovrebbe essere al primo posto. Se si tratta di un errore eccezionale, beh, questo è ciò che le eccezioni sono (in molte lingue).

Ora, vediamo qualcosa su questo codice:

class Person:
    def __init__(self, name):
        self.name = name

    def set_name(self, new_name):
        return ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))

def __main__():
    me = Person('Foo')
    me.name = me.set_name('foo bar')

Vedi quelle linee che ho chiamato? Non stanno facendo quello che dicono che stanno facendo affatto .

La funzione denominata set non è impostata. La sua formattazione, ma non è impostata.

Certo, scrivere format_name non è un metodo sbagliato per scrivere. E sarebbe una funzione - perché restituisce qualcosa. E a (perdonare il mio Java) public void setName(String name) ha un tipo di ritorno void - perché non è utile a nessuno. Comunica chiaramente il suo intento: "Sto impostando questo valore e il suo risultato. Non ti preoccupare".

I metodi dovrebbero avere solo utili valori di ritorno.

    
risposta data 25.02.2016 - 21:07
fonte
1

me.name = me.set_name('foo bar')

Non farlo.

Se vuoi sapere che cosa è una pratica migliore, leggi i tuoi esempi. Il primo:

  1. Crea una persona, con il nome di Foo
  2. su quella persona, imposta il nome su "foo bar"

Ora con il secondo:

  1. Crea una persona, con il nome di Foo
  2. Imposta il nome della persona su un metodo che imposta il nome della persona su "foo bar"
    • È una pessima idea utilizzare "set" come parte del tuo metodo qui perché rende i tuoi esempi impossibili da elaborare, poiché è più un "nome di formato"

Il punto di tutto questo è che il problema qui non riguarda "un metodo restituisce?" o non la domanda, si tratta di "fa quello che stai facendo ha senso affatto ." Una funzione void / no return ha senso se la funzione non restituisce nulla.

Quindi è necessario rendere il metodo set_name con un nome diverso, quindi ha senso, oppure eliminarlo. In questo momento il motivo per cui hai un problema è che set_name è una funzione davvero scadente.

I tuoi esempi sarebbero migliori se entrambi:

  • La tua __init__ ha formattato il nome quando lo hai impostato
  • Non hai consentito il passaggio di un nome al costruttore e l'attuale set_name ha effettivamente impostato il nome formattato

Un esempio come:

class Person:
    def __init__(self, name):
        self._name = self._format_name(name)

    def _format_name(self, n):
        return ' '.join(s[0].upper() + s[1:] for s in n.split(' '))

def __main__():
    me = Person('Foo')

ha un senso. Vorrei mettere in discussione il motivo per cui autorizzi entrambi a impostare un nome e quindi a richiedere a qualcuno di formattare il nome in modo specifico. Potresti anche fare:

class Person:
    def __init__(self):
        pass

    def set_name(self, n):
        self._name = ' '.join(s[0].upper() + s[1:] for s in n.split(' '))

def __main__():
    me = Person()
    me.set_name('foo')
    
risposta data 25.02.2016 - 21:07
fonte
0

Il metodo 2 è ripugnante. Stai facendo in modo che l'utente lavori extra senza un vero scopo. Come altri hanno notato, non stai impostando il nome; lo stai formattando Per lo meno, dovrebbe essere un metodo di classe:

class Person:
    def __init__(self, name):
        self.name = name

    @classmethod
    def format_name(new_name):
        return ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))

def __main__():
    me = Person('Foo')
    me.name = me.format_name('foo bar')

Ma anche così, il problema qui è che .name è una proprietà interamente pubblica. Perché preoccuparti se stai costringendo gli utenti a conoscere la formattazione dei nomi.

Per getter / setter, se il tuo problema è che pensi di non restituire un valore è brutto, allora considera di restituire self . Questo consente il concatenamento:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def set_name(self, new_name):
        self.name = ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))

        return self

    def set_age(self, age):
        self.age = age

        return self

def __main__():
    me = Person('Foo', 50)
    me.set_name('foo bar').set_age(42)
    
risposta data 25.02.2016 - 21:22
fonte