Una delle fonti di bug che più frequentemente ci capita di incontrare quando sviluppiamo software per il web (gestionali per aziende, documentali web based, CMS, web app, ...), soprattutto quando lavoriamo sul codice di terze parti o sul codice prodotto internamente dai nostri Clienti è concetto di "copia vs riferimento".
Che tu sia un programmatore javascript, un project manager per un sito web o un product manager per una app, non puoi non sapere quel che ti stiamo per dire...
Copia vs Riferimento: Valori Primitivi
Partiamo dalle cose più semplici: copie e riferimenti di valori primitivi, con un esempio in JavaScript.
let azienda = 'Librasoft';
let azienda2 = azienda;
console.log(azienda); // Librasoft
console.log(azienda2); // Librasoft
azienda = 'Kuma';
console.log(azienda); // Kuma
console.log(azienda2); // Librasoft
Da questo esempio si evince che quando si vuole assegnare una variabile di valori primitivi (stringhe, ma anche interi, booleani, ...) a un'altra variabile, la seconda diventa una copia della prima. Ciò significa che i cambiamenti sulla prima variabile non si rispecchieranno sulla seconda.
Copia vs Riferimento: Array
Cosa succede quando invece si lavora con gli array? Vediamo un altro esempio.
let software = ['Jester', 'Doko', 'GeCo'];
let prodotti = software;
console.log(software); // Jester, Doko, GeCo
console.log(prodotti); // Jester, Doko, GeCo
software[0] = 'XXX';
console.log(software); // XXX, Doko, GeCo
console.log(prodotti); // XXX, Doko, GeCo
Da questo esempio vediamo come il cambio del primo array abbia modificato in qualche modo anche i contenuti del secondo array... È un errore?
No: è il funzionamento atteso (nonché la fonte di numerosi bug da parte di team inesperti), in quanto gli array non sono tipi primitivi, perciò quando si assegna una variabile che "contiene" un array a un'altra... non se ne passa la copia, bensì il riferimento.
Quindi, come fare per copiare un array su un altro? Una delle possibili risposte è nel seguente snippet.
let software = ['Jester', 'Doko', 'GeCo'];
let prodotti = Array.from(software);
console.log(software); // Jester, Doko, GeCo
console.log(prodotti); // Jester, Doko, GeCo
software[0] = 'XXX';
console.log(software); // XXX, Doko, GeCo
console.log(prodotti); // Jester, Doko, GeCo
Copia vs Riferimento: Oggetti
Anche gli oggetti, come gli array, vengono passati per riferimento. Vediamo un terzo esempio.
let software = {nome: 'Jester', tipo: 'Gestionale'};
let prodotto = software;
console.log(software); // nome: Jester, tipo: Software Gestionale
console.log(prodotto); // nome: Jester, tipo: Software Gestionale
software.tipo = 'Gestionale Aziendale';
console.log(software); // nome: Jester, tipo: Gestionale Aziendale
console.log(prodotto); // nome: Jester, tipo: Gestionale Aziendale
Anche in questo caso, le modifiche sul primo oggetto sembrano creare un effetto collaterale sul secondo oggetto... anche se, in realtà, è il funzionamento corretto del passaggio per riferimento.
Come fare a copiare un oggetto? Una possibile soluzione è nell'esempio che segue.
let software = {nome: 'Jester', tipo: 'Software Gestionale'};
let prodotto = Object.assign({}, software);
console.log(software); // nome: Jester, tipo: Software Gestionale
console.log(prodotto); // nome: Jester, tipo: Software Gestionale
software.tipo = 'Gestionale Aziendale';
console.log(software); // nome: Jester, tipo: Gestionale Aziendale
console.log(prodotto); // nome: Jester, tipo: Software Gestionale
Copia superficiale vs copia profonda
Un'altra fonte di bug che spesso ci capita di incontrare è dovuta alla differenza tra copia superficiale (shallow copy) e copia profonda (deep copy) di un oggetto JavaScript.
Vediamo il seguente esempio, dove vogliamo copiare un oggetto che contiene un altro oggetto.
let azienda = {nome: 'Librasoft', prodotto: {nome: 'Jester', tipo: 'Software'}};
let azienda2 = Object.assign({}, azienda);
console.log(azienda); // nome: Librasoft, prodotto: {nome: Jester, tipo: Software}
console.log(azienda); // nome: Librasoft, prodotto: {nome: Jester, tipo: Software}
azienda.nome = 'Kuma';
azienda.prodotto.nome = 'Consulenza';
azienda.prodotto.tipo = 'Servizio';
console.log(azienda); // nome: Kuma, prodotto: {nome: Consulenza, tipo: Servizio}
console.log(azienda2); // nome: Librasoft, prodotto: {nome: Consulenza, tipo: Servizio}
Dall'esempio si vede come le modifiche apportate all'oggetto "interno" della prima variabile si rispecchino anche nella seconda variabile. Questo accade perché il metodo "assign" esegue una copia di ciò che trova sulla superficie dell'oggetto (il suo primo livello), e non esegue la copia in maniera ricorsiva.
Per effettuare la copia profonda (o clone) è quindi necessario scrivere appositamente una funzione che esegua la deep copy, oppure utilizzare una libreria che lo faccia al posto nostro.
Hai bisogno di aiuto nello sviluppo del tuo software aziendale?
Se il software custom che attualmente utilizzi nella tua azienda presenta molti bug, o se pensi che al tuo team di sviluppo serva una mano a realizzare una particolare feature del tuo gestionale, portale o app... contattaci senza impegno: analizzeremo il tuo software, identificheremo le aree più critiche e ti prospetteremo la migliore soluzione.