Drag mod vest, unge mand (*)

Som tidligere diskuteret her på bloggen er det sundt at udvide sine horisonter og kigge på alternativer til indholdet af ens programmeringssproglige værktøjskasse.

Jeg synes altid, at det er underholdende at rode med nye sprog, men regningerne skal jo betales, så det skal også helst give mening i professionel sammenhæng. Det vil for mit vedkommende sige, at det skal kunne lade sig gøre at skrive store, serverbaserede applikationer, som laver CRUD mod en relationel database, som har integrationer til et stort antal andre tilsvarende applikationer primært gennem webservices, og som selv eksponerer sig som webservices til klienter i en browser. Det bliver nok ikke meget mere mainstream end som så, så der er et utal af sprog at vælge imellem, men for at reducere udfaldsrummet har jeg to grundlæggende krav: det skal være JVM-baseret og statisk typet.

Valget af JVM'en som runtimeplatform hænger sammen med, at jeg skal kunne lave en gradvis migrering af eksisterende kode, og at jeg bruger trediepartskode, som kun er tilgængelig på JVM'en (og evt. .Net). Desuden findes JVM'en på de operativsystemer, som jeg har brug for at kunne køre på, og den er bare generelt velfungerende. At den så er open source gør det bare bedre.

Det med statiske typer handler ikke først og fremmest om at fange fejl (hvilket dog er en behagelig sideeffekt), men om at kunne navigere rundt i koden. Jeg har behov for, at mine værktøjer kan fortælle mig præcis hvilke typer elementerne i koden har, hvor de er defineret osv. Beklager, men statiske typer øger bare vedligeholdbarheden i store projekter.

