fun createShipment(command: CreateShipmentCommand, notify: Boolean): Shipment {
if (command.countryCode !in setOf("NO", "SE", "DK", "FI")) {
throw ValidationException("Unsupported country ${command.countryCode}")
}
val first = command.recipientFirstName.trim().replaceFirstChar { it.uppercase() }
val last = command.recipientLastName.trim().uppercase()
val entity = ShipmentEntity()
entity.id = UUID.randomUUID()
entity.recipientName = "$first $last"
entity.addressLine = command.addressLine
entity.postalCode = command.postalCode.replace(" ", "")
entity.countryCode = command.countryCode
entity.status = "CREATED"
entity.createdAt = Instant.now()
for (p in command.parcels) {
val pe = ParcelEntity()
pe.shipmentId = entity.id
pe.weightGrams = (p.weightKg * 1000).toInt()
entity.parcels.add(pe)
}
val saved = shipmentRepository.save(entity)
val payload = mapOf(
"shipmentId" to saved.id.toString(),
"status" to saved.status,
"ts" to saved.createdAt.toEpochMilli(),
)
kafka.send("shipment-events", saved.id.toString(), json.writeValueAsString(payload))
if (notify) {
val token = tokens.findByCustomerId(command.customerId) ?: return saved.toDomain()
push.send(PushMessage(token, "Forsendelse registrert", "Pakken din er på vei"))
}
return saved.toDomain()
}
Åpning
Samme metode
fun createShipment(command: CreateShipmentCommand): Shipment {
validateShipment(command)
val shipment = saveShipment(command)
publishShipmentCreated(shipment)
notifyCustomer(shipment)
return shipment
}
Rendyrket kode
I AI-ens tidsalder
Fem synder jeg ser ofte – og spørsmålene som avslører dem
Åpning
Hva er god kode?
Kommuniserer forretningsreglene, og abstraherer bort overflødige detaljer
Hvorfor bry seg
«AI skriver jo koden min»
1
AI følger eksisterende kodestil
2
AI skriver kode ved addisjon, fremfor opprydding
3
Vi trenger fortsatt å kunne inspisere systemet
Fem faresignaler
Tenkt kundereise: Postforsendelse hjemmefra
1
Kunden registrerer forsendelse
2
Budet henter og skanner
3
Betalingsvarsel sendes
4
Kunden betaler
fun createShipment(command: CreateShipmentCommand, notify: Boolean): Shipment {
if (command.countryCode !in setOf("NO", "SE", "DK", "FI")) {
throw ValidationException("Unsupported country ${command.countryCode}")
}
val first = command.recipientFirstName.trim().replaceFirstChar { it.uppercase() }
val last = command.recipientLastName.trim().uppercase()
val entity = ShipmentEntity()
entity.id = UUID.randomUUID()
entity.recipientName = "$first $last"
entity.addressLine = command.addressLine
entity.postalCode = command.postalCode.replace(" ", "")
entity.countryCode = command.countryCode
entity.status = "CREATED"
entity.createdAt = Instant.now()
for (p in command.parcels) {
val pe = ParcelEntity()
pe.shipmentId = entity.id
pe.weightGrams = (p.weightKg * 1000).toInt()
entity.parcels.add(pe)
}
val saved = shipmentRepository.save(entity)
val payload = mapOf(
"shipmentId" to saved.id.toString(),
"status" to saved.status,
"ts" to saved.createdAt.toEpochMilli(),
)
kafka.send("shipment-events", saved.id.toString(), json.writeValueAsString(payload))
if (notify) {
val token = tokens.findByCustomerId(command.customerId) ?: return saved.toDomain()
push.send(PushMessage(token, "Forsendelse registrert", "Pakken din er på vei"))
}
return saved.toDomain()
}
Faresignal 1
Flere abstraksjonsnivåer samtidig
Forretningsregel
Strengmanipulasjon
Databaseformat
Integrasjoner
Faresignal 1
Flere abstraksjonsnivåer samtidig
Kan en annen utvikler forstå omtrent hva denne metoden gjør, uten å lese alle detaljene?
Faresignal 2
Flagg skjuler ulike operasjoner
createShipment(command, true)
Faresignal 2
Flagg skjuler ulike operasjoner
val shipment = createShipment(command)
notifyCustomer(shipment)
Beskriver parameteren data – eller velger den hvilken operasjon metoden skal utføre?
Faresignal 2
To metoder, nesten like
fun registerPrivateParcels(shipment: Shipment) {
for (parcel in shipment.parcels) {
require(parcel.weightKg <= 35.0) { "Too heavy" }
val label = Label(parcel.id, Barcode.CODE_128)
label.recipient = shipment.recipient.name
label.address = shipment.address.oneLine()
courierClient.send(label)
val price = BASE_FEE + parcel.weightKg * KG_RATE
ledger.add(shipment.id, parcel.id, price)
}
val total = ledger.totalFor(shipment.id)
push.send(shipment.customerId, "Betal $total kr")
}
fun registerBusinessParcels(shipment: Shipment) {
for (parcel in shipment.parcels) {
require(parcel.weightKg <= 35.0) { "Too heavy" }
val label = Label(parcel.id, Barcode.CODE_128)
label.recipient = shipment.recipient.name
label.address = shipment.address.oneLine()
label.reference = shipment.poNumber
courierClient.send(label)
val price = BASE_FEE + parcel.weightKg * KG_RATE
ledger.add(shipment.id, parcel.id, price)
}
val total = ledger.totalFor(shipment.id)
invoicing.createInvoice(shipment.customerId, total)
}
Faresignal 2
«Generalisert» med et flagg
fun registerParcels(shipment: Shipment, isBusiness: Boolean) {
for (parcel in shipment.parcels) {
require(parcel.weightKg <= 35.0) { "Too heavy" }
val label = Label(parcel.id, Barcode.CODE_128)
label.recipient = shipment.recipient.name
label.address = shipment.address.oneLine()
if (isBusiness) {
label.reference = shipment.poNumber
}
courierClient.send(label)
val price = BASE_FEE + parcel.weightKg * KG_RATE
ledger.add(shipment.id, parcel.id, price)
}
val total = ledger.totalFor(shipment.id)
if (isBusiness) {
invoicing.createInvoice(shipment.customerId, total)
} else {
push.send(shipment.customerId, "Betal $total kr")
}
}
Faresignal 2
Trekk ut detaljene, behold metodene
fun registerPrivateParcels(shipment: Shipment) {
validateWeights(shipment.parcels)
val labels = createLabels(shipment)
sendToCourier(labels)
val total = priceParcels(shipment)
sendPaymentRequest(shipment.customerId, total)
}
fun registerBusinessParcels(shipment: Shipment) {
validateWeights(shipment.parcels)
val labels = createLabels(shipment).withReference(shipment.poNumber)
sendToCourier(labels)
val total = priceParcels(shipment)
invoicing.createInvoice(shipment.customerId, total)
}
Faresignal 2
Foretrekk litt duplisering fremfor en dårlig abstraksjon.
Faresignal 3
Skjult mutasjon
fun createShipment(command: CreateShipmentCommand): Shipment {
val shipment = buildShipment(command)
// lag en kopi isteden!
applyPricing(shipment) // Setter shipment.price og shipment.currency
notifyCustomer(shipment)
return shipment
}
Faresignal 3
Skjult mutasjon
fun createShipment(command: CreateShipmentCommand): Shipment {
val shipment = buildShipment(command)
val pricedShipment = shipment.withPrice(calculatePrice(shipment))
notifyCustomer(pricedShipment)
return pricedShipment
}
Faresignal 4
Kommentarer som oversetter koden
// Create the parcel labels
val labels = mutableListOf<Label>()
for (parcel in shipment.parcels) {
labels.add(Label(parcel.id, BarcodeFormat.CODE_128))
}
// Send the labels to the courier
for (label in labels) {
courierClient.send(label)
}
Faresignal 4
Kommentarer som oversetter koden
val labels = createParcelLabels(shipment)
sendLabelsToCourier(labels)
Faresignal 5
Parameterlisten er egentlig en datastruktur
createShipment(
recipientFirstName,
recipientLastName,
addressLine,
postalCode,
city,
countryCode,
)
Faresignal 5
Parameterlisten er egentlig en datastruktur
createShipment(recipient)
Er dette separate argumenter – eller data som hører sammen og mangler et navn?
AI og best practice
Hvordan få AI-en til å følge reglene?
Pre-commit hooks
Formatering og linting før hver commit
Statisk analyse fanger de vanligste syndene
Skills og AGENTS.md
Foretrekk immutable og funksjonelle mønstre
Små metodekropper
AI-en gjør oversettelsen for deg
CI
Samme sjekker på hver PR
Rødt bygg stopper koden før review
Avslutning
Tre spørsmål til neste code review
1
Kan jeg forstå forretningsflyten uten å lese detaljene?
2
Gjør navnene og API-et de mulige handlingene tydelige?
3
Kan data endres på steder jeg ikke forventer?
fun createShipment(command: CreateShipmentCommand, notify: Boolean): Shipment {
if (command.countryCode !in setOf("NO", "SE", "DK", "FI")) {
throw ValidationException("Unsupported country ${command.countryCode}")
}
val first = command.recipientFirstName.trim().replaceFirstChar { it.uppercase() }
val last = command.recipientLastName.trim().uppercase()
val entity = ShipmentEntity()
entity.id = UUID.randomUUID()
entity.recipientName = "$first $last"
entity.addressLine = command.addressLine
entity.postalCode = command.postalCode.replace(" ", "")
entity.countryCode = command.countryCode
entity.status = "CREATED"
entity.createdAt = Instant.now()
for (p in command.parcels) {
val pe = ParcelEntity()
pe.shipmentId = entity.id
pe.weightGrams = (p.weightKg * 1000).toInt()
entity.parcels.add(pe)
}
val saved = shipmentRepository.save(entity)
val payload = mapOf(
"shipmentId" to saved.id.toString(),
"status" to saved.status,
"ts" to saved.createdAt.toEpochMilli(),
)
kafka.send("shipment-events", saved.id.toString(), json.writeValueAsString(payload))
if (notify) {
val token = tokens.findByCustomerId(command.customerId) ?: return saved.toDomain()
push.send(PushMessage(token, "Forsendelse registrert", "Pakken din er på vei"))
}
return saved.toDomain()
}
fun createShipment(command: CreateShipmentCommand): Shipment {
validateShipment(command)
val shipment = saveShipment(command)
publishShipmentCreated(shipment)
notifyCustomer(shipment)
return shipment
}