Cargo-Culting in JavaScript

La programmazione di settaggio del carico è ciò che fa un programmatore quando non conosce abbastanza bene un particolare linguaggio o paradigma, e quindi finisce per scrivere codice ridondante e possibilmente dannoso. Alza la testa abbastanza spesso nella terra di JavaScript. In questo articolo, esploro il concetto di programmazione del culto del carico e luoghi da tenere d'occhio in JavaScript.

Le regole dogmatiche emergono e si diffondono, finché non sono considerate la norma.

Il culto del carico viene talvolta definito come "l'estrema aderenza alla forma anziché al contenuto". La forma, nella programmazione, è la sintassi, i paradigmi, gli stili e i modelli che impieghiamo. Il contenuto è la cosa astratta che stai cercando di rappresentare attraverso il tuo codice - la sostanza stessa del tuo programma. Una persona con scarsa comprensione in un'area può copiare la forma di altri senza una vera comprensione, e quindi il loro contenuto - il loro programma - può subire.

Il culto del carico è curiosamente comune in JavaScript, probabilmente a causa della bassa barriera generale all'ingresso nel mondo dello sviluppo front-end. Puoi creare una pagina HTML con un po 'di JavaScript in pochi secondi. Di conseguenza, ci sono molte persone che diventano sufficientemente abili in queste tecnologie per sentirsi a proprio agio nel creare e imporre regole a se stessi e agli altri. Alla fine, altri nuovi arrivati ​​copiano queste regole. Le regole dogmatiche emergono e si diffondono, finché non sono considerate la norma:

  • Utilizza sempre operatori rigorosi di uguaglianza
  • Non usare mai eval
  • Utilizzare sempre una singola dichiarazione var per ambito
  • Usa sempre un IIFE - ti "protegge"

Una regola continua a diffondersi fino a quando un programmatore utilizza solo una determinata tecnica a causa della sua popolarità, invece di considerare ogni caso d'uso specifico in modo indipendente.


JavaScript Abuzz con punto e virgola

Se hai avuto l'opportunità di assistere alle battute spiritose e alla retorica dello sviluppatore di software nel corso degli anni, avrai notato la tendenza a discutere cose apparentemente minuscole. Cose come il punto e virgola, la virgola, lo spazio bianco o la parentesi graffa.

La sintassi come punto e virgola o spazio bianco può sembrare puramente elementi di forma, non di contenuto. Ma molte di queste sottili regole di sintassi possono avere effetti significativi in ​​JavaScript. Se non capisci il 'modulo', allora non puoi iniziare a capire il 'contenuto'.

In questo articolo, quindi, identificheremo quali aree della forma in JavaScript sono frequentemente cariche di merci, cioè copiate senza comprensione.

Come può sembrare JavaScript ... un'immagine tratta dalla presentazione di "The Politics Of JavaScript" di Angus Croll

Non definito

Angus Croll, in una recente presentazione, intitolato "The Politics Of JavaScript", ha evidenziato uno dei pezzi più comuni del dogma di JS che la gente carica di culto fuori da:

if (typeof myObject.foo === 'undefined') ...

Il più delle volte, facendo un controllo così prolisso per non definito è inutile. La tecnica divenne comune perché le persone stavano copiando altre persone, non a causa del loro valore reale.

Certo, ci sono momenti in cui:

 typeof x === 'undefined'

... è preferibile a:

x === non definito

Ma, allo stesso modo, ci sono momenti in cui quest'ultimo è preferito. Una rapida panoramica delle opzioni:

 // Determina se 'x' non è definito: x === undefined typeof x == 'undefined' typeof x === 'undefined' x === void 0 // Determina se 'x' non è definito O null: x = = null x == indefinito

Le persone hanno iniziato a usare il tipo di approccio perché si proteggevano contro:

  • Una variabile potenzialmente non dichiarata (approcci non-typeof genererebbero TypeErrors)
  • Qualcuno ha sovrascritto indefinitamente globalmente o nell'ambito di un genitore. Alcuni ambienti ti consentono di sovrascrivere non definito a qualcosa di simile vero. Devi chiedertelo: "È probabile che qualcuno abbia sovrascritto indefinito, e che il mio copione debba assecondare tale sciocchezza?"

Ma la maggior parte delle volte si stanno proteggendo dal doversi preoccupare. È un modo per evitarlo di dover conoscere i dettagli. Conoscere i dettagli può aiutarti comunque. Ogni carattere del tuo codice dovrebbe esistere con uno scopo in mente.

