kotlin中的异常处理_Kotlin中基于值的类和错误处理

kotlin中的异常处理

Not to be confused with value types of project Valhalla.

不要与Valhalla项目的价值类型混淆。

Value-based classes, as defined in the Java docs, are classes which conform to a set of rules:

Java文档中定义的基于值的类是符合一组规则的类:

- are final and immutable (though may contain references to mutable objects);

-是最终的且不可变的(尽管可能包含对可变对象的引用);

- have implementations of equals, hashCode, and toString which are computed solely from the instance's state and not from its identity or the state of any other object or variable;

-具有equalshashCodetoString实现,这些实现仅根据实例的状态而不是根据其标识或任何其他对象或变量的状态计算;

- make no use of identity-sensitive operations such as reference equality (==) between instances, identity hash code of instances, or synchronization on an instances's intrinsic lock;

-不使用身份敏感的操作,例如实例之间的引用相等( == ),实例的身份哈希码或实例的固有锁上的同步;

- are considered equal solely based on equals(), not based on reference equality (==);

-仅基于equals()而不是基于引用相等( == )被视为相等;

- do not have accessible constructors, but are instead instantiated through factory methods which make no committment as to the identity of returned instances;

-没有可访问的构造函数,而是通过工厂方法实例化的,该方法不承诺返回实例的身份;

- are freely substitutable when equal, meaning that interchanging any two instances x and y that are equal according to equals() in any computation or method invocation should produce no visible change in behavior.

-在相等时可以自由替换 ,这意味着在任何计算或方法调用中互换根据equals()相等的任意两个实例xy不会在行为上产生任何可见的变化。

Regarding the first rule: Immutable classes cannot contain references to mutable objects, because they’re immutable. The rest of the article assumes they don’t.

关于第一个规则:不可变类不能包含对可变对象的引用,因为它们是不可变的。 本文的其余部分假定它们没有。

In Kotlin, that may look like this:

在Kotlin中,可能看起来像这样:

data class Password private constructor(val value: String) {
  companion object {
    fun of(value: String): Password? {
      return if (value.isAGoodPassword()) Password(value) else null
    }
  }
}

There are three advantages of this:

这有三个优点:

  • You are certain that all instances of Password are ‘good passwords’, it became an invariant of your system.

    您可以确定Password所有实例都是“好密码”,它成为系统的不变式

  • Your domain model enforces its own rules, which is more convenient than having the ‘api consumer’ enforce them.

    您的域模型执行自己的规则,这比让“ api使用者”执行它们更方便。
  • You have complete control over the instance your factory returns.

    您可以完全控制工厂返回的实例。

Pretty simple, huh? In fact, too simple.

很简单吧? 其实太简单了。

Image for post
freepik.com freepik.com上

What does returning null here mean to the api consumer? That this’s a bad password? But why?

在这里返回null对api使用者意味着什么? 这是一个错误的密码吗? 但为什么?

Returning null in this case is barely helpful. What do we tell the app user? bad password? better luck next time? What if the api user wants to execute different actions on different failing scenarios? We need to provide more context!

在这种情况下返回null几乎没有帮助。 我们告诉应用程序用户什么? 密码错误? 下次好运吗? 如果api用户想要在不同的失败情况下执行不同的操作怎么办? 我们需要提供更多背景信息!

Which takes us to a pretty controversial subject…

这使我们进入了一个颇具争议的话题……

异常与ADT (Exceptions vs ADTs)

There are mainly two ways in which this problem is tackled:

解决此问题的方法主要有两种:

  • Exceptions:

    例外情况:

data class Password private constructor(val value: String) {
  companion object {
    fun of(value: String): Password {
      return when {
        value.isTooShort() -> throw PasswordTooShortException()
        value.containsNoNumbers() -> throw PasswordContainsNoNumbersException()
        else -> Password(value)
      }
    }
  }
}


class PasswordTooShortException() : Exception("Password is too short!")
class PasswordContainsNoNumbersException() : Exception("Password contains no numbers!")

This looks nice at first, but maintaining it, definitely ain’t.

起初看起来不错,但是保持它肯定不是。

All the try-catching aside, what happens when we decide we need to add seventy-five more constraints on our user’s passwords? We add the exceptions, update our documentations and .. start asking who has been instantiating our class?

除了所有尝试性捕获之外,当我们决定需要在用户密码上增加75个约束时,会发生什么情况? 我们添加了例外,更新了文档,然后开始询问谁在实例化我们的课程?

Some people take this a step further and throw exceptions directly inside the constructors which is even more dangerous, but more on that later.

有些人更进一步,直接在构造函数中抛出异常这更加危险,但稍后会更多。

Overall, we can do better!

总体而言,我们可以做得更好!

Image for post
freepik.com freepik.com上
  • Algebraic Data Types:

    代数数据类型:

data class Password private constructor(val value: String) {
  companion object {
    fun of(value: String): Either<Violation, Password> {
      return when {
        value.isTooShort() -> Violation.PasswordTooShort(MIN_LENGTH).left()
        value.containsNoNumbers() -> Violation.PasswordContainsNoNumbers.left()
        else -> Password(value).right()
      }
    }
  }
	
  sealed class Violation {
    data class PasswordTooShort(val minLength: Int) : Violation()
    object PasswordContainsNoNumbers : Violation()
  }
}

By using sum types (Either), sealed classes and exhaustive when, the compiler is now responsible for maintaining our code and will force us (unless explicitly told not to) to handle all the error scenarios wherever we instantiate this class.

