2015-02-25 6 views
5

Я пытаюсь упростить процесс проверки ответов на запросы HTTP-запросов в Spray (я использую Slick для доступа к базе данных). В настоящее время я проверяю один запрос, если я должен перейти к следующему запросу или нет (ошибка возврата). В результате получается вложенное сопоставление шаблонов. Каждый случай проверки может возвращать разные ошибки, поэтому я не могу использовать flatMap.Scala - избегать слишком сложного вложенного соответствия шаблону

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

Что такое хороший способ упростить это? Может быть, есть другой подход ..

+2

Ваш образец кода слишком специфичен для того, что вы делаете, старайтесь быть более общим. Но для вашего вопроса, если вы измените методы xxxRowOption, чтобы вернуть «Либо», вы сможете использовать их для понимания. – Maxim

+0

Используйте для понимания. –

+0

Я переработал этот код с пониманием. Благодарю. – piobab

ответ

6

Как правило, вы можете использовать для постижения в цепь вместе много монадических структуры у вас есть здесь (в то числе Try, Option и Either) без вложенности. Например:

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

В вашем стиле, это возможно, иначе выглядело как:

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

Этот случай неверен: 'case Failure => None'. Либо используйте 'case f: Failure => None' или' case Failure (_) => None'. – jrudolph

+0

ты прав - мой плохой. Я не запускал какой-либо из этого кода, я просто пытался придать вкус для понимания по сравнению с вложенными матчами и картами. Не стесняйтесь редактировать ответ, если заметите какие-либо другие небольшие ошибки. –

1

Вы можете определить вспомогательный метод для Eiter Изинга вашего потока управления.

Преимущество этого в том, что у вас будет большой контроль и гибкость в вашем потоке.

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

Я принял разные апробации с пониманием. Я думаю, что это решение хорошо, но что, если мы хотим вернуть более одной ошибки? – piobab

+0

Для сжатия существует проблема тонкого управления ошибками. В этом случае вы можете управлять своими ошибками любым способом. На данный момент ... этот код будет возвращать разные ошибки для разных проблем точно так же, как и исходный код. –

0

Для меня, когда я когда-то не могу избежать вложенной сложности, я бы извлечь из секции коды, которые имеют смысл вместе и превратить его в новый метод и дать ему значимое имя. Это будет документировать код и делать его более читабельным и уменьшать сложность внутри каждого отдельного метода. И обычно, как только я это сделал, я могу видеть поток лучше и мог бы реорганизовать его, чтобы иметь больше смысла (после первого написания тестов, чтобы охватить поведение, которое я хочу).

например. для вашего кода, вы можете сделать что-то вроде этого:

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

} 

Обратите внимание, что это всего лишь иллюстрация, и, вероятно, не будет компилироваться, так как я не имею свою библиотеку, но вы должны быть в состоянии настроить код его компилировать.