L'unica volta che dovresti aver bisogno di usare a tipo di controlla non definito è quando si controlla una variabile che potrebbe non essere stata dichiarata, ad es. controllo di jQuery nell'ambito globale:

 if (typeof jQuery! = 'undefined') // ... Usa jQuery

Il fatto è, se jQuery fa esiste, quindi possiamo essere sicuri che sia un oggetto - una cosa "veritiera". Quindi questo sarebbe sufficiente:

 // o: if (window.jQuery) 

Il grande dibattito rigoroso / non severo

Prendiamo qualcosa di molto comune e generalmente considerato un buon consiglio, usando esclusivamente l'uguaglianza rigorosa:

 a === b

L'eguaglianza rigorosa si dice che sia buona perché evita l'ambiguità. Controlla sia il valore che il tipo, il che significa che non dobbiamo preoccuparci della coercizione implicita. Con l'uguaglianza non rigorosa, tuttavia, dobbiamo preoccuparcene:

 1 == 1 // true - ok, va bene 1 == "1" // true - hmm 1 == [1] // true - wat!?

Quindi sembrerebbe ragionevole consigliare di evitare completamente l'uguaglianza non rigorosa, giusto? In realtà, no. Esistono molte situazioni in cui l'uguaglianza rigorosa crea grandi quantità di ridondanza e l'uguaglianza non rigorosa è preferibile.

Quando sai, con certezza al 100%, che i tipi di entrambi gli operandi sono gli stessi, puoi evitare la necessità di un'uguaglianza rigorosa. Ad esempio, so sempre che il tipo di l'operatore restituisce una stringa e anche il mio operando di destra è una stringa (ad es. "numero"):

 // Con strict-equals typeof x === 'number' // Con non-strict-equal: typeof x == 'number'

Sono entrambi identici. Non sto necessariamente suggerendo di abbandonare gli eguali in questo caso - suggerisco di rimanere consapevoli di ciò che stiamo facendo in modo da poter fare le scelte migliori date ogni situazione.

Un altro esempio abbastanza utile è quando vuoi sapere se un valore è o nullo o non definito. Con una rigorosa uguaglianza, potresti fare questo:

 if (value === undefined || value === null) // ...

Con l'uguaglianza non rigorosa, è molto più semplice:

 if (value == null) // ...

Non c'è nessuna presa qui - sta facendo esattamente quello che vogliamo, solo, forse, meno visibilmente. Ma, se conosciamo la lingua, qual è il problema? È proprio lì nelle specifiche:

Il confronto x == y, dove X e y sono valori, produce vero o falso. Tale confronto viene eseguito come segue:

  • Se x è nullo e y non è definito, restituisce true.
  • Se x non è definito e y è nullo, restituisce true.

Se stai scrivendo JavaScript con l'intenzione di essere letto, se non del tutto, da persone che conoscono JavaScript, allora direi che non dovresti sentirti male sfruttando le regole del linguaggio implicito, come questo.


hasOwnProperty

Il hasOwnProperty metodo è usato per determinare se una proprietà è direttamente posseduta da un oggetto. È comunemente trovato in per ... in loop per assicurare che si scherzi solo con le proprietà dirette e non con le proprietà ereditate.

 for (var i in object) if (object.hasOwnProperty (i)) // Possiamo fare cose con 'object [i]'

È importante notare che il per-in l'affermazione circoscriverà solo le proprietà enumerabili. I metodi ereditati nativi, per esempio, non sono enumerabili e quindi non devi preoccuparti di loro comunque.

Il hasOwnProperty check impedisce specificamente di toccare proprietà che tu o alcuni script di terze parti hanno definito, vale a dire quando il prototipo dell'oggetto ha proprietà enumerabili.

Se conosci il prototipo del tuo oggetto (o il prototipo del suo prototipo ecc.) non ha proprietà enumerabili, quindi non devi preoccuparti sull'utilizzo hasOwnProperty nel tuo per-in loop. E, se il tuo oggetto è inizializzato, tramite ES5 Object.create (null), allora non sarai nemmeno in grado di chiamare hasOwnProperty direttamente sull'oggetto (nessun prototipo significa nessun metodo nativo ereditato). Ciò significa che l'utilizzo hasOwnProperty di default in tutti i tuoi per-in i loop possono effettivamente rompersi a volte.

