Search
Write a publication
Pull to refresh

Comments 21

Переизобрели шарповский Nullable на гошный манер?

Может быть, сам не силен в шарпах
Библиотека создавалась из потребности решить свои проблемы неопределенности, а также узнать как эту проблему обходят другие
Постарался сделать удобно и в общем виде, может кто еще будет пользоваться

Эту проблему другие не обходят, потому что это не проблема.

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

как и писал выше, хотел узнать как делают другие

Спасибо за наводку!
Будем исследовать и изучать как можно внедрить в свои проекты!

Можно использовать конструкторы
func NewPerson() *Person
и точно знать во что поля установлены по дефолту.
В вашем подходе можно поэкономить память. Тип bool в Go это alias int8. То есть вам не нужно две переменных bool для флагов. Одной int8 хватит на восемь бинарных флагов с применением битовых масок. Так сделано например в стандартном пакете os для масок атрибутов файла https://pkg.go.dev/os#pkg-constants Идея проста
const Defined int8 = 1
const Valid int8 = 2
var Mark int8
Mark += Valid
if Mark&Defined != 0 {//false}
if Mark&Valid != 0 {//true}

Спасибо за комментарий!
Да, это красиво
Но писать конструктор для каждой структуры не всегда хочется
+ при конструкторе nil всегда будет
а как понять, nil был прокинут ранее или по дефолту задан?
Хочется просто работать как с обычными типами, у которых просто внутри заложен этот механизм

Насчет оптимизации памяти - постараюсь доработать в следующих версиях

Вот тут по коду чуть-чуть рекомендаций себе позволю, если вы не против.

const Defined int8 = 1

В таких конструкциях лучше таки беззнаковые типы использовать.

  1. int8 заполненный единицами в бинарном представлении - это -128 (выглядит странно, да?), а uint8 с тем же содержимым - 255.

  2. С uint'ом открывается путь к грязным фокусам со сравнением. Например `mark > 7` вернет true, если есть хоть 1 взведенный флаг кроме первых трех. С int8 сравнение сфейлится, если будет взведен 8-й.

Mark += Valid

Вот тут однозначно Mark = Mark|Valid.

mark := 4 [00000100]
fmt.Println(mark + 5) // 9 [00001001]
fmt.Println(mark|5)   // 5 [00000101]

Вы вместо того, чтобы флаг взвести, суммируете числа. Чревато побочками, если в исходном значении не 0.

type bitmap uint8
const (
  defined bitmap = 1 << iota // 1 [00000001]
  valid                      // 2 [00000010]
  accepted                   // 4 [00000100]
  // дальше само инкрементить поразрядно будет
)

var mark = 5 // [00000101] - взведены 1 и 3 флаги
// Простые сравнения на наличие одного флага.
// В этом кейсе следующие 3 строки семантически идентичны
fmt.Println( mark & defined != 0 ) // true
fmt.Println( mark & defined > 0 ) // true
fmt.Println( mark & defined == defined ) // true
// И эти 3 сравнивают одно и то же
fmt.Println( mark & valid != 0 ) // false
fmt.Println( mark & valid > 0 ) // false
fmt.Println( mark & valid == valid ) // false

// Гораздо интереснее сравнение с несколькими флагами
// Проверяем, что все указанные флаги взведены - `mark & flags == flags`
fmt.Println(mark & (defined|valid) == defined|valid) // true, т.к. ОБА флага взведены
fmt.Println(mark & (defined|accepted) == defined|accepted) // false, т.к. взведен только ОДИН флаг.

Просьба, оформите код в статье с подсветкой синтаксиса.

А почему здесь "пустая строка" в person1.Position? Вы же инициализировали "Junior". Ну и опечатки

Здесь код не скомпилируется даже, переменные position1 и position2 не используются:

type Person struct { 
   Name string       
   Position *string   
}

position1 :=  "Junior"
person1 := Pesron{Position: "Junior"} // ТУТ НЕ СКОМПИЛИРУЕТСЯ
position2 :=  ""
person2 := Pesron{Position: ""}  // И ТУТ ТОЖЕ
person3 := Pesron{}

Ну блин, ну код-то свой запустить можно попробовать разок?

Ну и "библиотеку" посмотрел:

OptionalInt   = OptionalType[*int]
OptionalBool = OptionalType[*bool]

Кхм... bool-значение, занимающее 16 байт в стеке и еще 8 в куче. А может, ну его, этот ваш паттерн Optional (ну либо уже просто возьмите существующие реализации, и не парьте мозг). Ну и да, раздельные поля defined/valid нафиг не нужны (ну либо вы мне сейчас расскажете, зачем инициализировать переменную невалидным значением).

Спасибо за комментарий! Ошибки есть, не спорю

Но Вы поругаться пришли?) Или так, свое мнение оставить? Я Вас не заставляю качать библиотеку и использовать
Вроде как Хабр свободная площадка

Лучше бы предложили свое решение проблемы касаемо запросов в БД - как делать универсальную функцию, которая будет в зависимости от заполненности/незаполненности параметров формировать sql запрос

Или как Вы узнаете, поле было заполнено nil-ом или нет?
Ощущение, что Вы на go пишете просто посредственные вещи без каких-то паттернов, DRY, SOLID и прочее

Лучше бы предложили свое решение проблемы касаемо запросов в БД

sqlc

Или как Вы узнаете, поле было заполнено nil-ом или нет?

Зачем?

Ощущение, что Вы на go пишете просто посредственные вещи без каких-то паттернов, DRY, SOLID и прочее

Ну вот про SOLID: буковка S говорит о том, что в реализации паттерна Optional одно из полей valid/defined - лишнее.

type Optional[T any] struct {
	Value T
	IsSet bool
}

Все, готовый Optional. Всяческие OptionalBool и OptionalInt - не нужны, тем более в том виде, который вы использовали.

OptionalInt = OptionalType[*int]

Вот это вы зачем написали? Зачем тут указатель на int? Из принципа "что-то у меня оперативки многовато" или "а давайте посмотрим, насколько говенный у нас сборщик мусора"?

Просто вопрос: сколько памяти и где занимает ваш OptionalInt?

Вы не углубились в суть проблемы, раз пишете об этом

Я углубился в суть. Суть вашей проблемы в 2 словах: "Go не Python". Это если коротко.

Решение еще проще: не пишите на Go как на Python'е.

Вы рисуете какие-то странные примеры с GetPersonListDTO, на которых демонстрируете билд запроса к БД и сериализацию/десериализацию в JSON.

Сразу возникает вопрос, почему сигнатура метода репозитория выглядит вот так:
func (r *Repository) GetPersonList(ctx context.Context, dto GetPersonListDTO) []Person

Почему не так вот, например:

type PersonRepository interface {
  GetPersonListByName(ctx context.Context, name string) ([]Person, error)
  GetPersonListByNameAndPosition(ctx context.Context, name string, position string) ([]Person, error)
}

Зачем эти приседания с мутными dto'шками? Не, в Java/C#/Python понятно, зачем оно. Там ООП и всякие LINQ/EntityFramework'и/Streams и т.д. В Go всего этого нет и, вероятно, не будет в обозримом будущем. Зачем "городить огород"?

Еще более непонятно, зачем типу, описывающему запрос к БД уметь сериализоваться/десериализоваться в JSON. Тем более криво.

Вот у вас только что OptionalValue[int]{value: 0, defined: true, valid: true}, а вот мы его замаршаллили/анмаршаллили, и оно уже OptionalValue[int]{value:0, defined: false, valid: false}. Ну и зачем городили-то?

Если вы утверждаете, что ваш тип сериализацию умеет, то свою семантику при сериализации/десериализации он терять не должен.

Теперь по поводу defined/valid полей. У вас в каких случаях nil-value defined? Только если вы его сами ручками задали - все верно же? А valid оно когда? Правильно, в том же случае. А теперь объясните мне, в каком случае у вас поле определено, но не валидно, а в каких валидно, но не определено.

Короче, заранее извиняюсь, но решение - дичь, порожденная ООП головного мозга.

Готовые решения - знал бы я о них раньше - не писал бы свою либу

"ну либо вы мне сейчас расскажете, зачем инициализировать переменную невалидным значением" - ощущение, что Вы не пишете продакшн код

отовые решения - знал бы я о них раньше - не писал бы свою либу

И эти люди...

"ну либо вы мне сейчас расскажете, зачем инициализировать переменную невалидным значением" - ощущение, что Вы не пишете продакшн код

Ну т.е. не расскажете...

Вы не углубились в суть проблемы, раз пишете об этом

я правильно понимаю, что данная библиотека решает проблему составления SQL запроса с помощью ORM, когда ORM является антипаттерном?

Sign up to leave a comment.

Articles