私には、互いに動的な緊張関係がある3つの異なるニーズがあるように見えます。
- のエクステンダーの便利さ
RuntimeException
; つまり、の子孫を作成するために記述される最小限のコードRuntimeException
- クライアントの知覚された使いやすさ。つまり、コールサイトで記述される最小限のコード
null
恐ろしいJavaをコードに漏らさないようにするというクライアントの好み
3番を気にしないのであれば、この答え(これと同じ)はかなり簡潔に思えます。
ただし、1と2にできるだけ近づけようとしているときに、1つの値が3である場合、以下のソリューションは、Javanull
リークをScalaAPIに効果的にカプセル化します。
class MyRuntimeException (
val optionMessage: Option[String],
val optionCause: Option[Throwable],
val isEnableSuppression: Boolean,
val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
) {
def this() =
this(None, None, false, false)
def this(message: String) =
this(Some(message), None, false, false)
def this(cause: Throwable) =
this(None, Some(cause), false, false)
def this(message: String, cause: Throwable) =
this(Some(message), Some(cause), false, false)
}
new
また、実際に使用されている場所を使用する必要をなくしたい場合はMyRuntimeException
、このコンパニオンオブジェクトを追加します(これにより、すべての適用呼び出しが既存の「マスター」クラスコンストラクターに転送されます)。
object MyRuntimeException {
def apply: MyRuntimeException =
MyRuntimeException()
def apply(message: String): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message))
def apply(cause: Throwable): MyRuntimeException =
MyRuntimeException(optionCause = Some(cause))
def apply(message: String, cause: Throwable): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None,
optionCause: Option[Throwable] = None,
isEnableSuppression: Boolean = false,
isWritableStackTrace: Boolean = false
): MyRuntimeException =
new MyRuntimeException(
optionMessage,
optionCause,
isEnableSuppression,
isWritableStackTrace
)
}
個人的には、new
将来のリファクタリングを容易にするために、できるだけ多くのコードで演算子の使用を実際に抑制したいと思っています。上記のリファクタリングがファクトリパターンに強く影響する場合は特に役立ちます。私の最終的な結果は、より冗長ですが、クライアントが使用するには非常に良いはずです。
object MyRuntimeException {
def apply: MyRuntimeException =
MyRuntimeException()
def apply(message: String): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message))
def apply(cause: Throwable): MyRuntimeException =
MyRuntimeException(optionCause = Some(cause))
def apply(message: String, cause: Throwable): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None,
optionCause: Option[Throwable] = None,
isEnableSuppression: Boolean = false,
isWritableStackTrace: Boolean = false
): MyRuntimeException =
new MyRuntimeException(
optionMessage,
optionCause,
isEnableSuppression,
isWritableStackTrace
)
}
class MyRuntimeException private[MyRuntimeException] (
val optionMessage: Option[String],
val optionCause: Option[Throwable],
val isEnableSuppression: Boolean,
val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
)
より洗練されたRuntimeExceptionパターンの調査:
RuntimeException
パッケージまたはAPIに特化したエコシステムを作成したいというのは、元の質問からのほんの小さな飛躍です。RuntimeException
アイデアは、特定の子孫例外の新しいエコシステムを作成できる「ルート」を定義することです。catch
私にとっては、特定の種類のエラーを使用し、match
悪用しやすくすることが重要でした。
たとえばvalidate
、ケースクラスの作成を許可する前に一連の条件を検証するメソッドが定義されています。失敗する各条件はRuntimeException
インスタンスを生成します。そして、RuntimeInstance
sのリストがメソッドによって返されます。これにより、クライアントは応答をどのように処理するかを決定できます。throw
例外を保持しているリスト、特定の何かについてリストをスキャンするthrow
か、非常に高価なJVMthrow
コマンドを使用せずにコールチェーン全体をプッシュします。
RuntimeException
この特定の問題空間には、1つの抽象(FailedPrecondition
)と2つの具象(FailedPreconditionMustBeNonEmptyList
および)の3つの異なる子孫がありFailedPreconditionsException
ます。
最初のFailedPrecondition
、は、の直接の子孫でありRuntimeException
、に非常に似てMyRuntimeException
おり、抽象的です(直接のインスタンス化を防ぐため)。インスタンス化ファクトリとして機能する(オペレータを抑制する) FailedPrecondition
「コンパニオンオブジェクト特性」があります。FailedPreconditionObject
new
trait FailedPreconditionObject[F <: FailedPrecondition] {
def apply: F =
apply()
def apply(message: String): F =
apply(optionMessage = Some(message))
def apply(cause: Throwable): F =
apply(optionCause = Some(cause))
def apply(message: String, cause: Throwable): F =
apply(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): F
}
abstract class FailedPrecondition (
val optionMessage: Option[String],
val optionCause: Option[Throwable],
val isEnableSuppression: Boolean,
val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
)
2番目のFailedPreconditionMustBeNonEmptyList
、は、の間接的なRuntimeException
子孫であり、直接的な具体的な実装ですFailedPrecondition
。コンパニオンオブジェクトとクラスの両方を定義します。コンパニオンオブジェクトは、トレイトを拡張しますFailedPreconditionObject
。そして、クラスは単に抽象クラスを拡張し、それ以上の拡張を防ぐためFailedPrecondition
にそれをマークします。final
object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): FailedPreconditionMustBeNonEmptyList =
new FailedPreconditionMustBeNonEmptyList(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
}
final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
optionMessage: Option[String]
, optionCause: Option[Throwable]
, isEnableSuppression: Boolean
, isWritableStackTrace: Boolean
) extends
FailedPrecondition(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
3番目の、は、sのaをラップし、例外メッセージの発行を動的に管理するFailedPreconditionsException
直接の子孫です。RuntimeException
List
FailedPrecondition
object FailedPreconditionsException {
def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
FailedPreconditionsException(List(failedPrecondition))
def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
tryApply(failedPreconditions).get
def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
tryApply(List(failedPrecondition))
def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
if (failedPreconditions.nonEmpty)
Success(new FailedPreconditionsException(failedPreconditions))
else
Failure(FailedPreconditionMustBeNonEmptyList())
private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
if (failedPreconditions.size > 1)
s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
else
s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
val failedPreconditions: List[FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))
そして、それらすべてを全体としてまとめて整理し、両方FailedPrecondition
とFailedPreconditionMustBeNonEmptyList
オブジェクト内に配置しますFailedPreconditionsException
。そして、これが最終結果のようになります。
object FailedPreconditionsException {
trait FailedPreconditionObject[F <: FailedPrecondition] {
def apply: F =
apply()
def apply(message: String): F =
apply(optionMessage = Some(message))
def apply(cause: Throwable): F =
apply(optionCause = Some(cause))
def apply(message: String, cause: Throwable): F =
apply(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): F
}
abstract class FailedPrecondition (
val optionMessage: Option[String]
, val optionCause: Option[Throwable]
, val isEnableSuppression: Boolean
, val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
)
object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): FailedPreconditionMustBeNonEmptyList =
new FailedPreconditionMustBeNonEmptyList(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
}
final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
optionMessage: Option[String]
, optionCause: Option[Throwable]
, isEnableSuppression: Boolean
, isWritableStackTrace: Boolean
) extends
FailedPrecondition(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
FailedPreconditionsException(List(failedPrecondition))
def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
tryApply(failedPreconditions).get
def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
tryApply(List(failedPrecondition))
def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
if (failedPreconditions.nonEmpty)
Success(new FailedPreconditionsException(failedPreconditions))
else
Failure(FailedPreconditionMustBeNonEmptyList())
private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
if (failedPreconditions.size > 1)
s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
else
s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
val failedPreconditions: List[FailedPreconditionsException.FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))
そして、これは、クライアントが上記のコードを使用して、次のような独自の例外派生を作成する場合にどのように見えるかを示していますFailedPreconditionMustBeNonEmptyString
。
object FailedPreconditionMustBeNonEmptyString extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyString] {
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): FailedPreconditionMustBeNonEmptyString =
new FailedPreconditionMustBeNonEmptyString(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
}
final class FailedPreconditionMustBeNonEmptyString private[FailedPreconditionMustBeNonEmptyString] (
optionMessage: Option[String]
, optionCause: Option[Throwable]
, isEnableSuppression: Boolean
, isWritableStackTrace: Boolean
) extends
FailedPrecondition(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
そして、この例外の使用は次のようになります。
throw FailedPreconditionMustBeNonEmptyString()
私は元の質問に答えるだけでなく、Scalaで具体的かつ包括的であることに近いものを見つけるのが非常に難しいことを発見しましたRuntimeException
。具体的には、Javaにいるときにとても快適になったより一般的な「例外エコシステム」に拡張します。
ソリューションセットに関するフィードバック(「うわー!それは私には冗長すぎる」のバリエーションを除く)を聞きたいです。そして、このパターンのクライアントのために生成した値や簡潔さを失うことなく、冗長性を減らすための追加の最適化や方法があればいいのですが。