Overbruggen van Menselijke Intentie en AI-Codegeneratie met @AIGuideline

Introductie

Nu AI-gedreven code-assistenten steeds vaker voorkomen in softwareontwikkeling, ontstaat er een nieuwe uitdaging: hoe zorgen we ervoor dat door AI gegenereerde code aansluit bij de architecturale patronen, ontwerpprincipes en codeerstandaarden van ons project?

Bestaande AI-richtlijnen, bijvoorbeeld junie/.guidelines, bestaan voor dit doel. Ze zijn echter vaak groot en lastig te onderhouden. Hoe zorgen we ervoor dat de richtlijnen consistent blijven met de codebase? Afhankelijk van het project kunnen ze veel informatie bevatten. Hoe groter het richtlijnbestand, hoe lastiger het voor de AI wordt om deze consequent te volgen.

Hoe kunnen we dit oplossen?

Maak kennis met @AIGuideline: een eenvoudige en krachtige annotatie waarmee architecturale en ontwerp-richtlijnen direct in de codebasis worden opgenomen, zodat ze direct vindbaar zijn voor AI-assistenten bij het genereren van code.

Het Probleem: AI-assistenten hebben context nodig

Moderne AI-code-assistenten zoals GitHub Copilot, JetBrains AI Assistant en anderen zijn uiterst bekwaam in het genereren van syntactisch correcte code. Echter, ze hebben vaak geen begrip van:

  • Architecturale patronen specifiek voor jouw project (bijv. use case-gedreven ontwerp, repository patterns)
  • Technologiekeuzes en de onderliggende motivatie hiervan (bijv. waarom Kotlin Exposed in plaats van JPA)
  • Cross-cutting concerns zoals teststrategieën of dependency injection frameworks
  • Project-specifieke conventies die afwijken van algemene best practices

Zonder deze context kan AI-gegenereerde code technisch wel werken, maar toch de architecturale principes van je project schenden, wat leidt tot technische schuld en inconsistentie.

De Oplossing: Zelfdocumenterende architectuur

De @AIGuideline-annotatie biedt een mechanisme om architecturale richtlijnen direct op de plek waar het telt—je code—te documenteren. Hier is de implementatie:

package nl.moxie.embeddings.common.annotations

/**
 * Annotation for documenting guidelines within the codebase.
 * 
 * @property description The description of the guideline.
 */
@Retention(AnnotationRetention.SOURCE)
@Target(
    AnnotationTarget.CLASS,
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY,
    AnnotationTarget.FILE,
    AnnotationTarget.VALUE_PARAMETER
)
annotation class AIGuideline(
    val description: String,
)

De annotatie is bewust eenvoudig:

  • Source-level retention: Geen runtime overhead, puur voor documentatie
  • Brede toepasbaarheid: Kan toegepast worden op classes, functies, properties, bestanden, en parameters
  • Enkelvoudige parameter: Een beknopte omschrijving van de richtlijn

Integratie met AI Assistant-richtlijnen

Om ervoor te zorgen dat AI-assistenten deze annotaties herkennen en respecteren, verwijs je ernaar in het AI-richtlijnenbestand van je project (meestal .junie/guidelines.md of iets dergelijks):

## Backend Development

Important: Any code or section annotated with @AIGuideline indicates 
a project rule or practice that must be strictly followed by AI code 
generation or suggestions. Always prioritize and adhere to the logic, 
constraints, and recommendations present in such annotated code when 
generating, revising, or explaining code for this project. Do not add 
the @AIGuideline annotation yourself.

Deze expliciete instructie zorgt ervoor dat AI-assistenten:

  1. De annotatie herkennen als een signaal voor belangrijke architecturale informatie
  2. Deze richtlijnen prioriteren bij het genereren of suggereren van code
  3. De grenzen respecteren—zelf geen annotaties toevoegen, maar de bestaande volgen

Praktijkvoorbeelden

Laten we kijken hoe @AIGuideline wordt gebruikt in een echte Kotlin/Ktor-applicatie om architecturale patronen aan AI-assistenten over te brengen.

Voorbeeld 1: Technologiekeuze

@AIGuideline("Database access is implementing using Kotlin Exposed")
class DocumentsExposedRepository : DocumentsRepository {
    // Implementation using Exposed SQL library
}

Wat dit communiceert aan AI:

  • Het project gebruikt Kotlin Exposed, niet JPA of andere ORMs
  • Bij het genereren van database code, volg Exposed-patronen
  • Stel geen alternatieve database libraries voor

Voorbeeld 2: Teststrategie

@AIGuideline("Time repository is implementing using Kotlin Time and should be used when the current time is needed to facilitate testing")
@OptIn(ExperimentalTime::class)
interface TimeRepository {
    fun now(): Instant
}

Wat dit communiceert aan AI:

  • Gebruik nooit System.currentTimeMillis() of Clock.systemUTC()
  • Injecteer altijd TimeRepository voor tijdsafhankelijke logica
  • Dit patroon is er speciaal voor deterministisch testen

Voorbeeld 3: Abstractielaag

@AIGuideline("This interface abstracts the searching of content from different technologies and sources.")
interface ContentSearcher {
    fun search(): List<Document>
}

