Cách đây không lâu, tôi đã bắt đầu sử dụng Scala thay vì Java. Một phần của quá trình "chuyển đổi" giữa các ngôn ngữ đối với tôi là học cách sử dụng Either
s thay vì (đã kiểm tra) Exception
. Tôi đã viết mã theo cách này được một thời gian, nhưng gần đây tôi bắt đầu tự hỏi liệu đó có thực sự là một cách tốt hơn để đi không.
Một lợi thế lớn Either
có hơn Exception
là hiệu suất tốt hơn; một Exception
nhu cầu để xây dựng một dấu vết ngăn xếp và đang bị ném. Theo như tôi hiểu, mặc dù, ném phần Exception
không phải là đòi hỏi, nhưng xây dựng dấu vết ngăn xếp là.
Nhưng sau đó, người ta luôn có thể xây dựng / kế thừa Exception
s scala.util.control.NoStackTrace
, và thậm chí nhiều hơn thế, tôi thấy rất nhiều trường hợp trong đó bên trái của một Either
thực tế là một Exception
(cho phép tăng hiệu suất).
Một lợi thế khác Either
là an toàn trình biên dịch; trình biên dịch Scala sẽ không phàn nàn về các trình xử lý Exception
không (không giống như trình biên dịch của Java). Nhưng nếu tôi không nhầm, quyết định này được đưa ra với cùng lý do đang được thảo luận trong chủ đề này, vì vậy ...
Về mặt cú pháp, tôi cảm thấy như Exception
kiểu là rõ ràng hơn. Kiểm tra các khối mã sau (cả hai đều đạt được cùng chức năng):
Either
Phong cách:
def compute(): Either[String, Int] = {
val aEither: Either[String, String] = if (someCondition) Right("good") else Left("bad")
val bEithers: Iterable[Either[String, Int]] = someSeq.map {
item => if (someCondition(item)) Right(item.toInt) else Left("bad")
}
for {
a <- aEither.right
bs <- reduce(bEithers).right
ignore <- validate(bs).right
} yield compute(a, bs)
}
def reduce[A,B](eithers: Iterable[Either[A,B]]): Either[A, Iterable[B]] = ??? // utility code
def validate(bs: Iterable[Int]): Either[String, Unit] = if (bs.sum > 22) Left("bad") else Right()
def compute(a: String, bs: Iterable[Int]): Int = ???
Exception
Phong cách:
@throws(classOf[ComputationException])
def compute(): Int = {
val a = if (someCondition) "good" else throw new ComputationException("bad")
val bs = someSeq.map {
item => if (someCondition(item)) item.toInt else throw new ComputationException("bad")
}
if (bs.sum > 22) throw new ComputationException("bad")
compute(a, bs)
}
def compute(a: String, bs: Iterable[Int]): Int = ???
Cái sau có vẻ sạch hơn đối với tôi và mã xử lý lỗi (khớp mẫu trên Either
hoặc try-catch
) khá rõ ràng trong cả hai trường hợp.
Vì vậy, câu hỏi của tôi là - tại sao sử dụng Either
hơn (kiểm tra) Exception
?
Cập nhật
Sau khi đọc câu trả lời, tôi nhận ra rằng tôi có thể đã thất bại trong việc trình bày cốt lõi của tình huống khó xử của mình. Mối quan tâm của tôi là không có việc thiếu các try-catch
; một có thể "bắt" một Exception
với Try
, hoặc sử dụng catch
để bọc các ngoại lệ với Left
.
Vấn đề chính của tôi với Either
/ Try
xuất hiện khi tôi viết mã có thể thất bại tại nhiều điểm trên đường đi; trong các kịch bản này, khi gặp phải một thất bại, tôi phải tuyên truyền sự thất bại đó trong toàn bộ mã của mình, do đó làm cho mã trở nên cồng kềnh hơn (như trong các ví dụ đã nói ở trên).
Thực sự có một cách khác để phá mã mà không cần Exception
s bằng cách sử dụng return
(thực tế là một "điều cấm kỵ" khác trong Scala). Mã sẽ vẫn rõ ràng hơn so với Either
cách tiếp cận, và trong khi sạch hơn một chút so với Exception
kiểu, sẽ không sợ Exception
s không bị bắt .
def compute(): Either[String, Int] = {
val a = if (someCondition) "good" else return Left("bad")
val bs: Iterable[Int] = someSeq.map {
item => if (someCondition(item)) item.toInt else return Left("bad")
}
if (bs.sum > 22) return Left("bad")
val c = computeC(bs).rightOrReturn(return _)
Right(computeAll(a, bs, c))
}
def computeC(bs: Iterable[Int]): Either[String, Int] = ???
def computeAll(a: String, bs: Iterable[Int], c: Int): Int = ???
implicit class ConvertEither[L, R](either: Either[L, R]) {
def rightOrReturn(f: (Left[L, R]) => R): R = either match {
case Right(r) => r
case Left(l) => f(Left(l))
}
}
Về cơ bản, các return Left
thay thế throw new Exception
và phương thức ngầm định rightOrReturn
là một phần bổ sung cho việc truyền ngoại lệ tự động lên ngăn xếp.
Try
. Phần về Either
vs Exception
chỉ đơn thuần nói rằng Either
s nên được sử dụng khi trường hợp khác của phương thức là "không đặc biệt". Đầu tiên, đây là một định nghĩa rất, rất mơ hồ. Thứ hai, nó có thực sự đáng bị phạt cú pháp không? Ý tôi là, tôi thực sự không phiền khi sử dụng Either
s nếu nó không dùng cho cú pháp mà họ trình bày.
Either
trông giống như một đơn nguyên đối với tôi. Sử dụng nó khi bạn cần các lợi ích thành phần chức năng mà các đơn vị cung cấp. Hoặc, có thể không .
Either
bản thân nó không phải là một đơn nguyên. Hình chiếu sang bên trái hoặc bên phải là một đơn nguyên, nhưng Either
bản thân nó thì không. Bạn có thể làm cho nó trở thành một đơn nguyên, bằng cách "thiên vị" nó sang bên trái hoặc bên phải, mặc dù. Tuy nhiên, sau đó bạn truyền đạt một ngữ nghĩa nhất định trên cả hai mặt của một Either
. Scala Either
ban đầu không thiên vị, nhưng gần đây đã bị thiên vị, vì vậy mà ngày nay, nó thực sự là một đơn nguyên, nhưng "sự đơn điệu" không phải là một tài sản vốn có Either
mà là kết quả của nó bị thiên vị.