JDepper

Den här artikeln presenterar det lilla verktyget JDepper som är ett program skrivet i Smalltalk (närmare bestämt Squeak) vilket analyserar beroenden i Javaprogram.

JDepper utvecklades på BlueBrim för att analysera och hantera beroenden bland cirka 5000 Javaklasser.

Problemet

När vi bygger program (visst låter det roligare än ordet "system"? :-), framför allt riktigt stora sådana, behöver vi verktyg för att kontrollera beroenden mellan olika delar av programmet. Typiska exempel är att vi vill säkerställa att verksamhetsobjekten inte är beroende av användargränssnittsobjekten eller att funktionellt olika delar i ett program inte är beroende av varandra mer än nödvändigt. Ofta är dubbelriktade beroenden mindre bra och cirkulära beroenden vill vi verkligen inte ha.

För att minska våra beroenden strukturerar vi ofta upp program i en "lagermodell" där de övre lagren är beroende av 1 eller flera underlager men de undre lagren är helt oberoende av lagren ovanför.

Helst skulle vi vilja att utvecklingsmiljön såg till att vi följer våra fastslagna regler - men ifall det av olika skäl är mindre lätt att åstadkomma vill vi i alla fall att vår byggmiljö kontrollerar reglerna.

Efter att ha letat runt på Internet efter verktyg för att göra dessa beroendekontroller i Java utan att hitta något bra verktyg för detta, beslöt sig BlueBrim för att bygga ett eget. Eftersom det var jag som fick uppgiften att göra det på så kort tid som möjligt valde jag att göra det i min favoritmiljö - Squeak - en mycket bra Smalltalk under en fri licens.

Vad vi skulle vilja kunna göra

När jag använder ordet "typ" i den följande beskrivningen betyder det att det kan vara en klass eller ett interface i Java.

Vi skulle vilja kunna få svar på frågor som exempelvis:

I stora projekt är Javapaket lite för små som enhet för att beskriva systemets olika delar och lager. Sålunda inför vi ett nytt begrepp - moduler. En modul består av ett eller flera Javapaket och för att vi skall kunna dela upp systemet beroende på flera olika aspekter låter vi modulerna kunna överlappa varandra, d v s ett Javapaket kan ingå i flera moduler. För total flexibilitet låter vi också moduler kunna konstrueras genom aggregering av andra moduler enligt Composite-mönstret - sålunda består en modul av ett eller flera Javapaket och en eller flera andra moduler.

Sedan när vi har delat upp Javapaketen i ett antal olika moduler vill vi dels kunna besvara frågor som:

Framför allt vill vi kunna sätta upp regler för vilka beroenden vi vill tillåta för våra olika moduler. Ett vanligt exempel är att vi vill dela upp programmet i en lagermodell. I en lagermodell tillåter man normalt att de övre lagren beror på de direkt underliggande lagren men absolut inte tvärtom - beroenden skall vara riktade neråt i modellen.

Slutligen, när vi beskrivit våra moduler och vilka beroenden vi tillåter dem emellan vill vi kunna få en detaljerad rapport över vilka moduler som bryter reglerna och på vilket sätt. En sådan rapportkörning skulle vi slutligen gärna vilja integrera i utvecklingsmiljön eller i byggprocessen så att vi relativt snabbt får feedback till utvecklarna om att reglerna brutits.

Som "grädde på moset" skulle det också vara trevligt ifall verktyget kunder rensa upp bland importsatserna i koden. All information finns ju för att verktyget skall kunna automatgenerera importsatserna så att de alltid är "minimala".

Lösningen

Visst är det roligt att bygga parsers, men man skall akta sig för onödigt arbete! Efter lite efterforskning visade det sig att den ypperliga Java kompilatorn Jikes kan generera fullständiga s k dependency reports under kompileringen såhär:

jikes +DR=deps.txt *.java

Detta skriver ner alla beroenden i filen deps.txt för varje körning av Jikes. Typiskt blir det då en deps.txt per Java paket.

JDepper börjar med att läsa in ett filträd med alla Javakällkod och deps.txt-filerna från Jikes och bygger upp en modell över klasserna, gränssnitten, paketen och alla deras inbördes beroenden. Våra körningar på BlueBrim har visat att ett källkodsträd med cirka 1700 Javafiler tar runt 90 sekunder att läsa in. Den föregående kompileringen med Jikes tar ungefär 5 minuter. (Detta kördes på en relativt gammal PC (Pentium II, 300Mhz) med Windows NT version 4.0, NTFS och Squeak version 3.0).

Denna modell kan därefter deklarativt partitioneras upp i moduler. Nedanstående är ett påhittat exempel, beskrivningen är skriven i Smalltalk vars syntax kanske kan verka underlig men jag har sprängt in lite kommentarer så att det blir förståeligt ändå:

