L'uso di funzioni linguistiche avanzate diminuisce la manutenibilità? [duplicare]

15

Durante una revisione del codice, mi sono imbattuto in un'idea interessante, che non posso giudicare correttamente da sola.

È corretto migliorare la leggibilità del codice mediante una sintassi del linguaggio non ampiamente conosciuta? Quando lo scrittore originale non utilizzava una sintassi speciale designata per risolvere uno specifico problema di codifica e lo risolveva simulando con soluzioni alternative, vale la pena di ridefinirlo per chiarezza e concisione? Cosa accadrebbe se anche il revisore del codice non fosse a conoscenza della funzione della lingua?

Per essere precisi, porterò il problema esatto che ho incontrato sulla revisione del codice, ma tieni presente che questo è solo un esempio.

Quindi il codice originale era un piccolo servizio web in Python. Ad un certo punto, è emerso un requisito per diversi gestori di richieste da proteggere con un meccanismo di autenticazione. Qualcuno l'ha risolto con l'ereditarietà:

class AuthOnlyAccess:
    def GET(self, *args):
        if not hasattr(self, 'PROTECTED_GET'):
            raise web.Nomethod()

        if check_auth():
            return self.PROTECTED_GET(*args)
        else:
            raise web.Unauthorised()

    def POST(self, *args):
        if not hasattr(self, 'PROTECTED_POST'):
            raise web.Nomethod()

        if check_auth():
            return self.PROTECTED_POST(*args)
        else:
            raise web.Unauthorised()

...

class FooHandler(AuthOnlyAccess):
    def PROTECTED_GET(self, a, b, c):
        handle_foo_get(a, b, c)

    def PROTECTED_POST(self, x, y, z):
        handle_spam_eggs()

...
<more handlers deriving AuthOnlyAccess>

Il refactorer ha trovato questo codice brutto e ha introdotto un decoratore:

from functools import wraps
...

def requires_auth(wrapped_func):
    """ (a little doc telling that this is a decorator and how to use it) """
    @wraps(wrapped_func)
    def decorator(*args):
        if check_auth():
            return wrapped_func(*args)
        else 
            raise web.Unauthorized()

    return decorator

...

class FooHandler:
    @requires_auth
    def GET(self, a, b, c):
        handle_foo_get(a, b, c)

    @requires_auth
    def POST(self, x, y, z):
        handle_spam_eggs()

...
<more refactored handlers>

Il revisore sostiene che questa modifica effettivamente riduce la manutenibilità, perché la maggior parte del team non conosce la sintassi del decoratore. Quindi la prossima persona che visita il codice refactored potrebbe essere confusa dal costrutto, ma non con la versione precedente.

C'è anche un argomento convincente che la maggior parte del codebase non è in Python , (il linguaggio è lì solo per quel piccolo servizio web), e quindi ai membri del team non è richiesto di avere conoscenza superficiale di Python. Tuttavia, per lavorare con il codice, è necessario conoscere la sua lingua, giusto?

E se hai la tentazione di dire che i decoratori python non hanno la sintassi avanzata quella , tieni presente che questo è solo un esempio. Ogni lingua ha i suoi angoli oscuri. (Ma imparare quegli angoli significa diventare migliori.)

Quindi la domanda è ... Puoi leggere la sintassi sconosciuta? L'eleganza merita la sorpresa? Almeno, quali sono i compromessi e / o le linee guida che puoi nominare? Ho davvero bisogno di alcune opinioni dalla comunità dei professionisti della programmazione.

    
posta ulidtko 15.10.2013 - 23:34
fonte

3 risposte

19

L'esempio refactored è oggettivamente superiore perché ha un codice meno ripetuto. Pertanto, tale implementazione è in realtà più semplice e probabilmente più breve. C'è anche l'argomento che il codice originale nel tuo esempio è un'implementazione ad-hoc dei tratti (o un'espressione scomoda del modello di strategia), il risultato è un codice inutilmente complesso.

In questo caso, dovresti usare i decoratori, anche quando la maggior parte dei tuoi sviluppatori non li conosce. Questo aggiunge solo un concetto extra singolo a quel pezzo di codice, che è molto economico considerando che la programmazione è generalmente ricca di fantasiosi nuovi concetti. Le informazioni necessarie possono e devono essere aggiunte in un breve commento che spiega questa funzione, che dovrebbe contenere anche un link ai documenti pertinenti.

Per le altre funzionalità linguistiche, dovrebbero essere fatte le stesse considerazioni:

  • Quale versione è più semplice ed esprime una soluzione più vicina alla lingua del dominio problematico?
  • Quanto costa educare i futuri manutentori? Tutto può essere spiegato in un breve commento o dovresti tenere una lezione sulla teoria delle categorie?
  • Qual è il costo di non per educare i futuri manutentori? Solo due righe in più qui o dover scrivere un codice per bambini alla fine della tua vita?

    Give a man a fish and you feed him for a day.
    Teach a man to fish and feed him for his life.
    Dumb down your code for a programmer and he can understand it at once.
    Teach a programmer advanced techniques, and he'll understand all your future code on his own.

risposta data 16.10.2013 - 00:40
fonte
8

Questo è qualcosa che devi prendere caso per caso. Direi che dovresti usare le caratteristiche non familiari di una lingua se questo migliorasse la chiarezza e facilitasse la manutenzione futura (entrambe le cose a mio parere portano a compimento il tuo esempio).

Devi mai usare le funzionalità solo perché vuoi essere intelligente o perché vuoi usare la funzione per il gusto di farlo. D'altra parte, non penso che dovresti evitare di usare una funzione che potrebbe migliorare il codice semplicemente perché gli altri sviluppatori del tuo team non sanno ancora come usarlo. Se valgono la pena, lo impareranno presto e lo utilizzeranno da soli se l'idea ha un valore. Gli sviluppatori che si rifiutano di adottare tecniche possibilmente superiori solo perché non li hanno mai visti o usati prima dovrebbero alzare grosse bandiere rosse.

    
risposta data 16.10.2013 - 00:23
fonte
2

Innanzitutto, direi che le convenzioni di codifica sono un luogo in cui vengono archiviate le restrizioni sull'utilizzo della lingua. Altrimenti ogni volta le persone avranno un'idea diversa di ciò che è familiare per una squadra e cosa no.

Quando scegli un sottoinsieme, crei una nuova lingua. Certamente condivide molte cose con il linguaggio "host", ma è diverso. Alcune cose potrebbero diventare più semplici, alcune più difficili.

Perché definire sottoinsieme:

  1. Il linguaggio host ha caratteristiche considerate pericolose (ad esempio, la limitazione del preprocessore in C ++)
  2. Le tue esigenze sono molto più piccole delle abilità linguistiche (ad esempio JSON come sottoinsieme di JavaScript)
  3. Hai esigenze specifiche (ad esempio supportare versioni precedenti di un compilatore senza supporto per le funzionalità linguistiche più recenti)

Perché attenersi a tutta la lingua:

  1. Le nuove funzionalità ci sono per un motivo (ad es. il tuo caso)
  2. Un miglior supporto ("come risolverlo in LISP?" verrà risposto più velocemente di "come risolvere questo in LISP senza funzioni lambda")
  3. Assumere nuovi sviluppatori più semplici (per i sottoinsiemi i candidati dovrebbero essere in grado e di lavorare comodamente con le restrizioni che hai)
  4. Confidi che l'intero linguaggio sia stato progettato da migliori designer linguistici di te:)
risposta data 16.10.2013 - 14:52
fonte

Leggi altre domande sui tag