Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 6 Min.

Review von KI-generiertem Code

Der Code kompiliert. Die Tests sind grün. Der Pull Request sieht sauber aus. Und trotzdem ist er falsch. Fünf Fehlermuster zeigen, worauf die Reviewer beim KI-generierten Code achten müssen, weil Syntax-Korrektheit längst nicht Geschäftslogik-Korrektheit bedeutet.
Review.png
© Gemini

Dienstagmorgen, Sprint Review. Ein Kollege hat einen Coding-Agenten beauftragt, die Bestellauswertung zu überarbeiten. Der Pull Request sieht vorbildlich aus: saubere Struktur, vollständige Unit-Tests. Der Code kompiliert, die Pipeline ist grün. Im Review fällt niemandem etwas auf, der PR wird gemergt. Drei Wochen später meldet das Controlling, dass die Umsatzzahlen pro Filiale nicht mehr stimmen. Die Ursache: ein falscher Join. Die Abfrage verknüpfte Bestellungen über die Branch-ID des Kunden statt über die Branch-ID der Bestellung. Kunden, die in einer Filiale registriert sind, aber in einer anderen bestellen, wurden falsch zugeordnet. Der Code war syntaktisch einwandfrei. Die Tests prüfen, ob Daten zurückkamen. Nur prüfen sie nicht, ob es die richtigen waren.

Kein Einzelfall, sondern das typische Fehlermuster von KI-generiertem Code: technisch korrekt, semantisch falsch. Die ersten beiden Teile dieser Serie zum Thema „Projektanforderungen an Entwickler in Zeiten von KI“ haben gezeigt, wie man einem Agenten Architekturwissen mitgibt und Aufgaben präzise formuliert. Dieser dritte Teil behandelt die letzte Verteidigungslinie: den Code-Review. Fünf Muster, die bei KI-generiertem Code häufig auftreten, jeweils mit einem C#-Beispiel, das kompiliert, Tests besteht und trotzdem falsch ist.

Muster 1: Falsche Join-Bedingungen

Der häufigste und gefährlichste Fehler betrifft die Datenabfrage. Agenten erzeugen syntaktisch korrekte LINQ-Queries, die plausibel aussehen, aber in der Semantik der Verknüpfung falsch sind. Ein Agent, der eine Umsatzauswertung pro Filiale schreiben soll, produziert folgenden Code:

 

public async Task<List<BranchRevenueDto>>
        GetRevenueByBranch(int year)
{
    return await _context.Orders
        .Where(o => o.OrderDate.Year == year)
        .Join(_context.Customers,
            o => o.CustomerId,
            c => c.Id,
            (o, c) => new { o, c })
        .GroupBy(x => x.c.BranchId)
        .Select(g => new BranchRevenueDto
        {
            BranchId = g.Key,
            Total = g.Sum(
                x => x.o.TotalAmount)
        })
        .ToListAsync();
}

 

Die Abfrage kompiliert und liefert Ergebnisse. Ein Unit-Test mit einem Kunden, der in derselben Filiale bestellt, in der er registriert ist, wird grün. Aber die Gruppierung erfolgt über c.BranchId, die Stammfiliale des Kunden, nicht über o.BranchId, die Filiale der Bestellung. Sobald Kunden filialübergreifend bestellen, stimmen die Zuordnungen nicht. Korrekt wäre eine Gruppierung über o.BranchId, ohne den überflüssigen Join auf Customers. Die Review-Frage: Wird über die richtige Entity gruppiert, oder hat der Agent eine Indirektion eingefügt, die die Semantik verschiebt?

Muster 2: Übersehene Edge Cases

Agenten erzeugen Code für den Normalfall. Randwerte wie negative Eingaben, Nullwerte oder Nebenläufigkeit werden übersehen, weil sie in der Aufgabenstellung nicht erwähnt werden. Ein typisches Beispiel:

 

public decimal CalculateDiscount(
        Order order)
{
    decimal rate = order.Quantity switch
    {
        >= 100 => 0.15m,
        >= 50  => 0.10m,
        >= 10  => 0.05m,
        _      => 0m
    };
    return order.UnitPrice
        * order.Quantity * rate;
}

 