// Deklarera ett antal temporära variabler:
| javax dnd ui utils business |
// Skapa en ny modul kallad "JavaX" och tilldela den till variabeln "javax":
javax := MyProject newModuleNamed: 'JavaX'.
// Lägg till alla paket som börjar på "javax." till modulen "Java":
javax addPackages: 'javax.*'.

utils := MyProject newModuleNamed: 'Utilities'.
// Här vill vi bara lägga till paketet networking
// och inga av dess underpaket - ingen asterisk: 
utils addPackages: 'se.bamba.myproject.networking'.
utils addPackages: 'se.bamba.myproject.filetools'.
utils addPackages: 'se.bamba.myproject.utilities'.

dnd := MyProject newModuleNamed: 'DragAndDrop'.
dnd addPackages: 'se.bamba.myproject.draganddrop*'.
dnd addPackages: 'se.bamba.myproject.graphics'.

ui := MyProject newModuleNamed: 'UserInterfaces'.
ui addPackages: 'se.bamba.myproject.dialogs*'.
ui addPackages: 'se.bamba.myproject.explorer'.

business := MyProject newModuleNamed: 'BusinessObjects'.
business addPackages: 'se.bamba.myproject.customer.*'.
business addPackages: 'se.bamba.myproject.account.*'.

...avslutningsvis beskriver vi våra regler:

// Lägg till modulen "Utilities" som en tillåten
// modul för modulen "DragAndDrop":
dnd addApprovedModule: utils.
dnd addApprovedModule: javax.
ui addApprovedModule: business.
ui addApprovedModule: dnd.
ui addApprovedModule: utils.
ui addApprovedModule: javax.

Ovanstående regler säger att modulen "DragAndDrop" får endast ha beroenden mot modulerna "Utilities" och "JavaX". Modulen "UserInterfaces" får ha beroenden mot modulerna "Business", "DragAndDrop", "Utilities" samt "JavaX".

Modulbeskrivningen med tillhörande regler lagrar man enklast i en textfil som man läser in efter att JDepper har läst in all källkod och byggt upp grundmodellen. Detta steg går mycket fort att exekvera, någon sekund med en källkodsbas på cirka 1700 Javafiler.

Nu är vi framme vid det sista steget - att köra en rapport, vilket tar ungefär 10 sekunder för mina 1700 Javafiler. När man kör en rapport finns det ett tiotal olika flaggor man kan sätta för att få olika detaljnivåer mm. Så här skulle ett exempel på en väldigt enkel rapport kunna se ut för ovanstående system:

Module: Utilities
    Submodules:
    Approved modules:
    Used modules:
    Unapproved used modules:
    Unapproved used modules with details:
    Approved modules not used:

Module: DragAndDrop
    Submodules:
    Approved modules:
        Utilities
        JavaX
    Used modules:
        Utilities
    Unapproved used modules:
    Unapproved used modules with details:
    Approved modules not used:
        JavaX

Module: UserInterfaces
    Submodules:
    Approved modules:
        Business
        DragAndDrop
        Utilities
        JavaX
    Used modules:
        Business
        DragAndDrop
        Utilities
        JavaX
    Unapproved used modules:
    Unapproved used modules with details:
    Approved modules not used:

Module: BusinessObjects
    Submodules:
    Approved modules:
    Used modules:
        Utilities
    Unapproved used modules:
        Utilities
    Unapproved used modules with details:
        Utilities
            Package: se.bamba.myproject.customer
                Interface: se.bamba.myproject.customer.interfaces.NamedObject uses:
                    se.bamba.myproject.utils.DatePattern
            Package: se.bamba.myproject.account.logging
                Class: se.bamba.myproject.account.logging.Logger uses:
                    se.bamba.myproject.filetools.LogFile
                    se.bamba.myproject.filetools.LoggableFile
    Approved modules not used:

Summary

Modules: 4
    Complete: 3
    Incomplete: 1
    Approved: 3
    Unapproved: 1

En komplett modul använder alla moduler som den är tillåten att använda. En inkomplett modul använder sålunda inte alla de som är tillåtna. En godkänd modul använder inga otillåtna moduler, en underkänd använder otillåtna moduler. Det perfekta resultatet är sålunda 4 kompletta, 0 inkompletta, 4 godkända och 0 underkända.

I ovanstående är det DragAndDrop-module som är inkomplett och BusinessObjects-modulen som är underkänd.

Den allra sista finessen som lades till i JDepper är just automatgenerering av importsatserna, men det har vi ännu inte hunnit testa ordentligt.

Slutsats

JDepper är ett litet verktyg men det kan faktiskt göra stor nytta och vi skulle mycket enkelt kunna bygga ut det för att klara av mer komplexa problem.

Ifall du blev lite intresserad av JDepper och skulle vilja använda det i ditt projekt för att få bättre kontroll på dina beroenden - hör av dig!

/Göran Krampe