For me, when I someday cannot escape complex nesting, I would extract a piece of code that makes sense together and turns it into a new method and gives it a meaningful name. This will document the code and make it more readable and reduce complexity within each individual method. And usually, as soon as I do this, I can see the stream better and could reorganize it to make more sense (after writing the tests the first time to cover the behavior I want).
eg. for your code, you can do something like this:
class LocationDao { val db = DbProvider.db // Database tables val devices = Devices.devices val locations = Locations.locations val programs = Programs.programs val accessTokens = AccessTokens.accessTokens def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = { try { db withSession { implicit session => checkDeviceRowOption(deviceSerialNumber, login, password) } } catch { case ex: SQLException => Left(DatabaseError) } } def checkDeviceRowOption(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = { val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption deviceRowOption match { case Some(deviceRow) => { val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption locationRowOption match { case Some(locationRow) => { checkProgramRowOption(locationRow) } case None => Left(IncorrectLoginOrPasswordError) } } case None => Left(DeviceNotExistError) } } def checkProgramRowOption(locationRow: LocationRowType): Either[Error, LocationResponse] = { val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption programRowOption match { case Some(programRow) => { val program = Program(programRow.name, programRow.logo, programRow.moneyLevel, programRow.pointsForLevel, programRow.description, programRow.rules, programRow.dailyCustomerScansLimit) val locationData = LocationData(program) val locationResponse = LocationResponse("access_token", System.currentTimeMillis(), locationData) Right(locationResponse) } case None => Left(ProgramNotExistError) } } }
Please note that this is just an illustration and probably will not compile because I do not have your library, but you should be able to configure the code to compile it.
source share