Această postare este destinată scepticilor și noilor veniți la sisteme de tip și își propune să articuleze mai degrabă decât să vândă greu.

  1. Mai întâi vom analiza modul în care apar convențiile de tip static în codarea dvs. tastată dinamic.
  2. Apoi vom face un pas înapoi și vom încerca să ne gândim la ceea ce ne spune acest fenomen despre modul în care vrem să codăm.
  3. În cele din urmă, vom pune câteva întrebări (de frunte!) Care ar trebui să apară din aceste perspective.

1A: Tipuri în nume

Indiferent de limbă, călătoria dvs. cu tipuri începe aproape de îndată ce învățați să codificați. Structura de date a listei de bază invită un plural corespunzător:

var dog = 'Fido'
var dogs = ['Fido', 'Sudo', 'Woof']

Pe măsură ce lucrați cu tot mai multe coduri, începeți să vă formați opinii pe care le-ați putea trimite echipei sau ghidului de stil:

  • folosiți întotdeauna nume specifice precum dogID vs. dogName vs. dogBreed sau un spațiu de nume / clasă / obiect de genul dog.name sau dog.id sau dog.breed
  • single-urile nu trebuie să fie subșiruri de plural, de exemplu BAD: blog și blogs, BUN: blogPost vs. blogList
  • booleeni ar trebui să aibă un prefix boolean-ish, ca isLoading, hasProperty, didChange
  • funcțiile cu efecte secundare ar trebui să aibă verbe
  • variabilele interne ar trebui să aibă un _prefix

Acest lucru poate părea banal, deoarece vorbim despre nume variabile, dar această venă funcționează extrem adânc. Numele din codarea noastră reflectă conceptele și constrângerile pe care le punem codului nostru pentru a-l face mai ușor de întreținut la scară:

Toate acestea pătrund în codul dvs. în consecință: *Container, *Component, *Reducer, *Template, *Page, with*.

Odată ce începeți să traversați paradigmele de execuție, începeți să vă simțiți drumul în indicii de tip monadic.

Node.js a simțit asta la început:

fs.readFile(myfile, callback)
fs.readFileSync(myfile) // introduced when people realized callback hell might not be worth non-blocking

React a introdus use prefix pentru a indica conectarea la runtime pe care trebuie să o respecte anumite reguli:

function Component() {
  const [bool, setBool] = React.useState(true)
  React.useEffect(callback)
  const foo = useCustomHook()
  // ...
}

Îmi plac personal amintirile nulității:

const maybeResult = await fetchAPI()
if (maybeResult) {
  const result = maybeResult
  // do things with result
} else {
  // maybeResult is falsy, dont assume it is there
}

În aproape tot ceea ce numiți, utilizați deja tipuri.

Deci ce, întrebi?

Continuați să citiți, mă pregătesc.

1B: Tipuri în structuri de date

Problema cu tipurile de codificare în nume este că limbajul probabil nu-i pasă de variabilele dvs. numite meticulos (într-adevăr, în JavaScript, probabil că este minimizat fără milă dincolo de recunoaștere). Vă va rula cu fericire codul și va genera o eroare de runtime dacă uitați să vă respectați propriile indicii de nume. Ce se întâmplă dacă am face tipurile verificabile formal prin structuri de date?

Cele mai de bază sunt constantele. În Redux, este comun pentru a seta în mod explicit (și redundant) SCREAMING_CASE_CONSTANTS:

const ADD_TODO = 'slice/ADD_TODO'

// later in redux code:
import { ADD_TODO } from './redux/types'
switch (action.type) {
  case ADD_TODO:
  // do stuff based on the action
  // ...
}

Acest lucru se face mai ales pentru că nu poți avea încredere în colegii tăi dezvoltatori să nu le scape șirurile.

Cu toate acestea, chiar și aceste șiruri oferă prea multă încredere și am considerat că este suficient de important să adăugăm o nouă caracteristică de limbă pentru a garanta unicitatea:

const ADD_TODO = Symbol('slice/ADD_TODO')

De asemenea, ne falsificăm drumul către enumeri astfel:

