wipman wipman - 1 month ago 15
Scala Question

Best way to handle Error on basic Array

val myArray = Array("1", "2")
val error = myArray(5)//throws an ArrayOutOfBoundsException


myArray
has no fixed size, which explains why a call like performed on the above second line might happen.

First, I never really understood the reasons to use
error handling
for expected errors. Am I wrong to consider this practice as bad, resulting from poor coding skills or an inclination towards laziness?

What would be the best way to handle the above case?


  • What I am leaning towards: basic implementation (condition) to prevent accessing the data like depicted;

  • use
    Option
    ;

  • use
    Try
    or
    Either
    ;

  • use a
    try-catch
    block.


Answer

1 Avoid addressing elements through index

Scala offers a rich set of collection operations that are applied to Arrays through ArrayOps implicit conversions. This lets us use combinators like map, flatMap, take, drop, .... on arrays instead of addressing elements by index.

2 Prevent access out of range

An example I've seen often when parsing CSV-like data (in Spark):

case class Record(id:String, name: String, address:String)
val RecordSize = 3
val csvData = // some comma separated data
val records = csvData.map(line => line.split(","))
                     .collect{case arr if (arr.size == RecordSize) => 
                              Record(arr(0), arr(1), arr(2))}

3 Use checks that fit in the current context

If we are using monadic constructs to compose access to some resource, use a fitting way of lift errors to the application flow: e.g. Imagine we are retrieving user preferences from some repository and we want the first one:

Option

def getUserById(id:ID):Option[User]
def getPreferences(user:User) : Option[Array[Preferences]]

val topPreference = for {
      user <- userById(id)
      preferences <- getPreferences(user)
      topPreference <- preferences.lift(0)
   } yield topPreference

(or even better, applying advice #1):

val topPreference = for {
      user <- userById(id)
      preferences <- getPreferences(user)
      topPreference <- preferences.headOption
   } yield topPreference

Try

def getUserById(id:ID): Try[User]
def getPreferences(user:User) : Try[Array[Preferences]]
val topPreference = for {
      user <- userById(id)
      preferences <- getPreferences(user)
      topPreference <- Try(preferences(0))
   } yield topPreference

As general guidance: Use the principle of least power. If possible, use error-free combinators: = array.drop(4).take(1) If all that matters is having an element or not, use Option If we need to preserve the reason why we could not find an element, use Try.

Let the types and context of the program guide you.