2015-02-25 11 views
5

Próbuję uprościć proces sprawdzania poprawności w celu obsługi odpowiedzi na żądania HTTP w Sprayu (używam Slicka do dostępu do bazy danych). Obecnie sprawdzam w jednym zapytaniu, czy powinienem przejść dalej do następującego zapytania, czy nie (błąd powrotu). Kończy się to z dopasowaniem do zagnieżdżonych wzorców. Każdy przypadek sprawdzania poprawności może zwrócić inny błąd, więc nie mogę użyć żadnej płaskiej mapy.Scala - unikaj zbyt skomplikowanych wzorców zagnieżdżonych pasujących do

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 => 
     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) => { 
       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) 
       } 
       } 
       case None => Left(IncorrectLoginOrPasswordError) 
      } 
      } 
      case None => Left(DeviceNotExistError) 
     } 
     } 
    } catch { 
     case ex: SQLException => 
     Left(DatabaseError) 
    } 
    } 
} 

Jaki jest dobry sposób na uproszczenie tego? Może jest inne podejście ..

+2

Twój przykładowy kod jest zbyt szczegółowy w stosunku do tego, co robisz, staraj się być bardziej ogólny. Ale na twoje pytanie, jeśli zmienisz swoje metody xxxRowOption, aby zwrócić 'Albo" będziesz w stanie użyć do zrozumienia. – Maxim

+0

Użyj a, aby zrozumieć. –

+0

Przebudowałem ten kod ze zrozumieniem. Dzięki. – piobab

Odpowiedz

6

Generalnie można użyć For-zrozumieniem do łańcucha razem wiele z monadycznych struktur masz tutaj (włączając Try, Option i Either) bez zagnieżdżania. Na przykład:

for { 
    val1 <- Try("123".toInt) 
} yield for { 
    val2 <- Some(val1).map(_ * 2) 
    val3 = Some(val2 - 55) 
    val4 <- val3 
} yield val4 * 2 

W swoim stylu, może to inaczej wyglądało tak:

Try("123".toInt) match { 
    case Success(val1) => { 
     val val2 = Some(val1).map(_ * 2) 
     val2 match { 
      case Some(val2value) => { 
       val val3 = Some(val2value - 55) 
       val3 match { 
        case Some(val4) => Some(val4) 
        case None => None 
       } 
      } 
      case None => None 
     } 
    case f:Failure => None 
    } 
} 
+0

Przypadek jest nieprawidłowy: 'case Failure => None'. Albo użyj "case f: Failure => None' lub' case Failure (_) => None'. – jrudolph

+0

masz rację - moje złe. Nie uruchomiłem żadnego z tego kodu, starałem się tylko nadać posmak zawiłości w porównaniu do zagnieżdżonych dopasowań i map. Możesz edytować odpowiedź, jeśli zauważysz jakieś inne drobne błędy. –

1

Można zdefiniować metody pomocnika dla Eiter Isinga przepływ sterowania.

Zaletą takiego rozwiązania jest duża kontrola i elastyczność w przepływie.

def eitherMe[ I, T ](eitherIn: Either[ Error, Option[ I ] ], 
         err:() => Error, 
         block: (I) => Either[ Error, Option[ T ] ] 
        ): Either[ Error, Option[ T ] ] = { 
    eitherIn match { 
    case Right(oi) => oi match { 
     case Some(i) => block(i) 
     case None => Left(err()) 
    } 
    case Left(e) => Left(e) 
    } 

} 

def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = { 
    try { 
    db withSession { implicit session => 
     val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption 

     val locationRowEither = eitherMe(
     Right(deviceRowOption), 
     () => { DeviceNotExistError }, 
     deviceRow => { 
      val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption 
      Right(locationRowOption) 
     } 
    ) 

     val programRowEither = eitherMe(
     locationRowEither, 
     () => { IncorrectLoginOrPasswordError }, 
     locationRow => { 
      val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption 
      Right(programRowOption) 
     } 
    ) 

     val locationResponseEither = eitherMe(
     programRowEither, 
     () => { ProgramNotExistError }, 
     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) 
     } 
    ) 

     locationResponseEither 

    } 
    } catch { 
    case ex: SQLException => 
     Left(DatabaseError) 
    } 
} 
+0

Wziąłem różne podejście z pojęć dla zrozumienia. Myślę, że to rozwiązanie jest dobre, ale co jeśli chcemy zwrócić więcej niż jeden błąd? – piobab

+0

W przypadku kompresji problem polega na dokładnym zarządzaniu błędami. W takim przypadku możesz zarządzać swoimi błędami w dowolny sposób. Na razie ... ten kod zwróci różne błędy dla różnych problemów dokładnie tak samo jak oryginalny kod. –

0

Dla mnie, kiedy czasami nie można uniknąć zagnieżdżony złożoność, chciałbym się wyodrębnić fragment kodu, który ma sensu razem i uczynienia go do nowej metody i nadać mu nazwę opisową. Pozwoli to zarówno udokumentować kod i uczynić go bardziej czytelnym, jak i zmniejszyć złożoność każdej indywidualnej metody. I zwykle, gdy już to zrobię, jestem w stanie lepiej widzieć przepływ i być w stanie go zmodyfi- kować, aby było bardziej sensowne (po pierwszym napisaniu testów, aby objąć zachowanie, które chcę).

np. w kodzie, można zrobić coś takiego:

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) 
    } 
    } 

} 

pamiętać, że jest tylko ilustracją i prawdopodobnie nie będzie kompilować, bo nie masz lib, ale powinieneś być w stanie dostosować kod do kompilacji.

Powiązane problemy