Java-guru: Moderne Java er fuld af problemer

Joshua Bloch var leder af udviklingen af Java 3 til 5 og er en kendt skikkelse i Java-verdenen. Illustration: Bob Lee
Boganmeldelse: I tredje udgave af klassikeren 'Effective Java' tager en tidligere Java-styrmand bladet fra munden og fremfører sin uforbeholdne kritik.

Joshua Bloch, som var styrmand bag udviklingen af Java 3 til 5, har sendt tredje udgave af sin klassiske bog 'Effective Java' på gaden. Inspireret af en anden klassiker, 'Effective C++', gennemgår den som andre bøger i genren en lang række tips om korrekt anvendelse af Java – den slags tips, som man ikke lige lærer i introkurser eller på internettet, men som er resultatet af mange års erfaring.

Joshua Bloch er ud over at være skarp en glimrende formidler, der giver baghjul til langt de fleste forfattere af programmeringslitteratur.

Nu er bogen – der så dagens lys i 2001 – kommet i tredje udgave. Og forfatterens entusiasme for sin gamle platform er ikke længere ubetinget:

»Jeg kan stadig godt lide Java, selvom min begejstring har taget en smule af, i takt med at platformen er vokset,« skriver Joshua Bloch i forordet.

Han lægger ikke fingrene imellem i sin kritik af funktioner, som er kommet til siden sidste udgave af bogen i 2008, og den kritik går implicit også ud over flere af hans tidligere kolleger, der stadig deltager i Javas løbende udvikling.

Undgå Java 9-moduler

Den rammer blandt andet Java 9’s moduler, der giver mulighed for opsplitning af kode i større bunker. Men adgangsbeskyttelsen er kun vejledende: Hvis man indlæser et kodebibliotek, en såkaldt Jar-fil, på den gammeldags facon, bliver programmøren ikke tvunget til at overholde de regler om adgang, som modulerne specificerer. Han råder programmørerne til at droppe faciliteten.

Læs også: Kæmpe omlægning i Java 9: Sådan bliver koden skåret i bidder

»Ikke alene har adgangsbeskyttelsen af ​​moduler begrænset anvendelighed for den typiske Java-programmør, den er også i høj grad af rådgivende karakter. For at udnytte faciliteten skal man gruppere pakkerne i moduler, gøre alle deres afhængigheder eksplicitte i modulerklæringer, omarrangere kildetræet og tage særlige forholdsregler for at imødekomme enhver adgang til ikke-modulariserede pakker fra ens moduler. Det er for tidligt at sige, om moduler vil blive udbredt. I mellemtiden synes det bedst at undgå dem, medmindre man har et tvingende behov.«

Standardmetoder knækker eksisterende klasser

Før Java 8 kunne man ikke tilføje metoder til eksisterende interfaces uden at få kompileringsfejl i de klasser, der implementerer interfacet. Sådan er det ikke længere. Faciliteten ‘default method’ – standardmetode, som kan minde lidt om mixins og traits i andre sprog, ved at introducere kode i en klasse uden nedarvning – har sat den grundregel ud af spil, påpeger Joshua Bloch. Faciliteten blev indført for at retrofitte gamle klassebiblioteker til Java 8’s closures og funktionelle muligheder.

»Selvom tilføjelsen af ​​standardmetoder til Java gør det muligt at tilføje metoder til et eksisterende interface, er der ingen garanti for, at disse metoder vil fungere i alle tidligere eksisterende implementeringer. Standardmetoder er ‘injiceret’ i eksisterende implementeringer uden viden eller samtykke fra den, der har skrevet klientkoden. Før Java 8 blev disse implementeringer skrevet med en stiltiende forståelse af, at deres interfaces aldrig ville få nye metoder.«

Han nævner specifikt, at standardmetoden java.util.Collection.removeIf() knækker Apache-projektets klasse org.apache.commons.collections4.collection.SynchronizedCollection, på trods af at det pågældende bibliotek er under aktiv udvikling (da bogen blev skrevet). Apache-folket har ganske simpelt ikke været klar over, at den nye standardmetode underminerer bibliotekets synkroniseringsfunktion. Det kan i sidste ende give parallelliseringsfejl under kørselstid for de brugere, der benytter Apaches bibliotek – en fejltype, som er notorisk svær at få styr på, da den ofte ikke er deterministisk og derfor ikke nødvendigvis fanges af test.

Hold fingrene væk fra eksisterende interfaces, lyder rådet fra Joshua Bloch:

