Modelli di codifica: copia difensiva

5

Durante la visione di un video di YouTube su Tipi di valore in Swift , sono rimasto sorpreso da un semplice esempio ( intorno al minuto 3:00) che è stato dato per dimostrare le insidie dei tipi di riferimento.

Codice di esempio:

let home = House()
let temp = Temperature()
temp.fahrenheit = 75
home.thermostat.temperature = temp.copy()

temp.fahrenheit = 425
home.oven.temperature = temp.copy()
home.oven.bake()

Nel codice precedente, House e Temperature sono classi e quindi hanno semantica del tipo di riferimento. La giustificazione per invocare il metodo copy () in più punti è quello di garantire che la modifica della variabile temp per l'impostazione del forno non modifichi la temperatura del termostato della casa (ovvero impedisca la mutazione non intenzionale dovuta alla semantica di riferimento).

Personalmente trovo questo uno stile di codifica molto strano, ma il presentatore va avanti per diversi minuti su come questa cosiddetta "copia difensiva" sia usata nelle core librairie di Apple per prevenire i bug, quindi mi chiedo se sono stato fare cose sbagliate.

Personalmente, avrei scritto questo seguendo le seguenti linee:

let home = House()
home.thermostat.temperature = Temperature()
home.thermostat.temperature.fahrenheit = 75   //actually I would personally have supplied this as a parameter to the initializer but I'm trying to remain as close to the original code as possible.

home.oven.temperature = Temperature()
home.oven.temperature.fahrenheit = 425
home.oven.bake()

In altre parole, creerei esplicitamente nuove istanze della classe Temperature chiamando il suo inizializzatore (costruttore) invece di riutilizzare una variabile locale di quel tipo e assegnarne delle copie. Non solo evita l'ultima copia, che è inutile, ma il codice mi sembra più chiaro.

Ora concesso, questo è un esempio banale. Nel caso di una classe complessa con molti membri che condividono gli stessi valori tra le istanze, ricreare un nuovo tipo da zero e assegnare ogni valore più e più volte è un sovraccarico di programmazione. Tuttavia, almeno nella mia esperienza, di solito ho creato inizializzatori o metodi di fabbrica che eseguono questi compiti comuni, quindi devo apportare solo alcune modifiche laddove necessario.

Nei rari casi in cui non si applica quanto sopra, trovo che chiamare esplicitamente un metodo clone () o copy () sia corretto. Solo facendo così raramente, mi trasmette il fatto che sta succedendo qualcosa di speciale (ad esempio, una copia profonda di un albero per esempio).

Pertanto, la mia domanda è: il codice sopra di Apple è una pratica consigliata? E se no, in quali circostanze (a parte le copie profonde) la copia manuale come sopra sarebbe il modo preferito di fare le cose?

    
posta Dragonspell 30.03.2016 - 21:54
fonte

2 risposte

4

Sono d'accordo con Phil Lello sul fatto che questo sia stato probabilmente troppo semplificato, forse fino al punto di confonderlo. Un esempio molto più chiaro della necessità e non desiderabilità di (dover fare) la copia difensiva è il seguente usando C # per la concretezza.

class House {
    private Thermostat thermostat;
    private ControlDisplay cc;
    // ...
    public void ShowDisplay() {
        // ...
        cd.SetTemperatureField(thermostat.Temperature.Clone());
        // ...
        cd.UpdateDisplay();
    }
}

Ora, se Temperature è mutabile, ogni volta che vogliamo mostrare il display di controllo dobbiamo copiare la temperatura del termostato anche se è improbabile che venga cambiata dal display. Anche se controlliamo il codice per ControlDisplay e quindi possiamo verificare guardando il codice sorgente che non muta il passato in Temperature , continuiamo a copiare perché potrebbe essere cambiato in futuro. Non c'era modo di comunicare e far rispettare che non dovrebbe essere cambiato.

Se Temperature fosse immutabile, d'altra parte, non ci sarebbe bisogno di questa copia. Il risultato sarebbe un codice più pulito e più efficiente, che rende semplicemente impossibili alcuni errori e accoppiamenti.

Immagino che la maggior parte delle istanze nelle librerie principali della Apple siano più simili a quelle sopra l'esempio fornito nel video. Posso solo immaginare alcuni scenari un po 'bizzarri in cui codice come l'esempio originale potrebbe essere preferibile codificare più come il tuo secondo esempio (o il tuo alluso alla riscrittura).

    
risposta data 31.03.2016 - 00:33
fonte
4

Quindi ho guardato il video. L'esempio inizialmente dato è al di là di ridicolo. Se non altro, il suo uso principale dovrebbe essere un esempio di bug in MyFirstProgram a causa del riutilizzo variabile. Gli esempi successivi (che sono stati ripuliti) illustrano più direttamente gli usi per la copia difensiva.

Tuttavia, la copia difensiva non era il punto del video. Né l'esempio (copia originale o difensiva) codifica un modello da seguire. E infatti, il presentatore lo dice anche a partire da 3:59 nel video. Sta fondamentalmente creando un problema (male) per spiegare come hanno risolto con Swift. Se guardi alle 19:45, mostra che il codice di esempio non si sarebbe nemmeno compilato in Swift. E a partire dalle 20:03 mostra che il metodo difensivo non è necessario. (Anche se il suo esempio sembra orribilmente simile al codice originale ... con un let cambiato in var . O :) Lungo la strada anche lui riesce a battere con le sopracciglia Haskell con un algoritmo colto dalla ciliegia e un'accettazione singolare criteri.

In sintesi, il take-away dei segmenti del video con quel codice sono:

  • Dovresti non scrivere codice come nell'esempio
  • Se scrivi codice come questo, non verrà compilato in Swift
  • Il successivo codice difensivo "corregge il problema di riferimento" ma è troppo lavoro (3:59)
  • Non è necessario scrivere questo codice copiato difensivo a causa del modo in cui Swift implementa la semantica del valore
  • Gli esempi in questa presentazione non sono molto buoni.

La mia ipotesi sui pensieri che portano a questa presentazione.

  • "Sai, dovremmo davvero incoraggiare un maggiore riutilizzo delle variabili."
  • "Se un'altra persona chiede perché Swift non ha il paradigma funzionale, io trollero completamente FP al WWDC."
risposta data 31.03.2016 - 00:50
fonte

Leggi altre domande sui tag