Найти в Дзене
Дед Мазай на Котлине

Как код наследует привычки разработчика

На днях, очень оживлённо обсуждали с коллегами один интересный подход к написнию кода. Хочу поделиться с вами некоторыми его деталями и пригласить присоединиться к обсуждению. Представим, что у нас есть некоторая сущность, соответствующая одноимённой таблице в базе данных: @Table(value = "employee")
data class EmployeeEntity(
@Id
val id: Long? = null,
@Column("name")
val name: String,
) Модель, которая соответствует этой сущности, я и мои коллеги-котлинисты привыкли писать примерно так: data class Employee(
val id: Long,
val name: String,
) Та же самая модель, но написанная с применением подхода, который мы обсуждали, будет выглядеть примерно так: data class Employee(
val id: Id<Employee>,
val name: String,
) Отличаются эти две модели только полем id. В первом случае, id - это простой Long. Во втором - это кастомный тип, параметризованный типом самой сущности (такой уроборос). Вот так этот кастомный тип Id может выглядеть: class Id<T : Any> private co

На днях, очень оживлённо обсуждали с коллегами один интересный подход к написнию кода. Хочу поделиться с вами некоторыми его деталями и пригласить присоединиться к обсуждению.

Представим, что у нас есть некоторая сущность, соответствующая одноимённой таблице в базе данных:

@Table(value = "employee")
data class EmployeeEntity(

@Id
val id: Long? = null,

@Column("name")
val name: String,
)

Модель, которая соответствует этой сущности, я и мои коллеги-котлинисты привыкли писать примерно так:

data class Employee(
val id: Long,
val name: String,
)

Та же самая модель, но написанная с применением подхода, который мы обсуждали, будет выглядеть примерно так:

data class Employee(
val id: Id<Employee>,
val name: String,
)

Отличаются эти две модели только полем id. В первом случае, id - это простой Long. Во втором - это кастомный тип, параметризованный типом самой сущности (такой уроборос).

Вот так этот кастомный тип Id может выглядеть:

class Id<T : Any> private constructor(
private var _value: Long?,
) {
// from 100 lines of code to do it work
}

Помимо написания около 100 строк кода, чтобы такой кастомный Id работал, в коде присутствуют ещё от 100 и более строк кода, реализующих всякие фабрики и билдеры, призванные конвертировать модель в сущность и обратно.

Основной вопрос нашего обсуждения был - как лучше (правильнее):
val id: Long
или
val id: Id<Employee>.

Из плюсов первого подхода (val id: Long):

  • это Котлин,
  • это просто и понятно всем,
  • нет лишних простыней кода, не несущих в себе бизнес-логику,
  • меньше кода - меньше когнитивная нагрузка, быстрее делать фичи, меньше возможности ошибиться, меньше писать тестов.

Из минусов:

  • можно "случайно" передать в функцию не тот id. Например, не id работника, а id организации
val employee = getEmployeeById(employeeId = organization.id)
fun getEmployeeById(employeeId: Long): Employee {}
  • кому-нибудь этот подход может показаться слишком простым

Из плюсов второго подхода (val id: Id<Employee>):

  • сложнее ошибиться. Собственно, это и был основной довод оппонентов: нельзя доверять даже себе, все могут ошибиться:
fun getEmployeeById(employeeId: Id<Employee>): Employee {}
  • порция дофамина для тех, кому не хватало сложности в проекте ))

Из минусов:

  • "случайно" ошибиться всё ещё можно
val employee = getEmployeeById(employeeId = Id<Employee>.from(organization.id.getValue)
fun getEmployeeById(employeeId: Id<Employee>): Employee {}
  • это НЕ Котлин. Котлин - язык, созданный для того, чтобы можно было просто делать сложные вещи, а не наоборот.
    Чтобы случайно не перепутать параметры, в Котлине есть возможность передавать параметры не по позиции, а по имени. Пример:
    getEmployeeById(employeeId = employee.id)
    Само название переменной класса Id говорит о том, что эта конструкция могла прийти не из Котлина: private var _value: Long?
    Таким способом приватные переменные называют в языках программирования, в которых пефикс "нижнее подчёркивание" означает, что это переменная с областью видимости private. При этом модификатор private перед названием такой переменной не пишется. Если не ошибаюсь, такими языками могут быть: Python, C, C++, JavaScript, может быть какой-то ещё.
    Судя по необходимости наличия в коде защиты от ошибок в виде кастомного типа
    Id<Employee>, пришла такая конструкция из языка, в котором нет возможности передать параметр по имени (например, из Java).
  • код наследует чью-то привычку. Судя по тому, что этот код пришёл в новый проект на Котлине из какого-то другого языка, не умеющего передавать параметры по имени, это чья-то привычка - разработчик привык писать так (например в Java) и теперь без этой конструкции не чувствует себя комфортно (в безопасности), программируя на Котлине.
  • это лишние 200 строк кода со всем их багажом: ошибки, тесты, когнитивная нагрузка, увеличенное время разработки.
  • это отвлекает внимание от бизнес-логики приложения.

Мы все не раз слышали фразу "со своим уставом в монастырь не приходят". Вот это относится и к языкам программирования. Программируя на Котлине, не нужно писать в стиле Java или тянуть в код хаки и фичи, разработанные для Java. На Котлине нужно писать с помощью средств, предоставляемых Котлином.

На мой взгляд, Котлин - это очень хорошо продуманный и элегантный язык. Тащить в код на Котлине багаж своих привычек из Java - это добровольно отказываться от нового и эффективного в пользу устаревшего и неуклюжего.

Такое вот моё мнение по теме нашего обсуждения. Если у вас есть свои аргументы - пишите здесь или в телеграм, будет интересно их обсудить.