Enforcing type safety of identifiers in Kotlin

栏目: IT技术 · 发布时间: 6年前

内容简介:For codebases with many different entities, most of us will at some point have hit a bug due to the wrong identifier being assigned to a property or passed to a function. Why don’t we use the type system to give all of our identifiers their own type and en

For codebases with many different entities, most of us will at some point have hit a bug due to the wrong identifier being assigned to a property or passed to a function. Why don’t we use the type system to give all of our identifiers their own type and ensure this never happens again.

It is very common for objects to need a form of identifier, frequently the reason being to refer to entities when communicating with a remote API or a database. We can easily implement this by giving our classes an ID property with an appropriate type, such as String , UUID or Long . An issue here is that imagine User and Team both have a String ID, it would be perfectly possible to pass a User ID to a function when we were meant to provide a Team ID instead.

Let’s explore the idea of enforcing type safety by giving our identifiers their own types. :pray:

Enforcing type safety of identifiers in Kotlin
Type safety

General types

One option for our IDs is to use the String type, especially if they are in an unexpected format. Another common type of identifier is a UUID , an implementation of which is provided by the Java standard library. UUID is designed for storing identifiers and is backed by a String in a specific format.

data class Team(val id: UUID, val size: Int)

data class TeamMember(val id: String, val name: String)

Wrapper type

Rather than relying on the general types, we can easily create a dedicated one.

data class Identifier(val rawValue: UUID)

If we require different raw types, we can either create extra identifier types that contain different raw types or use a single one with a generic type parameter. Using a generic version allows us to have properties or function arguments that require a particular type of identifier.

data class Identifier<RawT>(val rawValue: RawT)

Ensuring the correct type

There are some issues that can appear when using these general types internally. Say Team has a UUID ID and Member has a String ID:

  • Every Team ID is a UUID , but not every UUID refers to a valid Team .
  • A Member ID could have the same value as the raw String backing a Team ID, even though they refer to different entities.

Mixing and matching two different identifiers that use the same raw type is perfectly valid and not prevented by the compiler. We can do better than this! :arrow_double_up:

By adding a second generic type parameter to our Identifier we ca limit its use for a particular entity.

data class Identifier<EntityT, RawT>(
    val rawValue: RawT
)

data class Room(val id: Identifier<Room, UUID>)
data class Meeting(val id: Identifier<Meeting, UUID>)

fun bookMeeting(id: Identifier<Meeting, UUID>) {}

// :x: Compile error: Type mismatch.
bookMeeting(room.id)

Due to EntityT not actually being used within Identifier we will likely get a warning about it being unused, which can be ignored using @Suppress("unused") .

Type aliases

When our codebase has a variety of entities, the number of identifiers will grow and we are likely to get fed up of typing out the Identifier signature. We can rely on type aliases to simplify this task. A nice organisation tip is to store these alongside the entity they identify, making them easy to find.

// Team.kt

typealias TeamId = Identifier<Team, UUID>

data class Team(val id: TeamId)

Specifying identifiers in properties and functions is now so much simpler. :tada:

fun Team.inviteMember(id: MemberId) {}

// :x: Compile error: Type mismatch.
team.inviteMember(team.id)

// :white_check_mark: Compiles.
team.inviteMember(member.id)

Wrap up

By using a type that enforces type safety of our entity identifiers, we can make our model code safer to work on and avoid bugs due to an incorrect identifier being used. Our code will also be more readable as when we see an identifier we will know which entity it refers to. There may even be ways to extend this solution to make it even more powerful! :rocket:

What do you think of this approach to managing identifiers? Do you use something similar or something different all together? Please let me know any thoughts or questions you have on Twitter @lordcodes .

If you like what you have read, please don’t hesitate to share the article andsubscribe to my feed if you are interested.

Thanks for reading and happy coding! :pray:

Check out the sample code for this article on GitHub.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

游戏编程权威指南

游戏编程权威指南

Mike McShaffry 麦克沙福瑞、David “Rez” Graham 格雷海姆 / 师蓉、李静、李青翠 / 人民邮电 / 2016-3 / 99.00元

全书分为4个部分共24章。首部分是游戏编程基础,主要介绍了游戏编程的定义、游戏架构等基础知识。 第二部分是让游戏跑起来,主要介绍了初始化和关闭代码、主循环、游戏主题和用户界面等。 第三部分是核心游戏技术,主要介绍了一些*为复杂的代码 示例,如3D编程、游戏音频、物理和AI编程等。 第四部分是综合应用,主要介绍了网络编程、多道程序设计和用C#创建工具等,并利用前面所讲的 知识开发出......一起来看看 《游戏编程权威指南》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具