De to krav eliminerer et antal muligheder (fx Clojure, ECMAScript, F#, Go, Haskell, OCaml (**), Python og Ruby), som sikkert er udmærkede sprog (eller for F#'s vedkommende noget værre fis - ba dum tsh!), men hvor jeg ikke kan få øje på nogen uundværlige features, som jeg ikke kan finde andetsteds.

Det efterlader så de mere oplagte sprog:

Java var et godt valg for 20 år siden, og det bliver stadig videreudviklet - se fx Valhalla - så det er meget muligt, at det også er en brugbar platform fremover. Det har sine mangler, men jeg oplever stadig ikke, at de er væsentlige i det daglige arbejde. Jeg satser på at blive klogere.

Scala er blevet anbefalet af flere her, men jeg har mine tvivl. Det virker lidt a la C++, som indeholder et utal af features, men hvor det er svært at få øje på den overordnede idé i sproget, og så har det en særdeles fleksibel syntaks, hvilket gør det sværere end nødvendigt at læse kode skrevet i Scala. gcl's kommentar om implicitte parametre siger det meget godt:

This appears to be yet another "feature" that makes Scala code 10% easier to write, but 20% harder to understand/maintain.

A-men. Og så er der i øvrigt en uforholdsmæssigt stor andel af fortalerne for Scala, som fremstår ret arrogante i diskussioner. Det virker ikke som et behageligt miljø at færdes i.

Kotlin er lige kommet i version 1.0, hvilket gør det mere interessant; stabilitet er alfa og omega. Det er godt med fokus på pragmatisme, men det virker ikke helt gennemtænkt nok - at det er mere håndværk end akademi er ingen skade til, men et eller andet ved det virker bare ufærdigt. Måske den lidt løsagtige dokumentation?

Ceylon ender derfor med at blive mit valg, selv om jeg ellers ikke er den store spillefugl.

Det er et Java-agtigt sprog, som eksplicit er designet til at understøtte store programmer, skrevet af mange udviklere. Det afspejler sig i den gennemgående stringens - det er fx en syntaksfejl, hvis man erklærer et klassenavn med et lille begyndelsesbogstav eller et variabelnavn med et stort begyndelsesbogstav, og der er obligatoriske tuborgparenteser ved conditionals.

Det er også forsøgt holdt gennemskueligt, altså ingen overloading, nærmest ingen implicitte typekonverteringer, og null findes ikke. Et citat fra dokumentationen:

Ceylon discourages the creation of intriguing executable ASCII art. Therefore, true operator overloading is *not* supported by the language.

I afdelingen for interessante features har Ceylon et ret gennemarbejdet typesystem, og kan mange af de frække ting med højere-ordens-funktioner fra funktionsprogrammering; currying, fx. Og så har Ceylon interessant nok de småting, jeg tidligere har nævnt som mangler i Java; bl.a. defaultværdier for metodeparametre, konstant-by-default og tupler.

Endelig er det veldokumenteret inkl. en specifikation af sproget, og det kommer med et IDE. Min sjæl, hvad vil du mere?

Af mulige bagsider er den primære, at sproget ikke er særligt udbredt, så man kan have sin tvivl om dets langsigtede levedygtighed - om JetBrains er mere troværdige end Red Hat kan man jo altid overveje. Compiler- og runtimeperformance har jeg endnu ikke den store erfaring med, men det kan dårligt være værre end Scala, og det manglende plugin til IntelliJ er på vej.

Heldigvis har Scala, Kotlin og Ceylon meget til fælles mht. syntaks og features, så hvis det skulle vise sig, at man har satset på den forkerte hest kan man hurtigt skifte. Jeg har med interesse bemærket, at de alle ud over JVM'en understøtter JavaScript som runtimeplatform, hvilket kan give en håb om at kunne dele kode på tværs af server og klient. Mere derom senere.

(*): Sri Lanka (indtil 1972 kendt som Ceylon) ligger vest-agtigt for Java. Vi har ikke andet skæg end det, vi selv sidder på.

(**): Er der nogen, som har erfaringer med OCaml-Java?

Mikkel Lauritsens billede
Mikkel er CTO i IntraMed, som laver applikationer til håndtering af behandlingsforløb for patienter med kroniske sygdomme.

Kommentarer (26)

Torben Mogensen Blogger

Jeg forstår ikke, hvorfor du mener, at F#, Haskell og O'Caml ikke har statiske typer, det har de i allerhøjeste grad.

Men det var måske på grund af, at de ikke kører på JVM, at du fravalgte dem? Der findes en variant af Haskell, kaldet Frege, som kører på JVM. Der findes også en JVM implementering af Standard ML på JVM, men den har ikke været opdateret i mange år, og OCaml-Java er stadig i alpha-version, så vidt jeg kan se. Så Frege er nok indtil videre det mest oplagte valg udover Scala til statisk typet funktionsprogrammering på JVM.

Og Scala er efter min mening ikke specielt kønt. F.eks. har notationen for algebraiske datatyper (case classes i Scala) meget boilerplate, og manglen på global typeinferens medfører også meget boilerplate.

Christian Nobel

Men har du overvejet FreePascal?

Jeg er godt klar over at "nogen" mener det er gammeldags, men brugerskaren er altså ikke slet så lille som nogen vil italesætte den til, og væsentlig større end de fleste af de andre småsprog du nævner.

Og lige præcis typing er en af de ting jeg rigtig godt kan lide ved Pascal (plus det at man imo har et rigtig godt overblik over hvad det egentlig er man laver).

Troels Henriksen

Men jeg har ingen erfaring med hvor godt eller dårligt det virker, da JVM ikke er noget issue for mig.

Det er det til gengæld for bloggere, og det er også årsagen til at der ikke er så mange der er bidt på det her blogindlæg. Det er altid spændende at snakke programmeringssprog, men ikke hvis man lader diskussionen begrænse af implementeringsteknologi. Ville man ikke synes jeg var fjollet, hvis jeg kun ville bruge sprog hvis oversættere var bygget med LLVM? Kravet om at et sprog skal kunne køre (godt) på JVM sætter en masse grænser for sprogdesignerens kreativitet, og det medfører uværgeligt mere kedelige sprog.

Derudover, i nutidens verden med mikrotjenester og decentraliseret arkitektur, er det så ikke nemmere end nogensinde før at have systemer der er skrevet i forskellige sprog?

Lidt on-topic: Jeg er stor tilhænger af Ceylon's syntaksfascisme. En af de få ting et sprog som Go gør meget rigtigt er at aflive cykelskursargumenter om formatering og deslige. Ved at kræve at syntaktisk forskellige elementer (f.eks. værdikonstruktører og variabelnavne) er leksikalt forskellige, f.eks. ved at førstnævnte skal starte med stort bogstav, kan man også tillade en letvægts-syntaks der stadigvæk er entydig. Det er en af grundene til at jeg synes Haskells syntaks for mønstergenkendelse er meget pænere end SMLs ditto.

Mikkel Lauritsen Blogger

Jeg forstår ikke, hvorfor du mener, at F#, Haskell og O'Caml ikke har statiske typer, det har de i allerhøjeste grad.

Men det var måske på grund af, at de ikke kører på JVM, at du fravalgte dem?

Lige præcis. Beklager, hvis det ikke fremgik tydeligt nok - indlæggets allerede store længde taget i betragtning tillod jeg mig at undlade en nærmere diskussion af, hvorfor de nævnte sprog blev fravalgt. De er dynamisk typede og/eller ikke på JVM'en.

Mikkel Lauritsen Blogger

Det er altid spændende at snakke programmeringssprog, men ikke hvis man lader diskussionen begrænse af implementeringsteknologi. Ville man ikke synes jeg var fjollet, hvis jeg kun ville bruge sprog hvis oversættere var bygget med LLVM?

Jo, men det er heller ikke det, som er baggrunden for mine valg - det er et spørgsmål om at have runtimeplatformen til rådighed.

Jeg er i den situation, at jeg har trediepartskode, som er knyttet JVM'en. Det gælder fx Seal, som er det officielle hjælpebibliotek til integration med webservices baseret på Den Gode Webservice, som mange sundheds-IT-relaterede systemer i Danmark (fx Fælles Medicinkort) bruger. Okay, Seal findes også på .Net, men det hjælper mig ikke i forhold til fx Haskell, og jeg har andre komponenter, som har tilsvarende begrænsninger.

Jeg har også som nævnt brug for at kunne lave en glidende migrering, fordi jeg har så meget kode, der skal flyttes, at det ikke er realistisk at gøre det på en gang.

Kravet om at et sprog skal kunne køre (godt) på JVM sætter en masse grænser for sprogdesignerens kreativitet, og det medfører uværgeligt mere kedelige sprog.

Arh, det ved jeg nu ikke rigtig. Hvad er det konkret, som du mener, at man ikke kan lave på JVM'en, som samtidig er vigtigt at have? Ceylon har fx reified generics, selv om Java bruger erasure, så hvor der er vilje er der vej.

Og "kedelig" er i øvrigt ikke nødvendigvis en dårlig ting, synes jeg. Jeg vil gerne have et kedeligt sprog, hvis det samtidig er brugbart.

Derudover, i nutidens verden med mikrotjenester og decentraliseret arkitektur, er det så ikke nemmere end nogensinde før at have systemer der er skrevet i forskellige sprog?

Måske er netop det aspekt med sprogligt heterogene systemer nemmere, men TANSTAAFL. Microservices har også deres ulemper, og det vil ikke kunne betale sig at hakke en eksisterende applikation op i smådele kun for at kunne genimplementere nogen af dem i nye sprog.

Mikkel Lauritsen Blogger

Er påstanden ikke at det er umuligt at kombinere global type inferens og objekt orienteret programmering, i hvert fald den type som Scala implementere?

Typeinferens gør det i hvert fald ikke alene. Man ender hurtigt med noget a la duck typing, hvis man ikke eksplicit kan sætte typer på variable osv., og det løser ikke problemet med at kunne navigere rundt i ens kode.

Torben Mogensen Blogger

Er påstanden ikke at det er umuligt at kombinere global type inferens og objekt orienteret programmering, i hvert fald den type som Scala implementere?

Helt annoteringsfri global typeinferens er uafgørlig, hvis der er subtyping (nedarvning), ja, men det kan lade sig gøre, hvis man enten fravælger dynamisk nedarvning (det ville være mit valg, man kan komme ret langt med statisk nedarvning) eller gør nedarvning eksplicit. Generelt vil jeg undgå features, der gør global typeinferens (i praksis) umulig. Der findes sprog, der i praksis fungerer fint med uafgørlige typesystemer, fordi de instanser, der er uafgørlige, forekommer meget sjældent.

Helt generelt synes jeg, at dynamisk nedarvning er oversolgt: De fleste af de fordele, det giver, fås lige så godt eller bedre med parametrisk polymorfi og statisk nedarvning, og i praksis medfører dynamisk nedarvning en del køretidscheck, da f.eks. A[] logisk set ikke er en subtype af B[], selv om A er en subtype af B. Derfor skal man enten lave et køretids typecheck når man indsætter værdier i et array (Javas løsning) eller tager værdier ud af et array (C#s løsning). Nedarvning i kombination med parametrisk polymorfi har et lignende problem.

Stort set det eneste sted, hvor dynamisk nedarvning giver en fordel, er heterogene samlinger, altså f.eks. lister, hvor elementerne har forskellige typer. Det kan dog klares med enten sumtyper eller eksistentielle typer i stedet.

Christian Lynbech

Jeg antager dit fravlag Clojure skyldes en formodning om at det ikke understøtter navigering. Jeg er indrømmet ikke den store Clojure men de af Clojures fætre jeg kender (Common Lisp, Emacs Lisp) har ikke nogen problemer med at vide præcis hvor en funktion er defineret eller hvilke argumenter en funktion tager. Da den slags har været given funktionalitet siden Lisp maskinerne kom frem i 70'erne skulle det under mig meget om ikke Clojure understøtter dette også.

Source introspection er altså ikke noget der er snævert knyttet til statiske typer. Er det noget du har undersøgt (mht Clojure) eller hoppede du direkte til konklusionen?

Mikkel Lauritsen Blogger

Source introspection er altså ikke noget der er snævert knyttet til statiske typer. Er det noget du har undersøgt (mht Clojure) eller hoppede du direkte til konklusionen?

Det er noget jeg har undersøgt.

I et dynamisk typet sprog ved man generelt ikke hvilken type en variabels værdi har (eller snarere kan have) før programmet kører. Man kan selvfølgelig udlede forskellige typeinformationer ved at lave statisk analyse baseret på operatorer o.a. i kildekoden, men det er mindre end man kan med et statisk typet sprog. Derfor er der også ting som fx automatiserede refaktoreringer, som er problematiske at lave i dynamisk typede sprog, fordi værktøjerne ikke med 100% sikkerhed ved, om al relevant kode bliver håndteret.

Og nej, jeg har ikke skrevet store mængder kode specifikt i Clojure (JavaScript er en anden sag), men det er der andre, der har, og som synes, at det lader noget tilbage at ønske. Der henvises ofte til tests, som bestemt er fine til at sikre, at man ikke knækker noget, men det er ikke automatisering.

Cursive ser ud til at kunne løse visse af problemerne - især på de steder, hvor man integrerer med Java og derfor har statiske typer til rådighed.

Mikkel Lauritsen Blogger

Fair nok, men det er så heller ikke helt den samme problemstilling som det at vide hvor ting er defineret henne, som var noget af det der blev fremhævet.

Det, at vide, hvor noget er defineret (og bruges henne), er vel en forudsætning for, at ens værktøjer er i stand til at lave automatiserede ændringer?

Det er så også en forudsætning for, at man selv er i stand til at forstå koden og beslutte, hvilke ændringer man gerne vil lave, men det er en anden sag.

Det er nok bare mig der er gammeldags, men jeg har lidt svært ved at se at automatiseret refaktorering kan gøre noget værdifuldt for en stor applikation.

Til daglig sidder jeg med kode, som over snart 20 år er vokset til i størrelsesordenen 500.000 linjer. Man bliver bare klogere som tiden går, så det er ofte nødvendigt at lave store, gennemgribende ændringer - det sker, at der laves rettelser i hundredevis af klasser på en gang - og det er bare meget mindre tidskrævende, når ens værktøjer kan gøre det for en.

Christian Lynbech

Det, at vide, hvor noget er defineret (og bruges henne), er vel en forudsætning for, at ens værktøjer er i stand til at lave automatiserede ændringer?

Det modsiger jeg ikke, men at vide hvor noget er defineret kræver ikke statiske typer. Har du en rimeligt moderne Emacs i nærheden, kan du nemt prøve det selv.

Jeg kender for lidt til automatisk refaktorering til at kunne vurdere i hvor høj grad statiske typer er nødvendige i forhold til at lave rene source-til-source ændringer eller "blot" at have haskell-niveau type-inferens.

Man skal ihverfald ikke blande det sammen med at visse sprog har så kludret en syntaks at bare det for et værktøj at parse det, nærmest synes uafgørligt. Det løser til eksempel Lisp ved at have en meget enkel og stringent syntaks som nemt lader sig læse ind og behandle.

Har du nogen eksempler på typer af refaktoreringer hvor statisk typning er en forudsætning?

Baldur Norddahl

Jeg vil hævde at statiske typer også er en kæmpe hjælp ved manuelle refaktoreringer. Hvordan ved du ellers at du ikke har lavet fejl? Det er nemt at overse noget, men så er det heldigt hvis oversætteren kan sætte en bremse i.

Troels Henriksen

Jeg kender for lidt til automatisk refaktorerinmg til at kunne vurdere i hvor høj grad statiske typer er nødvendige i forhold til at lave rene source-til-source ændringer eller "blot" at have haskell-niveau type-inferens.

Jeg vil lige indskyde at Haskell-niveau type-inferens faktisk er et endnu stærkere værktøj end Javas relativt typesystem, også selvom man ikke selv skal angive typer for hver variabelbinding. Blot fordi man skal skrive en masse boilerplate er typesystemet ikke nødvendigvis stærkt, eller nemmere at behandle for værktøjer.

Mikkel Lauritsen Blogger

Jeg kender for lidt til automatisk refaktorering til at kunne vurdere i hvor høj grad statiske typer er nødvendige i forhold til at lave rene source-til-source ændringer eller "blot" at have haskell-niveau type-inferens.

Jeg tror, at vi snakker forbi hinanden.

At et sprog er statisk typet betyder, at compileren ved, hvilken type et udtryks værdi har; i et dynamisk typet sprog kendes værdiens type først, når programmet køres.

I statisk typede sprog kan compileren så få typeinformationen fra eksplicitte angivelser af typer eller ved typeinferens, hvor den udleder typen ud fra den kontekst, udtrykket indgår i.

Haskell er statisk typet, men bruger typeinferens på samme måde som fx OCaml og SML gør det. Java bruger primært eksplicitte typeangivelser men også enkelte steder typeinferens - i kode som

List<Elem> elems = new ArrayList<>();

regner compileren ud, at højresiden er af typen ArrayList<Elem>.

Typeinferens kan spare lidt indtastning (og afhængigt af ens religiøse tilhørsforhold måske gøre ens kode mere fleksibel), men det er ikke uden problemer, især i forbindelse med sprog med subtyping.

Statiske typer (som altså fint kan opstå ved typeinferens) er absolut nødvendige for at et værktøj kan lave refaktoreringer - ellers kan det ikke lave ret meget andet end søg+erstat, og det er ikke nok til at sikre, at der ikke opstår fejl.

Man skal ihverfald ikke blande det sammen med at visse sprog har så kludret en syntaks at bare det for et værktøj at parse det, nærmest synes uafgørligt. Det løser til eksempel Lisp ved at have en meget enkel og stringent syntaks som nemt lader sig læse ind og behandle.


Netop Java er rimeligt let at parse - i modsætning til fx C++.

Har du nogen eksempler på typer af refaktoreringer hvor statisk typning er en forudsætning?

Alt, der involverer ændringer af en metodesignatur. Hvis jeg gerne automatisk fx vil omdøbe en metode, ændre typen af en parameter eller tilføje en ny parameter er det nødvendigt, at værktøjerne præcist kan finde både erklæringen af metoden og alle de steder metoden bliver kaldt.

Hvis jeg har to klasser C1 og C2, som begge har en metode kaldet grok(), og en dynamisk typet funktion i stil med

function grokify(x) {  
    x.grok();  
}

hvordan skal et værktøj så kunne regne ud, om en tilføjelse af en parameter til C1.grok() skal betyde en ændring i grokify() eller ej?

Christian Lynbech

ændre typen af en parameter

Det er jo her det lidt, for mig, begynde at ligne en selvskabt plage. Har man et dynamisk typet sprog har man jo netop ikke behov for at gå rundt og opdatere alle instanser.

Det er jo lidt det der er humlen, med rigtige statiske typer (som i Java), betaler man en pris for hver eneste sted (variabel/attribut/parameter), hver gang, hele tiden. Med dynamisk typning kan man lægge type-annoteringer på efter behov. Og selv med dynamisk typning er det jo ikke sådan at compileren bare giver op og helt lader være med at checke noget som helst.

Det er ikke fordi dit eksempel er forkert, ligesom det heller ikke er forkert at statisk typning vil forhindre visse klasser af fejl, problemet er bare om det i praksis betyder nok til at man genindvinder den pris man har betalt up front.

Det er selvfølgeligt klart at hvis man et 500.000 liniers program med tusindvis af klasser og masser af overloadede 4 bogstavs metode navne, så har man da brug for hjælp .... af værktøjet :-)

Mikkel Lauritsen Blogger

Det er jo her det lidt, for mig, begynde at ligne en selvskabt plage. Har man et dynamisk typet sprog har man jo netop ikke behov for at gå rundt og opdatere alle instanser.

Hvis man tilføjer en ekstra parameter til en metode har man. Defaultværdier kan redde en i nogen tilfælde, men ikke i alle.

Jeg har en fornemmelse af, at det med den selvskabte plage i mindst lige så høj grad gælder den anden vej. Fortalere for dynamisk typede sprog er vant til værktøjer, som kun i begrænset omfang kan hjælpe dem med at navigere rundt i deres kode, og derfor kan de ikke se, hvilke fordele man har af statiske typer - de har aldrig oplevet, hvor stor en hjælp de kan være.

Hvis man bruger Notepad til at programmere i Java er det ikke særlig sjovt.

Det er jo lidt det der er humlen, med rigtige statiske typer (som i Java), betaler man en pris for hver eneste sted (variabel/attribut/parameter), hver gang, hele tiden.

Hvori består den pris? De eksplicitte typer er en fordel, fordi de gør det nemmere for udvikleren at forstå koden og nemmere for værktøjerne at modificere den.

Det er ikke fordi dit eksempel er forkert, ligesom det heller ikke er forkert at statisk typning vil forhindre visse klasser af fejl, problemet er bare om det i praksis betyder nok til at man genindvinder den pris man har betalt up front.

Igen: statiske typer fanger fejl, men det er bare en sidegevinst. Den helt store fordel er, at man altid uden at køre koden utvetydigt ved, hvilken type "ting" har. Det er værdifuldt, når man skal vedligeholde kode, som andre mere kreative udviklere har skrevet.

Mikkel Lauritsen Blogger

Det er selvfølgeligt klart at hvis man et 500.000 liniers program med tusindvis af klasser og masser af overloadede 4 bogstavs metode navne, så har man da brug for hjælp .... af værktøjet :-)

