【问题标题】:Should Kotlin's DAO return Optional or null?Kotlin 的 DAO 应该返回 Optional 还是 null?
【发布时间】:2018-05-11 19:06:32
【问题描述】:

在 Kotlin/JPA 之前,我曾经这样编写我的 DAO 层:

public interface UserDao extends JpaRepository<User,Long> {
    Optional<User> findBySsn(String ssn);
}

在调用方,如果我想通过 SSN 找人或创建用户,我可以这样写:

val user = userDao.findBySsn(value).orElseGet {
    userDao.save(value)
}

效果很好,看起来很流畅。

但由于 Kotlin 引入了 null-safety ,还有另一种惯用的方式(dao 仍在 Java 中):

public interface UserDao extends JpaRepository<User,Long>  {

    Optional<User> findBySsn(String ssn);

    @Query("select u from User u where u.ssn = :ssn")
    @Nullable User findBySsnNullable(@Param("ssn") String ssn)
}

在客户端:

val user = userDao.findBySsnNullable(value)
      .takeIf{ it -> it != null}? : userDao.save(User(value))

这两种方式都很好。但我想知道哪个是首选? Kotlin 在 API 设计中依赖 Java8 的 Optional 是否有好处? Kotlin 项目依赖(或通过)Java8 的 Optional/Stream API(因为 Kotlin 有自己的 API)有什么缺点?

Kotlin 可以编译成 JavaScript(我没研究过)。如果项目依赖Java的Optional/Stream,编译成JS会不会有问题?

----更新----

根据Jetbrains

不可以,通用代码只能依赖于其他通用库。科特林有 不支持将 Java 字节码翻译成 JS。

【问题讨论】:

  • An Optional’s place in Kotlin - 如果不需要,可空类型绝对是更惯用的解决方案
  • 在您的示例中,您甚至不必使用 takeIf 部分。没有也一样。
  • 谢谢@tynn ...

标签: kotlin spring-data spring-data-jpa dao


【解决方案1】:

如果你不需要,我不会使用Optional。它只会增加不必要的开销,因为在 Kotlin 中使用可空类型更具可读性和惯用性。在 Kotlin 中使用 Optional 没有任何优势。

这是另一个讨论:https://discuss.kotlinlang.org/t/java-api-design-for-kotlin-consumption-optional-or-null/2455

【讨论】:

    【解决方案2】:

    我不得不说我不同意@s1m0nw1

    Kotlin 所做的是为我们提供了一种处理错误的好方法。 Kotlin 无法消除这个错误,因为它在 JVM 中是如此固有,并且会破坏与遗留 Java 和编写不佳的 Java 的集成。然而,因为它有一个很好的工具来处理糟糕的设计,并不意味着我们应该接受糟糕的设计并自己去做。

    一些推理:

    • 在从 Java 调用 Kotlin 代码时,Nullable 仍然会出现同样的问题。现在你只是掩盖了潜在的问题,积极地损害了你的客户。 - 这本身就是 IMO 的一个足够充分的理由。

    • Null 还是有歧义的,null 是什么意思?错误?价值缺失?要么? Null 没有明确的含义,即使你赋予它一个含义,你也必须到处记录它并希望人们阅读它。 Value.EMPTY、Value.MISSING、throw new Exception() 都有(有些)明确定义的含义,可以从您的代码中清楚地阅读。这与将布尔值作为二进制枚举值的快捷方式的问题相同,例如:

    val testPassed = runTest()
    
    if (testPassed) // ...
    

    这有一个明确的含义,只要你有变量名。传递它,重构等可以很快地混淆它。用户的意思:

    val testResult = runTest()
    if (testResult == TestResult.PASSED)
    

    清晰易读。因此,根据相同的论点,让您的代码传达您的意图。不要走捷径。我再次看到 Kotlin 处理 null 非常好,因为处理糟糕的代码,我不认为它是产生糟糕代码的借口。

    • Null 在逻辑上是不合逻辑的,本质上它是一个不指向的指针。它基本上是一个无意义的“价值”,并不是真正的价值。这不是什么。

    • 使用空值,您仍然需要为利用各种流功能的 API 做一些奇怪的事情,因此,使用空值时,您要么必须做 s1m0nw1 链接到的辩论中说明的 hacky 事情,要么进行显式转换无论如何,所以无论如何你最终都会同时拥有它们,通过选择 null 完全没有节省任何东西,但最终要同时处理可为 null 和 Optional 的语义。

    • 确保您返回 Optional 是最低限度的工作。我的意思是来一个,我们不是在这里谈论很多额外的代码。不要害怕多写几行来做正确的事情。可读性、灵活性等应该放在短代码之前。仅仅因为有一个新的智能功能可以快速编写并避免错误并不意味着您必须一直使用它。请记住,对于锤子来说,一切看起来都像钉子:)

    不做 null 的问题是在 Java 中缺少正确的“Maybe”类型。更正确的解决方案是函数式语言使用的 Option/Maybe 类型,然而,Java 的 Optional 只是一半的措施。它没有完全捕获真正的 Maybe/Option 类型的语义,这就是为什么您不应该将 Optional 用于参数和字段的原因。

    对于参数,这不是问题,因为您可以轻松地确保重载不会以该参数开头 - 这在 Kotlin 等语言中更加容易。

    对于字段,当前的“解决方案”似乎是定义一个适当的 Maybe/Option 类型,可能特定于您的每种类型,以确保类型擦除和序列化不会妨碍您。您可以使用 NullObject 模式,这似乎是一种稍微丑陋的方式来做同样的事情。或者有讨厌的空值,但将它完全封装在你的类中。到目前为止,我一直在做最后一个,但我不喜欢它:/但实用主义有它的位置;)

    【讨论】:

    • 与其他语言相比,Kotlin 选项的实现很糟糕。我认为他上面的建议更好。
    • @JBarros35 可能最干净的方法是为此目的实现特定的数据结构并为此使用有意义的世界。毕竟“空”或“空”可能意味着任何取决于上下文的东西,但如果你有一个名为“NotSpecified”或“NotFound”的结构,那就更清楚了。问题是我们是否想做所需的工作量,以一种好的方式来实现它? (不,我对此没有一个好的答案)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    • 2010-12-10
    • 1970-01-01
    • 2011-03-14
    • 2016-07-09
    • 1970-01-01
    • 2017-06-12
    相关资源
    最近更新 更多