const colors = {
  BLUE: Symbol(1),
  GREEN: Symbol(2),
  RED: Symbol(3),
}

Dar valorile simple (șiruri, numere, booleeni) sunt de fapt ușor de comparat și de tratat în consecință.

Mai presant este codarea tipurilor în valori complexe.

Acest lucru se întâmplă de obicei atunci când aveți matrici de obiecte, iar obiectele sunt diferite în anumite moduri și similare în altele:

const animals = [{ name: 'Fido', legs: 4, says: 'woof' }, { name: 'Kermit', legs: 2, marriedTo: 'Piggy' }]
// will have bugs if an animal with both `says` and `marriedTo` exists
animals.forEach((animal) => {
  if (animal.says) {
    // i guess it's a dog?
  }
  if (animal.marriedTo) {
    // i guess it's a frog?
  }
})

Verificarea buggy-urilor și tipurile implicit asumate este adesea o cauză de multă durere. Mai bine să tastați explicit:

const animals = [
  {
    type: 'dog', // new!
    name: 'Fido',
    legs: 4,
    says: 'woof',
  },
  {
    type: 'frog', // new!
    name: 'Kermit',
    legs: 2,
    marriedTo: 'Piggy',
  },
]
animals.forEach((animal) => {
  if (animal.type === 'dog') {
    // must be a dog!
  }
  if (animal.type === 'frog') {
    // must be a frog!
  }
})

De fapt, acest lucru se întâmplă pentru Redux (și, destul de interesant, la îndemână pentru alte lucruri precum Uniuni discriminate), dar veți vedea acest lucru pretutindeni în Gatsby și Babel și Reacţiona și sunt sigur că știi de cazuri pe care nu le cunosc.

Tipuri chiar există în HTML: <input type="file"> și <input type="checkbox"> comportă-te atât de diferit! (și am menționat deja Tipuri în CSS cu Block__Element – Modificator)

Chiar și în HTML / CSS, utilizați deja tipuri.

1C: Tipuri în API-uri

Aproape am terminat. Chiar și în afara limbajului dvs. de programare, interfețele dintre mașini implică tipuri.

Marea inovație a REST a fost în esență o formă primitivă de tastare a cererilor client-server: GET, PUT, POST, DELETE. Convențiile web au introdus alte câmpuri de tip în cereri, cum ar fi accept-encoding antet, la care trebuie să respectați pentru a obține ceea ce doriți. Cu toate acestea, RESTfulness nu este practic aplicat și, deoarece nu oferă garanții, instrumentele din aval nu pot presupune puncte finale comportate corect.

GraphQL ia acea idee și o formează până la 11: tipurile sunt cheia pentru interogări, mutații și fragmente, dar și pentru fiecare câmp și fiecare variabilă de intrare, validate atât pe partea clientului, cât și pe partea serverului după spec. Cu garanții mult mai puternice, este capabil să livreze scule mult mai bune ca normă comunitară.

Nu cunosc istoria SOAP și XML și gRPC și a altor protocoale de comunicații mașină-mașină, dar sunt dispus să pariez că există paralele puternice.

Partea 2: Ce ne spune asta?

Aceasta a fost o examinare foarte lungă și totuși inexhaustivă a tipurilor care pătrund în tot ceea ce faceți. Acum că ați văzut aceste modele, vă puteți gândi probabil la mai multe exemple pe care le uit acum. Dar, la fiecare pas, pare calea către un cod mai ușor de întreținut, iar instrumentele mai bune sunt să adăugați tipuri într-un fel.

Am menționat părți ale acestei teze în Cum să denumiți lucrurile, dar practic toate schemele de denumire se încadrează într-o formă iluminată de notație maghiară, așa cum este descris în Joel Spolsky A face codul greșit să pară greșit.

Dacă nimic din ceea ce am descris nu rezonează cu dvs. și nu este ceva ce ați făcut deja, este posibil ca tipurile să nu fie pentru dvs.