通过使用和类型 ( Either ), 密封类以及when穷举 ,编译器现在负责维护我们的代码,并将迫使我们(除非明确告知不要这样做)在实例化此类的任何地方处理所有错误情况。

Further more, your ‘constructor’ is now an honset function, it returns exactly what its signature says it would, it doesn’t hijack your program’s execution, which is a huge plus for readability and maintainability.

此外,您的“构造函数”现在是honset函数,它完全返回其签名所说明的内容,不会劫持您程序的执行 ,这对于可读性和可维护性而言是一个巨大的优势。

It also allows us to do something like this, which we couldn’t do with exceptions:

它还允许我们做这样的事情,但是我们不能做以下例外:

data class Password private constructor(val value: String) {
  companion object {
    fun of(value: String): Either<List<Violation>, Password> {
      val violations = buildList {
        if (value.isTooShort()) { add(Violation.PasswordTooShort(MIN_LENGTH)) }
        if (value.containsNoNumbers()) { add(Violation.PasswordContainsNoNumbers) }
      }
      return if (violations.isEmpty()) Password(value).right() else violations.left()
    }
  }
	
  sealed class Violation {
    data class PasswordTooShort(val minLength: Int) : Violation()
    object PasswordContainsNoNumbers : Violation()
  }
}

Neat! Isn’t it?

整齐! 是不是

Our code is now predictable and our invariants are established!

现在,我们的代码是可预测的,并且我们的不变式已经确定!

Except, they’re not…

除了,他们不是……

Image for post
By Kaentian Street at shutterstock.com
通过 肯天街 ( Kaentian Street)shutterstock.com

This is valid Kotlin code:

这是有效的Kotlin代码:

val password = Password.of("Some34Really434Hard21Password!").orThrow() //Throw on violation just for demonstration
val haha = password.copy(value = "gotcha")

The copy method of Kotlin’s data classes breaks our invariants, it can bypass all of our rules and it can create ‘illegal’ instances of our class.

Kotlin数据类的copy方法破坏了我们的不变式,可以绕过我们的所有规则,并且可以创建类的“非法”实例。

Even worse for those throwing exceptions inside the constructor! Who would expect this innocent copy method to throw an exception? No one, and you can’t document it for that matter!

对于那些在构造函数内部抛出异常的人来说更糟! 谁会期望这种无辜的copy方法引发异常? 没有人,而且您无法为此记录它!

It is a well-known design flaw in the language, with no apparent way to fix it while keeping backward-compatibility.

它是该语言中的一个众所周知的设计缺陷,在保持向后兼容性的同时,没有明显的解决方法。

So .. dead end? Was this all in vain? That would’ve been funny, but no!

那么..死胡同? 这都是徒劳的吗? 那本来很有趣,但是没有!

This’s where the NoCopy compiler plugin comes into play, by simply annotating our data class with @NoCopy:

这就是NoCopy编译器插件发挥作用的地方,只需使用@NoCopy注释我们的数据类@NoCopy

@NoCopy
data class Password private constructor(val value: String) {
  companion object {
    fun of(value: String): Either<List<Violation>, Password> {
      val violations = buildList {
        if (value.isTooShort()) { add(Violation.PasswordTooShort(MIN_LENGTH)) }
        if (value.containsNoNumbers()) { add(Violation.PasswordContainsNoNumbers) }
      }
      return if (violations.isEmpty()) Password(value).right() else violations.left()
    }
  }
	
  sealed class Violation {
    data class PasswordTooShort(val minLength: Int) : Violation()
    object PasswordContainsNoNumbers : Violation()
  }
}

The copy method of this data class can no longer be referenced or called:

不能再引用或调用此数据类的copy方法:

val password = Password.of("Some34Really434Hard21Password!").orThrow() //Throw on violation just for demonstration
val haha = password.copy(value = "gotcha") //Unresolved reference: copy

With that taken care of, our invariants are now truly established and our code is now truly safe.

有了这些照顾,我们的不变量现在已经真正建立起来,我们的代码现在也真正安全了。

Image for post
By danmir12 at freepik.com
由danmir12在freepik.com

摘要 (Summary)

By using a combination of value-based classes and ADTs, we managed to create robust data models that enforce their own rules and have the compiler help us maintaining them.

通过将基于值的类和ADT结合使用,我们设法创建了健壮的数据模型,这些模型强制执行自己的规则,并让编译器帮助我们维护它们。

We also used the NoCopy compiler plugin to prevent the copy method of the data classes of breaking our invariants.

我们还使用了NoCopy编译器插件来防止数据类的copy方法破坏我们的不变式。

链接 (Links)

For ready implementations of Either and other functional constructs, Arrow-kt is considered the best choice for Kotlin developers.

对于Either和其他功能构造的现成实现,Arrow-kt被认为是Kotlin开发人员的最佳选择。

Follow me on Twitter & Github, Connect on LinkedIn.

Twitter Github 上关注我 ,在 LinkedIn 上关注我

Thank you for reading this article through. Liked it? Clap your 👏 to say “thanks!” and help others find this article.

感谢您通读本文。 喜欢吗? 拍拍你的👏说“谢谢!” 并帮助其他人找到本文。

翻译自: https://medium.com/@dev.ahmedmourad73744/value-based-classes-and-error-handling-in-kotlin-3f14727c0565

kotlin中的异常处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值