Go Level Up
268 subscribers
3 photos
5 links
Go Hard or Go Home

Контакт для связи со мной: @sQUARyss
Download Telegram
Что выведет код?

func main() {
s := make([]int, 0, 5)
s = append(s, 1, 2, 3)

f := func(sl []int) {
sl = append(sl, 4)
sl[0] = 10
}

f(s)

s = append(s, 5)
fmt.Println(s)
}


#код
Код выведет:
Anonymous Poll
0%
1 2 3 4 5
46%
10 2 3 4 5
26%
10 2 3 5
28%
1 2 3 5
➡️Код выведет: 10 2 3 5

➡️Идем по строчкам:
Создается слайс len=0 и cap=5, далее в него записываем 1, 2, 3. Массив родитель не аллоцировался, так как до cap мы еще не дошли.

📌Функция?! в рамках нее мы принимаем слайс, добавляем 4 и меняем первый элемент. И тут же мы ее вызываем, передавая s. И тут возникает вопрос: s вроде бы ссылочный тип данных, получается передав его в функцию, мы меняем именно его непосредственно по ссылке, ведь так?)

А вот и нет, в го ВСЕ аргументы функции передаются по значению, то есть копируются. Получается внутри функции
sl - копия s
То есть структура состоящая из трех полей len, cap, arr (ссылка на массив родитель) скопировалась

💻Теперь самый сок.
Мы добавили в sl 4 и поменяли первый элемент(помним в голове, что sl ссылается на тот же массив родитель), и получается, что у sl len=4, cap=5. Массив родитель в памяти выглядит так: [10 2 3 4]

Довольные выходим из функции, и приходим к s, у которого len=3, cap=5 (мы же с ним ничего не делали). И получается, что массив родитель [10 2 3 4], но len у него 3, то есть по сути сам s выглядит как [10 2 3]. И как раз-таки при append мы добавляем 5 в ячейку где уже была 4, и перезаписываем ее
🔘(память одна и та же). Таким образом итог вывода в s [10 2 3 5]🤍
Please open Telegram to view this post
VIEW IN TELEGRAM
13🔥21👍1
Хотели бы вы принять участие в бесплатном практическом созвоне, где мы напишем с нуля базовый микросервис на Go?
Anonymous Poll
66%
Да, интересно! Хочу посмотреть на реальный процесс разработки
29%
Возможно, зависит от темы и времени
6%
Нет, не интересно
🦆👎Утиная типизация (Duck Typing)

💡Идея: Если что-то ходит как утка и крякает как утка, то это утка.

💬Очень полезная вещь в продуктовой разработке, регулярно пользуюсь, когда хочу изолировать реализацию сущности (ее поля, от чего зависит и тд) от других сущностей. 💎Особенно полезно, когда пилим сервис и у нас есть слои, так вот по принципам красивой разработки и soLiD🟢 (в данном случае такой подход позволяет придерживаться сразу двух принципов L, D) архитектурные слои должны быть изолированными и независимыми друг от друга, таким образом любой слой очень просто меняется, не затрагивая другие.

Благодаря этому написание моков и подмена слоев на любую другую реализацию (например, юзали библиотеку БД gorm, а в v2-версии слоя решили перейти на самописные SQL запросы) превращается из боли и страданий, в кайф и красоту. На моем опыте я раз 20 переписывал слои сервиса, чтобы их просто замокать и сделать тесты, потому что все завязывались на реализации сущностей😭


type Speaker interface {
Speak() string
}

type Duck struct {
Name string
}

func (d Duck) Speak() string {
return fmt.Sprintf("%s говорит Кря", d.Name)
}

type Pigeon struct {
Name string
}

func (c Pigeon) Speak() string {
return fmt.Sprintf("%s говорит Гули-Гули", c.Name)
}