Dar dacă da, și ați făcut acest lucru într-un mod slipshod, s-ar putea să fiți interesat de mai multe structuri în ceea ce privește modul în care utilizați tipurile în codul dvs. și să utilizați instrumente mai bune care să profite de toată munca grea pe care ați pus-o deja în tipuri .

S-ar putea să vă îndreptați spre un sistem de tip, fără să știți.

Partea 3: Întrebări principale

Deci, știind ce știm acum despre utilizarea tipurilor în codul nostru fără un sistem de tipuri. Voi pune câteva întrebări grele.

Întrebarea 1: Ce faceți în prezent pentru a aplica tipurile fără un sistem de tipuri?

La nivel individual, vă angajați în codificare defensivă și verificare manuală. Practic, ocularizarea manuală a propriului cod și adăugarea reflexivă a controalelor și a pazelor fără a ști dacă sunt într-adevăr necesare (sau, mai rău, NU o fac și descopere după ce am văzut excepții de la timpul de rulare).

La nivel de echipă, petreceți mai multe ore de dezvoltator în revizuirea codului, invitând bicicletele să renunțe la nume, despre care știm cu toții că este foarte distractiv.

Aceste două procese sunt metode manuale și o utilizare foarte slabă a timpului dezvoltatorului. Nu fi polițistul rău – aceasta distruge dinamica echipei. La scară, vă este garantat matematic că aveți deficiențe în calitatea codului (cauzând astfel erori de producție), fie pentru că toată lumea a ratat ceva, fie că nu a fost suficient timp și a trebuit doar să livrați ceva, sau nu a fost suficient de bun politica în vigoare încă.

Soluția, desigur, este să o automatizăm. După cum spune Nick Schrock, Delegați la scule ori de câte ori este posibil. Prettier și ESLint vă ajută să vă mențineți calitatea codului – numai în măsura în care programul vă poate înțelege pe baza unui AST. Nu oferă niciun ajutor pentru trecerea funcției și a limitelor fișierelor – dacă funcție Foo așteaptă 4 argumente și treci doar 3, niciun linter nu va țipa la tine și va trebui să codezi defensiv în interior Foo.

Deci, există doar atât de multe lucruri pe care le puteți automatiza cu un linter. Dar restul pe care nu îl poți automatiza?

Aici stă ultima opțiune: Nu face nimic.

Majoritatea oamenilor nu fac nimic pentru a-și impune sistemele de tip proiectat informal.

Întrebarea 2: Cât din aceste tipuri scrieți singur?

Este de la sine înțeles că, dacă toate politicile dvs. de tip sunt create de dvs., atunci acestea trebuie să fie scrise de dvs. și aplicate de dvs.

Acest lucru este total diferit de modul în care scriem codul astăzi. Ne sprijinim puternic pe open source – 97% din codul aplicației web moderne este de la npm. Importăm cod partajat și apoi scriem părțile din ultimul kilometru care fac aplicația noastră specială (cunoscută și ca logica de afaceri).

Există o modalitate de a partaja tipuri?

(da)

Întrebarea 3: Ce se întâmplă dacă tipurile dvs. ar fi standardizate?

Cercetările au arătat că motivul # 1 pentru care programatorii adoptă un limbaj este capacitățile și funcționalitățile existente disponibile pentru aceștia. Voi învăța Python să folosesc TensorFlow. Voi învăța Obiectivul C pentru a crea experiențe native iOS. În mod corespunzător, JS a avut atât de mult succes, deoarece rulează peste tot, agravată de disponibilitatea largă a software-ului open source scris de către alți oameni. Cu un sistem de tip standardizat, putem tipuri de import la fel de ușor ca și noi software-ul open source scris de alte persoane.

La fel ca GraphQL vs REST, tipurile standardizate într-o limbă deblochează instrumente mult mai bune. Voi oferi 4 exemple:

Exemplul 1: Feedback mai rapid

S-ar putea să luăm luni și zile să învățăm de la erori de rulare, iar acestea sunt expuse utilizatorilor, deci sunt cel mai slab rezultat posibil.