Der Code besteht jeden Test mit positiven Mengen. Aber was passiert bei einer Retoure, wenn Quantität den Wert minus 50 hat? Der Wert fällt in den Default-Zweig, der Rabatt ist null, die Gutschrift wird ohne den ursprünglichen Rabatt berechnet. Der Kunde bekommt mehr zurück, als er bezahlt hat. Eine Guard Clause für negative Werte hätte das verhindert. Im Review sollte man bei jeder Berechnung die Extremwerte durchspielen: null, negative Werte, Überlauf. Agenten optimieren auf den Happy Path, weil ihre Trainingsdaten überwiegend Happy-Path-Code enthalten.

Muster 3: Silent Failures

Agenten haben gelernt, dass produktionsreifer Code Exceptions behandeln soll, und interpretieren das als Verschlucken statt Behandeln. Ein Beispiel aus einer Zahlungsverarbeitung:

 

public async Task<PaymentResult>
        ProcessPayment(PaymentRequest req)
{
    try
    {
        var charge = await _gateway
            .ChargeAsync(req.Amount,
                req.CardToken);
        await _orderRepo
            .UpdateStatusAsync(
                req.OrderId,
                OrderStatus.Paid);
        return new PaymentResult
            { Success = true };
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            "Payment issue: {Msg}",
            ex.Message);
        return new PaymentResult
            { Success = false };
    }
}

 

Auf den ersten Blick saubere Fehlerbehandlung: Es wird geloggt, ein Fehlerstatus zurückgegeben. Aber wenn ChargeAsync erfolgreich ist und UpdateStatusAsync fehlschlägt, wird die Kreditkarte belastet, die Bestellung bleibt jedoch im Status offen. Der Catch-Block behandelt beide Fehlerquellen identisch. LogWarning statt LogError sorgt dafür, dass Alerts nicht auslösen. Und Success = false suggeriert dem Aufrufer, dass keine Belastung stattfindet. Im Review muss man bei jedem Try-Catch prüfen, ob der Catch alle Fehlerzustände korrekt behandelt oder Teilausführungen maskiert.

Muster 4: Security-Antipatterns

Sicherheitslücken in KI-generiertem Code entstehen durch Auslassungen. Der Agent implementiert die Geschäftslogik, vergisst aber Autorisierung oder baut dynamische Queries ohne Parametrisierung:

 

[HttpGet("{tenantId}/invoices")]
public async Task<IActionResult>
        GetInvoices(int tenantId,
        [FromQuery] string filter)
{
    var sql = $"SELECT * FROM Invoices" +
        $" WHERE TenantId = {tenantId}"
        + (string.IsNullOrEmpty(filter)
            ? ""
            : $" AND Description LIKE" +
              $" '%{filter}%'");
    var invoices = await _context
        .Invoices
        .FromSqlRaw(sql)
        .ToListAsync();
    return Ok(invoices);
}

 

Drei Sicherheitsprobleme, keines ein Kompilierungsfehler. Erstens fehlt ein Authorize-Attribut, der Endpoint ist öffentlich erreichbar. Zweitens fehlt eine Prüfung, ob der Benutzer zum angegebenen Tenant gehört. Drittens erzeugt die String-Konkatenation im Filter-Parameter eine SQL-Injection-Lücke. Der Agent hat die fachliche Anforderung korrekt umgesetzt. Aber Sicherheit war nicht Teil der Aufgabenstellung, also ist sie nicht Teil der Lösung. Im Review: Authentifizierung vorhanden? Mandantenzugehörigkeit validiert? Eingaben parametrisiert?

Muster 5: Architekturverstöße

Das fünfte Muster verursacht keine unmittelbaren Fehler, aber es zerstört die Architektur. Agenten packen Geschäftslogik in Controller und greifen direkt auf den DbContext zu, weil das der kürzeste Weg ist:

 

[HttpPost("cancel")]
[Authorize]
public async Task<IActionResult>
        CancelOrder(int orderId)
{
    var order = await _context.Orders
        .Include(o => o.Items)
        .FirstAsync(
            o => o.Id == orderId);
    if (order.Status != 
        OrderStatus.Pending)
        return BadRequest(
            "Nur offene Bestellungen");
    order.Status =
        OrderStatus.Cancelled;
    foreach (var item in order.Items)
        item.Product.Stock +=
            item.Quantity;
    order.CancelledAt = DateTime.Now;
    await _context.SaveChangesAsync();
    return Ok();
}

 