Una potenziale soluzione per oggetti con nullo prototipi è di utilizzare un riferimento salvato a hasOwnProperty, così:

 var hasOwnProperty = Object.prototype.hasOwnProperty; // Più tardi nel tuo codice: for (var i in someObject) if (hasOwnProperty.call (someObject, i)) // ...

Funzionerà anche se l'oggetto non ha un prototipo (nel caso di Object.create (null)). Ma, naturalmente, dovremmo farlo solo in primo luogo se sappiamo di averne bisogno. Se stai scrivendo uno script di terze parti per un ambiente "ostile", allora sì, controlla definitivamente le proprietà ereditate enumerabili. Altrimenti, potrebbe non essere necessario tutto il tempo.

Nota: IE9 e Safari 2.0 complicano ulteriormente la questione quando si tenta di identificare proprietà enumerabili già definite come non enumerabili. Vale la pena di verificare una vera implementazione del ciclo forOwn cross-browser.

Per concludere: il tuo uso di hasOwnProperty dovrebbe dipendere dall'oggetto su cui viene eseguito il loop. Dipende da quali supposizioni si possono tranquillamente fare. Proteggersi ciecamente usando il hasOwnProperty non sarà sufficiente in tutti i casi. Diffida anche delle differenze tra browser.


Over-Parenthesising

Un'altra ridondanza comune che si insinua nel codice JS è la parentesi. All'interno di espressioni, viene utilizzato per forzare il raggruppamento specifico di sottoespressioni. Senza di loro, sei in balia delle precedenze e delle associazioni di operatori. Per esempio:

 A && B || C A && (B || C) (A && B) || C

Uno di quelli non è come l'altro. Le parentesi forzano un raggruppamento specifico e molte persone preferiscono la chiarezza extra. In questo caso, l'operatore AND logico ha una precedenza più alta rispetto all'operatore OR logico, il che significa che è la prima e l'ultima riga che sono equivalenti. La seconda linea è un'operazione logica completamente diversa.

La precedenza più alta significa che si verificherà prima di altre operazioni in una serie di operazioni.

Per evitare questa complessità, gli sviluppatori spesso optano per una "politica di parentesi", in cui si continua ad aggiungere parentesi finché non è chiaro quali operazioni si verificano, sia per te che per i potenziali lettori del codice. Si può sostenere che questa verbosità finisce per rendere le cose meno chiare però.

A volte è difficile per un lettore. Si deve considerare che ogni parentesi data può essere stata aggiunta perché:

  • Era necessario per sovrascrivere precedenza / associatività predefinite
  • Per nessuna ragione funzionale, solo per "protezione" o "chiarezza"

Prendi questo esempio:

 A && B? doFoo (): doBaz ()

Senza la conoscenza delle regole di precedenza degli operatori, possiamo vedere due possibili operazioni qui:

 (A && B)? doFoo (): doBaz () A && (B? doFoo (): doBaz ())

In questo caso, è l'AND logico che ha la precedenza più alta, nel senso che l'espressione parentesi equivalente è:

 (A && B)? doFoo (): doBaz ()

Non dovremmo tuttavia sentire l'obbligo di aggiungere queste parentesi nel nostro codice. Succede implicitamente. Una volta riconosciuto che ciò accade implicitamente, siamo liberi di ignorarlo e concentrarci sul programma stesso.

Esistono, naturalmente, argomenti validi per mantenere le parentesi in cui il raggruppamento implicito non è chiaro. Questo dipende davvero da te e da cosa ti senti a tuo agio. Tuttavia, ti implorerò di apprendere le precedenze e quindi potrai avere il pieno potere di scegliere la strada migliore, a seconda del codice specifico con cui hai a che fare.


Chiavi dell'oggetto

Non è raro vedere citazioni ridondanti nei letterali degli oggetti:

 var data = 'date': '2011-01-01', 'id': 3243, 'action': 'UPDATE', 'related': '1253': 2, '3411': 3;

Oltre alle stringhe, JavaScript consente di utilizzare nomi e numeri di identificatori validi come chiavi letterali dell'oggetto, pertanto quanto sopra potrebbe essere riscritto in:

 var data = date: '2011-01-01', id: 3243, action: 'UPDATE', related: 1253: 2, 3411: 3;

A volte, potresti preferire la coerenza aggiunta di poter usare le virgolette, specialmente se un nome-campo sembra essere una parola riservata in JavaScript (come 'classe' o 'instanceof'). E va bene.

Usare le virgolette non è una brutta cosa. Ma è ridondante. Sapere che non devi usarli è la metà della battaglia vinta. Adesso è la tua scelta per fare quello che vuoi.