»Brug af standardmetoder til at føje nye metoder til eksisterende interfaces bør undgås, medmindre behovet er kritisk. I så fald bør man tænke længe og dybt, om et eksisterende interface kan blive knækket af ens standardmetode.«

Men situationen er anderledes, hvis der er tale om frisk kode:

»Standardmetoder er imidlertid meget nyttige til at levere implementeringer, når et interface oprettes, for at lette opgaven med at implementere interfacet.«

Pas på parallelle streams

Et andet nyt emne, som tredjeudgaven af bogen behandler, er streams. Med streams, som er Java 8’s måde at tilnærme sig de funktionelle sprogs evner på med hensyn til lister og processering af elementer på en liste, lyder indsigten, at man skal være varsom med parallelisering af kode - noget, som ellers er en dyd i den moderne flerkerne-virkelighed.

I Java 8 er det nemlig såre nemt at parallelisere en stream: Man skal bare smide metoden parallel() ind i sin stream-pipeline, og så går det helt af sig selv. Det kan i visse tilfælde fordoble eller firedoble ydelsen, uden at yderligere krumspring er påkrævet.

Men parallel() kan også give uforudsigelig opførsel. Som eksempel giver han denne kodestump:

// Stream-based program to generate the first 20 Mersenne primes 
public static void main(String[] args) {
   primes().map(p -> TWO.pow(p.intValueExact())
      .subtract(ONE))
      .filter(mersenne -> mersenne.isProbablePrime(50)) 
      .limit(20) .forEach(System.out::println); 
}
 
static Stream<BigInteger> primes() {
   return Stream.iterate(TWO, BigInteger::nextProbablePrime); 
}

På Joshua Blochs computer tager det 12,5 sekunder at udføre programmet, men hvis man indsætter et parallel()-metodekald i streamen, går programmet ind i en uendelig løkke. Synderen her er limit()-kaldet (hvad API-dokumentationen retfærdigvis også gør opmærksom på), men det kan man ikke nødvendigvis lige se ved et hurtigt kig på koden. Det er også en af grundene til, at Joshua Bloch fraråder at benytte streams som returværdi i metoder, og at man i øvrigt er varsom med at parallellisere streams.

Serialisering er Javas svage punkt på sikkerheden

Den sidste opdatering i tredje udgave af Effective Java er i og for sig ikke nogen nyhed, da emnet også optrådte i den allerførste udgave fra 2001.

Men stadig værd at lægge mærke til: Javas serialisering er sikkerhedsmæssigt en farlig størrelse. Serialisering er processen, hvor et objekt laves om til en strøm af bytes, som kan sendes over nettet og gemmes på disk for senere at blive indlæst igen. Joshua Bloch skriver:

»Da serialisering blev tilføjet til Java i 1997, var det kendt, at det kunne være risikabelt. Tilgangen var blevet forsøgt i et forskningssprog (Modula-3), men aldrig i et produktionssprog. Mens løftet om distribuerede objekter med lille indsats fra programmørens side var tiltalende, så var prisen, der skulle betales, usynlige constructor-metoder samt slørede linjer mellem API og implementering, med potentielle problemer med korrekthed, ydeevne, sikkerhed og vedligeholdelse til følge. Fortalerne troede, at fordelene ville opveje ulemperne, men historien har vist noget andet.«

Her nævner han som eksempel et ransomware-angreb på San Franciscos tog, hvis billetsystem blev lagt ned i to dage.

Et langt værre eksempel, som nok er indtruffet efter bogens færdiggørelse, er det amerikanske firma Equifax, hvor 143 millioner forbrugeres personoplysninger blev lækket på grund af manglende installation af en patch til Java-biblioteket Apache Struts. Også her var det serialisering, der lå bag.

Læs også: Fejl i udbredt Java-bibliotek bag amerikansk kæmpelæk

Problemet er, at der simpelthen bare er for mange angrebspunkter at gå til for hackerne. For hvert enkelt objekt skal der skrives kode, som indlæser og udlæser objektet. Blot en enkelt fejl kan betyde adgang for hackerne. Her lyder bogens pragmatiske råd: Brug JSON til tekst-baseret serialisering og Googles Protocol Buffers til binære data.

Joshua Bloch: Effective Java: Programming Language Guide. Addison–Wesley 2018.

Tips og korrekturforslag til denne historie sendes til tip@version2.dk
Følg forløbet
Kommentarer (0)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Log ind eller Opret konto for at kommentere