Funktional korrekt: Statusänderung, Bestandsbuchung, Zeitstempel. Aber die gesamte Stornologik steckt im Controller statt in einem Handler. Der Controller greift direkt auf den DbContext zu statt über ein Repository. DateTime.Now anstelle einer injizierten IDateTimeProvider macht die Methode untestbar. Die Bestandszurückbuchung ist nicht transaktionssicher, weil die Products nicht explizit geladen werden. In einem CQRS-Projekt mit MediatR würde das als CancelOrderCommand mit eigenem Handler implementiert. Im Review erkennt man dieses Muster daran, dass der Controller mehr als Routing und Delegation enthält.

Die Review-Checkliste

Fünf Muster, fünf Fragen für jeden Review von KI-generiertem Code. Queries: Wird über die richtige Entity verknüpft? Berechnungen: Was passiert bei negativen Werten und Extremfällen? Fehlerbehandlung: Maskiert der Catch-Block Teilausführungen? Endpoints: Sind Authentifizierung, Autorisierung und Eingabevalidierung vorhanden? Architektur: Liegt die Logik in der richtigen Schicht?

Diese Checkliste ersetzt keinen gründlichen Review, aber sie lenkt die Aufmerksamkeit auf die Stellen, an denen KI-generierter Code am zuverlässigsten versagt. Die Gemeinsamkeit aller fünf Muster: Der Fehler ist nicht im Code sichtbar, sondern erst im Abgleich mit der Geschäftslogik, den Sicherheitsanforderungen oder der Projektarchitektur. Genau das macht Review von KI-Code so anspruchsvoll: Es ist keine Syntaxprüfung, es ist eine Geschäftslogik-Prüfung. Der Hauptartikel dieser Serie [1] zeigt, wie sich die Rolle des Entwicklers verschiebt, vom Codeschreiber zum Qualitätssicherer, der versteht, was der Code tun soll, und prüfen kann, ob er es tut.

[1] Patrick Schnell, Der Stellenwert von Developern in Zeiten von KI, dotnetpro 4/2026, Seite 6 ff.

Neueste Beiträge

Laufzeitgewinn messen - Schnellere .NET-CI-Pipelines mit Test Sharding, Teil 4
Mehr Shards können das Feedback beschleunigen, aber auch zugleich die CI-Kosten erhöhen. Ein Vergleich mit zwei Referenzläufen zeigt, wo sich zusätzliche Runner lohnen. Ebenso wichtig ist der Nachweis, dass weiterhin alle vorgesehenen Tests laufen.
6 Minuten
Vom vagen Ticket zum präzisen Prompt - Projektanforderungen an Entwickler in Zeiten von KI, Teil 2
Ein Jira-Ticket sagt: „Bewertungssystem implementieren.“ Ein Mensch liest und denkt den Kontext mit. Ein Agent nicht. Fünf Prompt-Patterns zeigen, wie der Weg vom Ticket zum funktionierenden Code kürzer wird, wenn man lernt, in Kontext statt in Code zu denken.
8 Minuten
22. Okt 2026
Setup per CLI oder als Stand-alone-Runtime - Infrastructure as Code mit Azure Bicep, Teil 1
Dieser Beitrag erklärt, was Bicep ist, wie das Bicep CLI als zugrunde liegende Engine installiert wird, aus welchen Grundbausteinen ein Template besteht und wie VS Code mit der passenden Erweiterung den Einstieg erleichtert.
5 Minuten

Das könnte Dich auch interessieren

CLAUDE.md richtig aufbauen - Projektanforderungen an Entwickler in Zeiten von KI, Teil 1
Gleicher Prompt, gleiches Projekt, einmal mit leerer CLAUDE.md, einmal mit gepflegter. Die Ergebnisse sind nicht vergleichbar. Wie eine Markdown-Datei zum wichtigsten Interface zwischen Entwickler und KI-Agent wird.
6 Minuten
Vom vagen Ticket zum präzisen Prompt - Projektanforderungen an Entwickler in Zeiten von KI, Teil 2
Ein Jira-Ticket sagt: „Bewertungssystem implementieren.“ Ein Mensch liest und denkt den Kontext mit. Ein Agent nicht. Fünf Prompt-Patterns zeigen, wie der Weg vom Ticket zum funktionierenden Code kürzer wird, wenn man lernt, in Kontext statt in Code zu denken.
8 Minuten
22. Okt 2026
PandaOS: Die Workstation, die ich mir immer gewünscht habe
Monatelang bin ich zwischen KI-Werkzeugen hin- und hergesprungen. Cursor, Claude Code, OpenCode, Aider. Jedes hatte etwas Geniales, doch keines fühlte sich vollständig an. Also habe ich ein eigenes Tool gebaut. PandaOS ist das Ergebnis.
6 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige