At bidrage til Babel:Tre lektioner at huske

At komme til at arbejde rundt i en ny kodebase giver altid udfordringer, og Babel var ingen undtagelse.

Jeg har arbejdet med Babel som en del af programmet Google Summer of Code 2017, hvor jeg arbejder på at opdatere Babel-transformationer og Babylon-parseren for at imødekomme ændringer af specifikationer og implementere nye funktioner.

Her er et par ting, jeg har lært af mine eventyr indtil videre.

1. Ja, kommunikation er vigtig

For at starte med at lære kodebasen bedre at kende, gennemgik jeg listen over åbne problemer på Babel og fandt en forholdsvis nem (udgave #5728) at håndtere.

Bare for at være sikker på, at jeg vidste, hvad jeg lavede, sendte jeg et hurtigt spørgsmål i tråden:

Efter at have fået afklaring gik jeg i gang med at ændre plugin'et til ikke at smide "runtime" fejl under transpilering, men kun når koden rent faktisk køres. Et inkriminerende stykke kode stak ud:

for (const violation of (binding.constantViolations: Array)) {
  throw violation.buildCodeFrameError(messages.get("readOnly", name));
}

Hvad der nu skulle gøres her var faktisk at indsætte en throw sætning i den genererede kode, hvilket ikke viste sig at være for svært. Der var dog stadig nogle få tilfælde, hvor runtime-fejl blev smidt andre steder fra kode, der ikke var direkte relateret til denne fil.

Da jeg ville ud og udforske andre dele af Babels kodebase, lægger jeg det fra mig, så jeg kan komme videre med det senere.

Ikke alt for længe efter modtog jeg en, ja, interessant opdatering om spørgsmålet... Vent hvad?

Jeg har faktisk aldrig sagt, at jeg arbejdede på at løse problemet, men antog, at indlægget ville have antydet, at jeg ville arbejde på det.

Ups.

2. Hvor snapshottestning kommer til kort

Efter at have draget af sted til endnu en jagt, faldt jeg over nummer 5656:

Argumenter deoptimeres, når de skygges i indlejret funktion

Dette er en funktionsanmodning (tror jeg). Argumenter er ikke optimeret, hvis en indre funktion skygger navnet med en parameter (eller hvileparametre i mit tilfælde).

Indtast kode

const log = (...args) => console.log(...args);

function test_opt(...args) {
  log(...args);
}

function test_deopt(...args) {
  const fn = (...args) => log(...args);
  fn(...args);
}

...

Forventet vs. aktuel adfærd

Jeg forventer, at koden kan optimeres til at bruge .apply( thisArg, arguments ) hele vejen igennem. Men i test_deopt bliver de ydre ...args kopieret bare for at blive sendt ind i den indre fn. Jeg kan bekræfte, at problemet forsvinder, hvis jeg omdøb enten ...args af test_deopt eller fn-pilefunktionen.

Hvad sker der her?

Hvad der nu skete var, at denne kode ville generere følgende:

var log = function log() {
  var _console;

  return (_console = console).log.apply(_console, arguments);
};

function test_opt() {
  log.apply(undefined, arguments);
}

function test_deopt() {
  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { // unnecessary loop
    args[_key] = arguments[_key];
  }

  var fn = function fn() {
    return log.apply(undefined, arguments);
  };
  fn.apply(undefined, args);
}

Se den for afsnit der? Normalt er dette nødvendigt, da arguments-objektet ikke er et rigtigt array — for eksempel, hvis du forsøgte at køre arguments.slice() , ville det mislykkes. I dette tilfælde bliver det dog kun sendt til Function.prototype.apply . Overraskende nok gider Babel allerede at optimere denne specifikke sag, som i test_opt eksempel ovenfor.

Forsøger at rette det

Så hvad gjorde jeg? Ved at tilføje problemfilen som en ny testcase prøvede jeg at se, om jeg kunne få outputtet til at afspejle det, jeg ønskede.

"Hvorfor fejler testen? Hvis jeg ændrer det lidt, vil det helt sikkert løse sig selv.”

På trods af spamming make test-only og ved at ændre transformationerne af refererede identifikatorer i koden, resulterede enhver ændring bare i, at en anden masse tests mislykkedes i stedet.

Chromium-debuggeren er "sjov"

Elendig, irriteret og forvirret gad jeg fyre Node.js-inspektøren op for at gennemgå, hvad der foregik.

Efter at være vendt tilbage til min computer fra en drinkspause, hilses jeg med glæde velkommen til min harddisklampe, der banker rundt og en praktisk talt hængt computer.

Holder min computer sammen med fornuftige programmer af Alt + SysRq + F , lykkedes det mig at gennemarbejde tingenes flow¹ og finde ud af, hvordan koden præcis fungerede.

Selv gennem alt det, kunne jeg stadig ikke se nogen grund til, hvorfor den besluttede at fjerne denne "nødvendige" (så jeg troede) kode, der blev fjernet med min oprindelige rettelse.

Det egentlige problem?

Ser du fejlen vist ovenfor? Hele koden med grønt var ikke beregnet til at være der, selvom det var "forventet".

Grundlæggende:testen var brudt. Fantastisk. :/

Den faktiske rettelse involverede oprettelse af en referencesRest funktion for at sikre, at spread-operatoren rent faktisk blev anvendt på den oprindelige parameter, snarere end en variabel i et andet omfang, der maskerer variablen.

¹:Det viser sig, at tilføjelse af en stor mappe til DevTools-arbejdsområdet ville lække hukommelse, indtil det forårsagede en OOM (fejl, jeg indgav for dette).

Hvorfor bruger vi så snapshottest?

For det første er det langt nemmere at oprette tests, når alt hvad du skal gøre er at bede Babel om at køre din testcase for at generere din forventede fil. Dette giver os en mulighed for lave tidsomkostninger, samtidig med at vi beskytter mod en betydelig del af potentielle fejl.

Også, især med den type program Babel er, ville det være langt sværere at teste for på andre måder. For eksempel kunne vi tjekke for specifikke noder af AST, men det tager meget længere tid at skrive og er også tilbøjelig til ikke-oplagte brud, når din kode forsøger at ændre den måde, transformationen udføres på.

Så alt i alt et par lektioner her:

  1. Sørg for, at dine tests er rigtige i første omgang – vær ikke selvtilfredse!
  2. Ja, debuggeren er faktisk nyttig til at se, hvad der foregår.
  3. Nogle gange tager det tid at løse tingene – hvis du ikke kommer nogen vegne, så tag en pause eller arbejd på noget andet.

3. Teammøder!

Jeg ved godt, at dette lidt strækker forestillingen om et "problem", men alligevel :)

