Digitare le conversioni in una lingua digitata dinamicamente - Dove dovrebbe avvenire la conversione?

1

Recentemente ho eseguito il debug di un po 'di codice in cui l'implementazione precedente era simile a questa:

# controller for group/customers
def index
  @customers = current_user.available_customers(param[:group_id].to_i)
end

# user
def available_customers(group_id)
  # some other code, that sometimes returns early    

  # this is the line of failure
  # self.accessible_group_ids returns an array of integers
  # as does Group.children_ids
  # This line is attempting to get the subset of group ids that the user
  # is *allowed* to touch, and the hierarchy of group ids under the selected one. 
  ids = (self.accessible_group_ids & ([group_id] + Group.children_ids(group_id)))
  return Customer.where(group_id: ids)
end

Stavo refactoring il metodo di chiamata a available_customers nel controller e ho perso la conversione specifica di params[:group_id].to_i , quindi introducendo un bug in cui quando l'input dell'utente viene inviato tramite i parametri, è una stringa e il confronto nella ids = line non funziona come previsto - esclude il group_id corrente dal set, perché una stringa non è un int, anche se ha le stesse cifre in esso. :)

Vedo questa implementazione originale come ... problematica ... tralasciando le dipendenze dalle classi Group e Customer. È improbabile che la conversione del tipo prima di passare i dati al metodo sia destinata a fallire (come nel mio caso) e richiede al callee di conoscere come funziona il metodo (e anche una conoscenza abbastanza intima!).

Non penso che il chiamato debba sapere che il metodo richiede un int, e in un linguaggio vagamente tipizzato come Ruby, va bene accettare tutti i tipi di anatre negli argomenti del metodo a patto che facciano il ciarlatano.

La mia soluzione era:

def available_customers(group_id)
  # nil handling
  group_id = group_id.to_i
  # everything else
end

Ora il metodo available_customers è l'unico posto che deve sapere che group_id deve essere un numero intero.

Dove pensi che sia un buon posto dove metterlo? Lo metteresti da qualche altra parte? Perché?

    
posta guiniveretoo 02.12.2016 - 21:01
fonte

1 risposta

3

I don't think the callee should have to know that the method requires an int, and in a loosely typed language such as Ruby, it's okay to accept all types of Ducks in method arguments as long as they quack.

I linguaggi dinamici come Ruby sono ottimi per la prototipazione rapida, ma sono terribili nel definire e controllare attentamente il contratto di ciascun metodo. Ecco dove la tipizzazione statica ha un vantaggio decisivo.

Ti stai sbagliando che il chiamante non deve sapere che devono fornire un int. Se non forniscono un valore int o coverabile, il metodo non funzionerà. Il chiamante deve saperlo.

Con il tuo metodo, hai tre alternative per richiedere un valore di tipo intero:

  • non hai bisogno di nulla e credi che il chiamante sappia cosa stanno facendo.
  • si richiede che il chiamante fornisca un numero intero, o che fallisca rapidamente.
  • è necessario che il chiamante fornisca qualcosa che può essere convertito in un numero intero.

Asserendo nessuna precondizione, si mantiene la massima flessibilità, ma è facile rompere accidentalmente il sistema. Questa flessibilità vale la difficoltà aggiuntiva del debugging?

Richiedendo un numero intero, si perde un po 'di flessibilità: non è più possibile accettare valori che assomigliano a un numero intero, anche se il chiamante sa cosa stanno facendo. Tuttavia, ottieni la massima debugabilità: se un chiamante non fornisce un numero intero, otterrà un'eccezione in una volta. L'unico svantaggio rispetto alla tipizzazione statica è che questo viene verificato solo in fase di esecuzione. Che il chiamante debba digitare un extra .to_i è solo un piccolo fastidio sintattico.

Richiedendo un valore che può essere convertito in un intero, in realtà non si guadagna nulla se si richiede direttamente un numero intero. Il chiamante deve ancora fornire qualcosa di intero-convertibile. Si finisce con un numero intero in entrambi i casi. Tuttavia, si perde molta debugabilità: se il valore non può essere convertito in un numero intero, vedrà qualche errore nel metodo. Questo è più confuso di un errore che dice "quando si chiama questa funzione da qui, è necessario fornire un numero intero".

Questo mi porta a credere che le lingue dinamiche ci lascino con due scelte effettive:

  • ignora completamente qualsiasi tipo e confida che tutti sappiano cosa stanno facendo. Questo è un approccio valido per i sistemi di piccole dimensioni.
  • ignora completamente qualsiasi tipo di digitazione dinamica e controlla religiosamente tutti gli argomenti del metodo per i metodi pubblici. Nessuna conversione implicita. Per citare lo "Zen di Python": "Explicit è meglio di implicito". Ciò significa che il chiamante è responsabile.

Nota che i buoni documenti dei metodi sono necessari per spiegare il contratto di un metodo, ma quella documentazione non può imporre un contratto. In assenza di tipi statici che dovrebbero dimostrare correttezza, dovrai scrivere il codice di convalida a mano.

    
risposta data 02.12.2016 - 21:40
fonte

Leggi altre domande sui tag