Wat dit communiceert aan AI:

  • Zoeken is geabstraheerd om meerdere implementaties te ondersteunen
  • Bij toevoegen van zoekmogelijkheden, implementeer dit interface
  • Koppel zoeklogica niet direct aan specifieke technologieën

Voorbeeld 4: Dependency Injection Configuratie

@AIGuideline("The main module is responsible for configuring the Koin dependency injection framework")
fun mainModule(environment: ApplicationEnvironment) = module {
    single<String>(named(OPEN_API_KEY)) {
        System.getenv("OPENAI_API_KEY")
            ?: throw IllegalStateException("OPEN_API_KEY environment variable is required")
    }
    
    single<DataSource> {
        val config = HikariConfig()
        // Configuration...
        HikariDataSource(config)
    }
    
    // Additional dependency configurations...
}

Wat dit communiceert aan AI:

  • Het project gebruikt Koin voor dependency injection
  • Alle DI-configuratie gebeurt in deze modulefuncties
  • Bij toevoegen van dependencies, registreer deze hier volgens Koin-patronen

Voorbeeld 5: API-encapsulatie

@AIGuideline("The OpenAI API's are encapsulated in the OpenAIApi implementation")
interface OpenAIApi {
    suspend fun createEmbeddings(input: List<String>): EmbeddingsResponse
    suspend fun chatCompletion(messages: List<ChatMessage>): ChatCompletionResponse
}

Wat dit communiceert aan AI:

  • Externe API-aanroepen van OpenAI moeten via dit interface gaan
  • Maak geen directe HTTP-calls naar OpenAI elders in de codebase
  • Deze abstractie maakt testen en eventueel wisselen van API-provider mogelijk

Voordelen van deze aanpak

1. Samenkomst van intentie en implementatie

Richtlijnen staan precies waar ze nodig zijn, niet in een apart document dat kan verouderen.

2. Vindbaarheid

Wanneer een AI-assistent een bestand analyseert, ziet die direct de architecturale richtlijnen via deze annotaties.

3. Versiebeheer

Richtlijnen evolueren mee met je code. Bij refactoring reist de richtlijn mee met de code.

4. Consistentie in AI-gegenereerde code

AI-assistenten kunnen architecturale consistentie behouden, ook bij het genereren van complexe code die ze niet eerder zagen.

5. Ondersteuning bij onboarding

Nieuwe ontwikkelaars (mens of AI) begrijpen de architecturale patronen sneller via de geannoteerde interfaces en base classes.

6. Eenvoudigere algemene richtlijnen

Het toevoegen van @AIGuidline minimaliseert de benodigde informatie in .junie/guidelines.md, waardoor onderhoud en updates eenvoudiger worden.

Best Practices

1. Wees beknopt maar duidelijk

// Goed: Duidelijk en toepasbaar
@AIGuideline("Use repository pattern for all database access")

// Te vaag
@AIGuideline("Database stuff goes here")

// Te uitgebreid
@AIGuideline("This repository should be used whenever you need to access the database because we've chosen to use the repository pattern as described in Clean Architecture by Robert Martin...")

2. Focus op architecturale beslissingen

Gebruik @AIGuideline voor keuzes die de structuur van code beïnvloeden, niet voor triviale implementatiedetails:

// Goed: Architecturaal patroon
@AIGuideline("Validation logic is implemented as extension functions on input types")

// Onnodig: Taalconventie
@AIGuideline("Use data classes for immutable data")

3. Pas toe op het juiste niveau

  • Interfaces/Abstracte klassen: Definieer patronen en contracten
  • Top-level functies: Leg projectbrede utilities of conventies vast
  • Bestanden: Documenteer module-brede beslissingen

4. Annoteer niet te veel

Zoals aangegeven in de richtlijnen: "Voeg de @AIGuideline-annotatie niet zelf toe." Deze instructie is voor AI-assistenten—gebruik de annotatie spaarzaam, enkel voor écht belangrijke architecturale beslissingen, niet voor elke klasse of functie.

5. Vul aan, vervang geen documentatie

@AIGuideline-annotaties werken het beste samen met goede documentatie. Gebruik annotaties voor snelle, toepasbare aanwijzingen en documentatie voor uitgebreidere uitleg.

Conclusie

De @AIGuideline-annotatie vertegenwoordigt een paradigmawisseling in de manier waarop we architecturale intentie communiceren. Door richtlijnen direct in de codebasis op te nemen, creëren we een zelfdocumenterende architectuur die zowel menselijke ontwikkelaars als AI-assistenten kunnen begrijpen en respecteren.

Naarmate AI-ondersteunde ontwikkeling verder evolueert, worden dit soort technieken steeds belangrijker. Code wordt niet alleen een set instructies voor computers, maar ook een naslagwerk voor AI-assistenten om contextueel passende, architectonisch juiste code te genereren.

Of je nu samenwerkt met AI-programmeurs van vandaag of voorbereidt op ontwikkelpraktijken van morgen, @AIGuideline biedt een eenvoudige, elegante manier om je architecturale visie te bewaken en uit te dragen in je codebase.

Het concept van de @AIGuideline-annotatie is taalonafhankelijk. Hoewel de voorbeelden hier in Kotlin zijn, is dezelfde aanpak toepasbaar in andere programmeertalen.

Referenties

Volgende
Volgende

💥 Werken IN je bedrijf vs. werken AAN je bedrijf