Posizionamento della virgola

C'è un'enorme quantità di preferenze soggettive, quando si tratta di posizionamenti di punteggiatura nella programmazione. Più di recente, il mondo JavaScript è stato in fermento con retorica e malcontento rispetto alla virgola.

L'inizializzazione di un oggetto in JavaScript tradizionalmente idiomatico assomiglia a questo:

 var obj = a: 1, b: 2, c: 3;

Esiste un approccio alternativo, che tuttavia sta guadagnando slancio:

 var obj = a: 1, b: 2, c: 3;

Il supposto vantaggio di mettere le virgole prima di ogni coppia chiave-valore (a parte il primo) è che significa che devi solo toccare una linea per rimuovere una proprietà. Utilizzando l'approccio tradizionale, è necessario rimuovere "c: 3"e quindi la virgola finale nella riga in alto, ma con l'approccio virgola-primo puoi rimuovere solo", c: 3"I sostenitori affermano che ciò rende meno probabile il trailing virgole e pulisce anche le differenze di controllo del codice sorgente.

Gli oppositori, tuttavia, affermano che questo approccio consente solo di liberarsi del "problema" della virgola finale introducendo un nuovo problema della virgola principale. Prova a rimuovere la prima riga e ti rimane una virgola nella riga successiva. Questo in realtà è considerato una cosa buona da parte dei sostenitori della virgola, perché una virgola iniziale lancia immediatamente un SyntaxError. Una virgola finale, tuttavia, non genera nulla, tranne in IE6 e 7. Quindi, se lo sviluppatore non riesce a testare il proprio JS in quelle versioni di IE, le virgole finali possono spesso insinuarsi nel codice di produzione, che non è mai buono. Una virgola leader getta in tutti gli ambienti, quindi è meno probabile che manchi.

Certo, potresti obiettare che tutta questa faccenda è discutibile. Probabilmente dovremmo usare linters come JSLint o il tipo di JSHint. Quindi siamo liberi di usare la punteggiatura e il posizionamento degli spazi bianchi che ha più senso per noi e per i nostri colleghi.

Non iniziamo nemmeno a utilizzare lo stile virgola prima nelle dichiarazioni variabili ...

 var a = 1, b = 2, c = 3;

Tu sei il codice per gli psicopatici?

Dovremmo cercare di imparare le lingue che usiamo a un livello abbastanza buono da essere in grado di evitare tecniche di codifica catch-all-catching e over-protection. E dovremmo fidarci che i nostri colleghi e altri sviluppatori facciano lo stesso.

Abbiamo anche discusso dell'abbandono di cruft in favore di sfruttare le idiosincrasie e le regole implicite di una lingua. Per alcuni, questo crea problemi di manutenibilità, specialmente se qualcuno più giovane nell'acquisizione di una determinata lingua si avvicina al codice. Ad esempio, cosa succede se non conoscono l'equità debole o rigida di JavaScript?

Sul tema della manutenibilità, ci ricorda questa famosa citazione:

Codifica sempre come se la persona che finisce per mantenere il tuo codice sia uno psicopatico violento che sa dove vivi.

Non so se questo è veramente un buon consiglio. Anche presa metaforicamente, suggerisce una sfiducia nella competenza del manutentore fittizio - e la necessità di preoccuparsi della loro comprensione sopra ogni altra cosa. Preferirei scrivere codice sapendo che sarà curato da persone che conoscono le loro cose. Quindi, come una possibile contraddizione o addirittura un addendum a quella citazione, offro:

Codifica sempre come se la persona che finisce per mantenere il tuo codice sia a conoscenza della lingua e dei suoi costrutti e stia cercando di comprendere il dominio del problema leggendo il tuo codice.

Anche se questo potrebbe non essere sempre vero, dovremmo cercare che sia così. Dovremmo sforzarci di garantire che le persone che lavorano su una tecnologia specifica abbiano le conoscenze sufficienti per farlo. Il colto-apprendista dice:

Se pervaso per sempre ad un livello inferiore di comprensione nel mio codice - calpestando dolcemente - rispettando rigorosamente le convenzioni e le guide di stile e le cose che vedo fare agli "esperti", allora non sono mai in grado di far progredire la mia comprensione, né approfittare di un linguaggio in tutta la sua stranezza e bellezza. Sono felicemente e beatamente sistemato in questo mondo di regole e di assoluti, ma per andare avanti, devo uscire da questo mondo e abbracciare una comprensione superiore.