func main() {
animals := []Speaker{
Duck{Name: "Утка"},
Pigeon{Name: "Голубь"},
}

for _, a := range animals {
a.Speak()
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👏221
Что выведет код?

func main() {
mainSl := []string{"Test", "slices", "copying"}

var sl []string
copy(sl, mainSl)

sl1 := make([]string, 1, 3)
copy(sl1, mainSl)

fmt.Println(sl, sl1)
}


#код
➡️Код выведет: [] [Test]⬅️

Лучшее объяснение этого вывода состоит в идее copy. Предназначена эта функция для копирования элементов слайса-родителя в слайс-ребенок, отвязывая от ссылки на массив родитель
🧐(частая ошибка начинающих), однако делает она это достаточно топорно, она не может ни проинициализировать слайс-ребенок, ни изменить его как-либо, чтобы все элементы родителя скопировались.📌

🖱Всё, что умеет эта функция - глупое перекладывание элементов по ячейкам.

То есть, если есть выделенная под элемент ячейка слайса, то copy сделает свою работу, а вот если ячейки не оказалось, то copy просто пожимает плечами.
➡️

Теперь важный момент на понимание слайса: выделенные ячейки - это не cap! Это len! Ровно по этому числу мы понимаем сколько проинициализированных ячеек в слайсе

💬Теперь к результатам:

В sl мы ничего не получили по причине того, что инициализация вида
var sl []string

это инициализация nil-слайса, у которого нет ни длины, ни капасити
🔜copy не смог ничего перетащить

💡Во втором примере у sl1 уже установлена длина 1 и капасити 3, следовательно copy переносит в инициализированную ячейку элемент

Польза copy:
На просторах коммерческой разработки мало встречал использования этой функции, хотя и не понимаю почему
😢 Встречал много решений, где люди копировали слайс через форики, чтобы потом его мутировать/менять и не завязываться двумя слайсами на один массив родитель (опасно!). На мой взгляд, решение громоздкое и некрасивое. copy гораздо эффективнее и лаконичнее, и к тому же оно может также использоваться как получение так называемого ломтика.⬅️

func main() {
sl := []int{1, 2, 3, 4}

part := sl[:2]

partByCopy := make([]int, 2)
copy(partByCopy, sl)

// Изменим sl, чтобы показать наследование ссылки на массив-родитель
sl[0] = 5

fmt.Println(part, partByCopy)
// Получаем вывод:[5 2] [1 2] -> получение базового ломтика, приводит к риску мутаций, в отличие от copy
}
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍22
Давайте согласуем удобное время для мита по написанию микросервиса
Anonymous Poll
19%
Сегодня
15%
25 июня
19%
26 июня
11%
27 июня
67%
Выходные
Что произойдет при запуске программы?

var m = map[string]int{"a": 1}

func main() {
go Read()
time.Sleep(1 * time.Second)
go Write()
time.Sleep(1 * time.Minute)
}

func Read() {
for {
fmt.Println(m["a"])
}
}

func Write() {
for i := 0; ; i++ {
m[fmt.Sprintf("%d", i)] = i
}
}


#код
Голосуем за время воскресного мита
Anonymous Poll
11%
11:00
17%
12:00
44%
17:00
28%
21:00
17%
Не могу в вс🙁
Go Level Up
Что произойдет при запуске программы? var m = map[string]int{"a": 1} func main() { go Read() time.Sleep(1 * time.Second) go Write() time.Sleep(1 * time.Minute) } func Read() { for { fmt.Println(m["a"]) } } func Write() { for i := 0;…
📌При запуске программы произойдет - бесконечный вывод 1 и фатальная ошибка fatal error: concurrent map read and map write

➡️Важно! Данный результат произойдет при версии го, где мапа реализована в виде бакетов, то есть до swiss table. Какой результат будет в новой версии, требует отдельного поста.➡️

💻Итак, вернемся к нашей старой доброй мапе на бакетах. Каждый раз, когда показываю менти этот код, они вчитываются в код и пытаются найти здесь скрытый смысл. Но нет, по сути все, что достаточно увидеть - чтение и запись в мапу из горутин 🔜🔜🔜 ошибка. Дело в том, что мапа не потокобезопасна. 🥳

➡️На моках постоянно отвечают так😳Но не понимают почему так и что это значит. Это значит, что как раз-таки из мапы нельзя читать и писать одновременно в связи с вероятностью обратиться к несуществующему эвакуированному куску памяти или вовсе вызвать гонку данных.

💬Причина: при эвакуации старые бакеты постепенно освобождаются и ключи-значения переходят в новые выделенные бакеты бОльшего количества. Так вот когда мы пишем мы сталкиваемся с риском эвакуации, а когда читаем с риском обратиться к несуществующему удаленному блоку памяти или непредсказуемым данным(из-за data race)🖥
👍321
➡️Итак, фаталы и конкурентные ошибки мапы это классика начинающего Go-разраба💻

🖱Но что делать, когда так хочется заюзать мапу конкурентно, чтобы не блокироваться на вставку/чтение, и при этом не упасть с злосчастным fatal error. 😨

💻Пример решения строится на мьютексах, что логично, нам ведь по факту просто нужно блокировать блоки памяти, чтобы они не сбежали от нас чисто случайно в другие недра ноликов и единичек.🖱

📌В данном примере лучше юзать не просто мьютекс, а RWMutex. Разница? Read-Write Mutex позволяет блокировать блок памяти только на запись, и при этом разрешать этот же блок читать.👣

type SafeMap struct {
sync.RWMutex
data map[string]int
}

func main() {
protectedMap := SafeMap{
data: map[string]int{"a": 1},
}

go protectedMap.Read()
time.Sleep(1 * time.Second)
go protectedMap.Write()
time.Sleep(1 * time.Minute)
}

func (sm *SafeMap) Read() {
for {
sm.RLock()
fmt.Println(sm.data["a"])
sm.RUnlock()
}
}

func (sm *SafeMap) Write() {
for i := 0; ; i++ {
sm.Lock()
sm.data[fmt.Sprintf("%d", i)] = i
sm.Unlock()
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
83👍3
Коллеги, спасибо за участие в опросе! Большинство выбрало вариант 17:00. 💻

🖱Сегодня в 17:00 (МСК) созвон — обсудим как писать микросервис на го. Жду всех!📌
Please open Telegram to view this post
VIEW IN TELEGRAM
31👍1
Коллеги, созвон по написанию микросервиса начинается. Всех жду

https://us04web.zoom.us/j/79268156565?pwd=dSYcbdVxZC0K0IJx97ubNHgoYIGt8w.1
4
Коллеги извиняюсь, зум у меня странно работает, крашится. Переходим в гугл мит https://meet.google.com/bfc-mioz-qhz
1
💻Благодарю всех, кто присоединился к созвону! На мой взгляд получилось удачно) Надеюсь всем было полезно🤍

💬На этой неделе я доделаю конспект с подробным разбором всего, что мы успели обсудить: поэтапно опишу создание микросервиса, приведу примеры кода, объясню почему и зачем использую конкретный паттерн разработки, ⚡️добавлю ответы на ваши вопросы которые были в созвоне

📌Оставайтесь на связи — скоро будет ещё больше интересного! 💎🔥
Please open Telegram to view this post
VIEW IN TELEGRAM
14👍4
Что выведет код?

func main() {
runtime.GOMAXPROCS(1)

wg := sync.WaitGroup{}

for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()

fmt.Println(n)
}(i)
}

wg.Wait()
}