For god ordens skyld: mit eksempel ovenfor var kogt ned for at eksemplificere strukturen. Det handler ikke om at give eksempler på repræsentative klasse- og metodenavne.

Torben Mogensen Blogger

Det er jo her det lidt, for mig, begynde at ligne en selvskabt plage. Har man et dynamisk typet sprog har man jo netop ikke behov for at gå rundt og opdatere alle instanser

Som Mikkel sagde, så er det kun simple ændringer såsom skift af typen af en enkelt parameter, hvor dynamiske typer gør det unødvendigt at omskriv en masse erklæringer. Lige præcis den samme fordel giver glibal typeinferens: Du skal ikke skrive typen af parametre, så hvis du laver en parameter om fra f.eks. heltal til bool, så skal du ikke omskrive erklæringer. Typeinferensen vil endvidere fortælle dig præcis, hvor det er nødvendigt at lave omskrivninger, f.eks. ved brug af konstanter.

Næsten alle de fordele, som dynamiske typer giver, får man også med global typeinferens. Igen er den eneste praktisk relevante undtagelse heterogene collections, hvor man skal bruge en sumtype (datatype), hvis man vil have fuld statisk typesikkerhed og global typeinferens. Eksistentielle typer kan også gøre det, men det kræver eksplicit typeannotering.

Christian Lynbech

Hvori består den pris? De eksplicitte typer er en fordel, fordi de gør det nemmere for udvikleren at forstå koden og nemmere for værktøjerne at modificere den.

Med et statisk type system er der en omkostning up-front i og med der skal skrives erklæringer på alle places og de skal vedligeholdes. Ændrer en type sig (eg nyt navn) er ikke bare noget kode muligvis berørt, men alle steder det gamle typenavn er skrevet ind, skal også opdateres. Med dynamiske typer eller statisk type inferens, der er ikke noget af den slags adminstration.

Det er ikke det samme som at der er en netto omkostning. Det er muligt (og mange er åbentlyst af den mening) at man får så mange tidsbesparelser længere henne i forløbet (som f.eks. tool assisted refaktorering eller forhindring af visse fejl) at det mere end opvejer den ekstra administration. Men der er en ekstra administration (rent source kode mæssigt) ved statiske typer som ikke findes i alternativerne.

Log ind eller opret en konto for at skrive kommentarer