Når du arbejder på et projekt med en flok andre mennesker, er det altid nyttigt at indhente hinanden og diskutere områder, som vi skal arbejde med.

Så hvordan gør vi det helt præcist?!

Øh, møder.

Når du har en flok mennesker spredt over hele verden, er det aldrig let at finde måder at kommunikere på, men uanset hvad må vi nøjes med vores forsøg på denne bedrift.

Tidszoner

Når du har at gøre med et open source-projekt, der spænder over hele kloden, bliver det hurtigt at vælge en passende time til en temmelig involveret øvelse i bikeshedding.

Selv med den store spredning mellem os hver især, så det ud til, at vi næsten kunne nå at få noget sammen.

Ak, dette skulle ikke holde. Til sidst endte vi med at skulle skifte mellem to gange hver anden uge for at imødekomme andre brugere (kl. 13.00 og 16.00 UTC), hvilket betød, at jeg kun kunne deltage én gang hver fjortende dag.

På trods af dette har vi formået at gøre betydelige fremskridt med at koordinere rettelser til forskellige dele, der udgør nøgleændringer til Babel, herunder understøttelse af TypeScript, ændringer i den rækkefølge, transformeringsplugins kører i, samt at holde sig ajour med ændringer fra TC39.

Hvor skal du næste gang?

Vi fortsætter med at polere Babel 7 op til almindeligt forbrug, med en række nye funktioner, der følger med.

Jeg arbejder sammen med en masse andre for at få support til opdateret Class Fields-specifikationsforslag inkluderet i Babel, så folk kan teste det og give feedback.

Mens jeg er i gang, vil jeg også gerne takke alle Babels mentorer og bidragydere for at hjælpe mig med peer reviews og give vejledning med forslag, hele vejen fra første kontakt til i dag.

Vil du vide mere om Babel? Klik op på vores bidragsside og meld dig ind i Slack-fællesskabet!

Mere om Karl

Karl Cheng er en GSoC 2017-studerende, der kommer fra Sydney, Australien. Find ud af mere om ham på GitHub (Qantas94Heavy) og Twitter (@Qantas94Heavy)!

Tjek venligst vores første indlæg om Summer of Code for mere info!