Scriem teste și aplicăm reguli de scame și alte verificări pentru a muta aceste erori construiți erori de timp, care scurtează ciclurile de feedback la minute și ore. (Așa cum am scris recent: Tipurile nu înlocuiesc testele!)

Sistemele de tip pot scurta acest feedback cu încă un alt ordin de mărime, la secunde, verificând în timpul scrie timpul. (Linters poate face acest lucru. Ambele sunt condiționate de un IDE de susținere, cum ar fi codul VS) Ca efect secundar, veți obține completarea automată gratuit, deoarece completarea automată și validarea timpului de scriere sunt cele două fețe ale aceleiași monede.

Exemplul 2: mesaje de eroare mai bune

const Foo = {
  getData() {
    return 'data'
  },
}
Foo['getdata']() // Error: undefined is not a function

JavaScript este o evaluare intenționată leneșă prin design. În loc de temut și nedescriptibil undefined is not a function în timpul rulării, putem muta acest lucru pentru a scrie timpul. Iată mesajul de eroare în timpul scrierii pentru același cod exact:

const Foo = {
  getData() {
    return 'data'
  },
}
Foo['getdata']() // Property 'getdata' does not exist on type '{ getData(): string; }'. Did you mean 'getData'?

De ce da, TypeScript, am făcut-o.

Exemplul 3: epuizarea carcasei marginii

let fruit: string | undefined
fruit.toLowerCase() // Error: Object is possibly 'undefined'.

Dincolo de verificarea nulă integrată (care se ocupă de probleme cum ar fi trecerea în 3 argumente atunci când o funcție așteaptă 4), un sistem de tip poate profita la maximum de enumere (aka tipuri de uniuni). M-am chinuit să vin cu un exemplu bun, dar iată unul:

type Fruit="banana" | 'orange' | 'apple'
function makeDessert(fruit: Fruit) {
  // Error: Not all code paths return a value.
  switch (fruit) {
    case 'banana':
      return 'Banana Shake'
    case 'orange':
      return 'Orange Juice'
  }
}

Exemplul 4: Refactoring Fearless

Mulți oameni au menționat acest lucru și voi fi sincer că mi-a luat mult timp să ajung la asta. Gândirea este: „și ce? Nu refac atât de mult. Deci asta înseamnă că beneficiul TypeScript este mai mic pentru mine decât pentru tine, pentru că sunt mai bun decât tine”

Aceasta este o abordare greșită.

Când începem să explorăm o problemă, începem cu o idee vagă a soluției. Pe măsură ce progresăm, aflăm mai multe despre problemă sau despre schimbarea priorităților și, cu excepția cazului în care am făcut-o de un milion de ori, probabil că am ales ceva greșit pe parcurs, fie că este vorba despre funcția API, structura datelor sau ceva la scară mai mare.

Utilizati deja tipuri Deci iata de ce ar trebui

Întrebarea este apoi să rămânem cu el până când se sparge sau să refactorizăm în momentul în care simțiți că veți depăși orice ați avut înainte. Presupun că acceptați că există adesea beneficii pentru refactorizare. Deci, de ce evităm refactorizarea?

Motivul pentru care ați amânat refactorul este că este costisitor, nu pentru că nu vă este benefic. Totuși, amânarea acestuia nu face decât să mărească costurile viitoare.

Instrumentele de tip System ajută la scăderea dramatică a costului refactorului, astfel încât să puteți experimenta avantajele mai devreme. Reduce acest cost prin feedback mai rapid, verificare exhaustivă și mesaje de eroare mai bune.

Adevărul în publicitate

Există un cost pentru învățarea sistemelor de tip pe care nu le-ai scris. Acest cost poate compensa orice beneficiu imaginat al verificării automate de tip. Acesta este motivul pentru care am depus eforturi mari pentru a ajuta la scăderea curbei de învățare. Cu toate acestea, fiți conștienți de faptul că este un limbaj nou și va implica concepte necunoscute și, de asemenea, că chiar și instrumentele sunt o lucrare în curs de desfășurare imperfectă.

Dar este suficient de bun pentru AirBnb și Google și Atlassian și Lyft și Priceline și Slăbiciune și poate fi pentru tine.