Larry Garfield
07/26/2024, 4:11 PMupdatedDto.registeredAgent?.let { ra ->
it.agentType = updatedEntity.agentType
it.agentName = updatedEntity.agentName
// .. A bunch more lines like this
}
That ra
is unused. Which, yes, true. But what I don’t understand is how the code even works. If ra
is defined, doesn’t that replace it
as the variable? Why is it
even valid at that point?Larry Garfield
07/26/2024, 4:11 PMChris Lee
07/26/2024, 4:18 PMit
is from some higher scope, not evident in that code snippet. Its questionable naming - that higher-level it
should be called something meaningful to avoid confusion with inner scopes.
Instead of ?.let
with an unused it
it may be cleaner to use ?.run
Larry Garfield
07/26/2024, 4:22 PMLarry Garfield
07/26/2024, 4:23 PMChris Lee
07/26/2024, 4:29 PMit
, run does not). I generally use let
in a similar way to .map
on a collection, to take some object and transform it. For your code snippet, where it appears the goal is to execute a block of code if there is a non-null registered agent, run
is cleaner (it doesn’t leave you with the unused object reference). If you were using the object reference also
would be an alternative (you can still use that, with _
as the object reference name). Note that run
and let
return the value of the lambda - also
return the context object (updateDto.registeredAgent
in your case).Larry Garfield
07/26/2024, 4:35 PMlet
to run
would work. In the general case, I think this all needs to get converted to an extension function anyway.Chris Lee
07/26/2024, 4:36 PMLarry Garfield
07/26/2024, 4:40 PMfun updateById(id, updatedDto) {
val updatedEntity = updatedDto.toEntity()
return repo.findById(id).map {
// A whole bunch of lines with bad null handling to
// map stuff from updatedEntity to it.
updatedDto.subObjA?.let { a ->
it.field1 = updatedEntity.field1
// ...
}
// The same block repeated for a few other fields.
return@map repo.save(it).toDto()
}
}
Which is… horrifying, to be clear. For many reasons, I’m sure.Klitos Kyriacou
07/26/2024, 4:42 PMif (updatedDto.subObjA != null) {
it.field1 = updatedEntity.field1
// ...
}
It makes it clear that the block is only executed if that object is not null, and that we are not actually using that object inside the block.
Also, that return@map
at the end is a smell.Chris Lee
07/26/2024, 4:44 PMfun updateById(id, updatedDto) {
val updatedEntity = updatedDto.toEntity()
val currentEntity = repo.findById(id) ?: return // or perhaps some form of error, unclear how the existing code works for "not found"
if(updatedDto.subObjA != null) {
currentEntity.field1 = updatedEntity.field1
// ...
}
return repo.save(currentEntity).toDto()
}
Larry Garfield
07/26/2024, 4:52 PMfun CurrentEntityType.updateFrom(updatedEntity): CurrentEntityType {
updatedEntity.fieldA?.let { fieldA = it }
// ...
}
(I did that elsewhere in this app and it seemed to work pretty well.)
And then something similar for the inner blocks.Chris Lee
07/26/2024, 4:57 PMfun updateById(id, updatedDto) {
val updatedEntity = updatedDto.toEntity()
val currentEntity = repo.findById(id).single() // or perhaps some form of error, unclear how the existing code works for "not found"
currentEntity.updateFrom(updatedEntity)
return repo.save(currentEntity).toDto()
}
fun CurrentEntityType.updateFrom(updatedEntity): CurrentEntityType {
fieldA = updatedEntity.fieldA // this overwrites in all cases, including nulls
updatedEntity.fieldA?.let { fieldA = it } // this only overwrites when there are incoming non-null values
// ...
}
Larry Garfield
07/26/2024, 4:58 PMLarry Garfield
07/26/2024, 4:59 PMChris Lee
07/26/2024, 5:00 PM(I’m finding I really like extension functions for “data translation” cases.)Yes, that’s a great use case for them - you can add them externally to the objects so they are in-context of whatever module you are working on without the main object knowing about all that stuff.
CLOVIS
07/29/2024, 8:03 AMmap
is mutating the contents of what it is mapping. I don't like this, map
should be pure transformation, where the source of the map is not changed.
• I guess they used map
because they want to return the result of the DTO mapping at the end, but there's a lot of other stuff going to and it obscures the save
.
I really don't like that objects are mutable. If I had the option, I would make them all read-only. If I couldn't do anything about it, I'd write:
fun updateById(id, updatedDto) {
val updatedEntity = updatedDto.toEntity()
return repo.findById(id)
.onEach {
// …
if (updatedDto.subObjA != null) {
it.field1 = updatedEntity.field1
}
// …
}
.onEach { repo.save(it) }
.map { it.toDto() }
}
Since mutability is used, this version changes the order of operations. You may want to insert asSequence
or asFlow
at the start of the pipeline to restore the previous order of operations.
Another thing that I find strange here, is that it searches by ID but the code is written using collection functions, meaning it expects multiple elements to be returned. I think this would need clarifying.CLOVIS
07/29/2024, 8:03 AMLarry Garfield
07/29/2024, 5:29 PMCAB
07/30/2024, 4:50 PMfun updateById(id, updatedDto) {
val updatedEntity = updatedDto.toEntity()
return repo.run { // repo -> this
findById(id).apply { // DB instance -> this
// the object from DB is represented here by “this”
updatedDto.subObjA?.let { // subObjA -> it and not used
field1 = updatedEntity.field1
// ...
}
}.save(it) // save updated DB instance
.toDto() // convert to DTO to return
} // returns updated DTO
}
Larry Garfield
07/31/2024, 2:37 PMChris Lee
07/31/2024, 2:38 PM