Golang | Вопросы собесов
4.33K subscribers
28 photos
700 links
Download Telegram
🤔 Какие типы каналов существуют?

В Go существует два типа каналов: буферизованные (buffered) и небуферизованные (unbuffered). Небуферизованные каналы требуют, чтобы отправляющая и принимающая горутина синхронизировались друг с другом, что делает их блокирующими. Буферизованные каналы имеют определённый размер буфера, и горутина может отправить сообщение в канал, не ожидая немедленного получения, пока буфер не заполнится. Оба типа каналов используются для передачи данных между горутинами и синхронизации их работы.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое lock-free структуры данных, и есть ли в Go такие?

Это структуры данных, которые обеспечивают синхронизацию и взаимодействие между потоками (горутинами) без использования традиционных блокировок (мьютексов). Вместо блокировок они полагаются на атомарные операции, такие как Compare-And-Swap (CAS), для обеспечения корректного и последовательного доступа к данным.

🚩Основные характеристики

🟠Отсутствие блокировок
Они не используют мьютексы или другие блокирующие механизмы.
🟠Высокая производительность
Они уменьшают задержки и повышают производительность за счет минимизации времени ожидания потоков.
🟠Избегание взаимных блокировок (deadlocks)
Так как нет блокировок, невозможно возникновение взаимных блокировок.

🚩Примеры использования атомарных операций

Атомарные счетчики:
package main

import (
"fmt"
"sync/atomic"
)

func main() {
var counter int32
atomic.AddInt32(&counter, 1)
fmt.Println(atomic.LoadInt32(&counter)) // Вывод: 1
}


Lock-Free очередь (псевдокод):
package main

import (
"fmt"
"sync/atomic"
"unsafe"
)

type Node struct {
value int
next unsafe.Pointer
}

type LockFreeQueue struct {
head unsafe.Pointer
tail unsafe.Pointer
}

func NewLockFreeQueue() *LockFreeQueue {
node := unsafe.Pointer(&Node{})
return &LockFreeQueue{head: node, tail: node}
}

func (q *LockFreeQueue) Enqueue(value int) {
node := &Node{value: value}
for {
tail := atomic.LoadPointer(&q.tail)
next := atomic.LoadPointer(&(*Node)(tail).next)
if next == nil {
if atomic.CompareAndSwapPointer(&(*Node)(tail).next, next, unsafe.Pointer(node)) {
atomic.CompareAndSwapPointer(&q.tail, tail, unsafe.Pointer(node))
return
}
} else {
atomic.CompareAndSwapPointer(&q.tail, tail, next)
}
}
}

func (q *LockFreeQueue) Dequeue() (int, bool) {
for {
head := atomic.LoadPointer(&q.head)
tail := atomic.LoadPointer(&q.tail)
next := atomic.LoadPointer(&(*Node)(head).next)
if head == tail {
if next == nil {
return 0, false // очередь пуста
}
atomic.CompareAndSwapPointer(&q.tail, tail, next)
} else {
value := (*Node)(next).value
if atomic.CompareAndSwapPointer(&q.head, head, next) {
return value, true
}
}
}
}

func main() {
q := NewLockFreeQueue()
q.Enqueue(1)
q.Enqueue(2)
value, ok := q.Dequeue()
if ok {
fmt.Println("Dequeued:", value) // Вывод: Dequeued: 1
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как слайсы работают?

Слайсы в Go — это динамические массивы, которые ссылаются на подмножество базового массива, но могут изменять свой размер. Они содержат указатель на базовый массив, длину и ёмкость (capacity), которая определяет, сколько элементов может быть добавлено до выделения новой памяти. Слайсы эффективны для работы с массивами, так как они позволяют обращаться к частям массива без копирования данных. Если слайс выходит за пределы своей ёмкости, создаётся новый массив, и элементы копируются.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как можно обработать панику с помощью defer и recovery?

Механизм отложенных вызовов (defer) и восстановления (recover) используется для обработки паник, что позволяет безопасно завершить выполнение программы или выполнить необходимые действия при возникновении ошибок.

🚩Шаги

1⃣Использование `defer` для отложенного выполнения
Вызовы, обернутые в defer, выполняются в обратном порядке по завершении функции, в которой они объявлены, даже если возникла паника.

2⃣Использование `recover` для восстановления после паники
recover перехватывает панику, если она вызывается внутри функции, отложенной с помощью defer. Если recover вызывается вне контекста паники, он возвращает nil.

🚩Пример использования `defer` и `recover`

package main

import (
"fmt"
)

func main() {
fmt.Println("Starting the program...")
safeFunction()
fmt.Println("Program finished successfully.")
}

func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
riskyFunction()
}

func riskyFunction() {
fmt.Println("About to cause a panic...")
panic("Something went wrong!")
fmt.Println("This line will not be executed.")
}


1⃣Основная функция `main`
Запускает программу и вызывает safeFunction.
Печатает сообщение "Starting the program...".

2⃣Функция `safeFunction`
Содержит отложенную анонимную функцию, которая вызывает recover для обработки возможной паники.
Вызывает riskyFunction.

3⃣Функция `riskyFunction`
Печатает сообщение "About to cause a panic...".
Вызывает панику с сообщением "Something went wrong!". Когда riskyFunction вызывает панику, управление передается отложенной функции в safeFunction, которая вызывает recover. recover перехватывает панику, и программа продолжает выполнение.
Starting the program...
About to cause a panic...
Recovered from panic: Something went wrong!
Program finished successfully.


🚩Использование `defer` и `recover` для безопасного завершения

Можно использовать defer и recover для обеспечения выполнения необходимых действий перед завершением программы, даже если возникла паника. Например, закрытие файлов, освобождение ресурсов и т.д.
package main

import (
"fmt"
"os"
)

func main() {
file, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
file.Close()
fmt.Println("File closed")
}()
// Работа с файлом
panic("Something went wrong during file processing")
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие БД бывают?

Базы данных бывают реляционные (например, PostgreSQL, MySQL), которые хранят данные в таблицах и используют SQL для управления данными, и нереляционные (NoSQL), такие как MongoDB и Cassandra, которые поддерживают гибкие структуры данных, такие как документы или ключ-значение. Реляционные БД обеспечивают строгую согласованность данных и сложные запросы, тогда как NoSQL базы данных ориентированы на масштабируемость и обработку больших объёмов данных. Выбор типа БД зависит от требований приложения.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Можно ли использовать один и тот же буфер []byte в нескольких горутинах?

Может быть как безопасным, так и небезопасным, в зависимости от контекста и конкретного использования.

🚩Когда использование небезопасно

package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
buffer := make([]byte, 10)

for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
buffer[i] = byte(i) // Несколько горутин одновременно модифицируют буфер
}(i)
}

wg.Wait()
fmt.Println("Buffer:", buffer)
}


🚩Безопасное использование одного буфера

🟠Только чтение
Если несколько горутин только читают из буфера, это безопасно. Пример безопасного использования:
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")

for i := 0; i < len(buffer); i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("buffer[%d]: %c\n", i, buffer[i])
}(i)
}

wg.Wait()
}


🟠Использование мьютексов
Для синхронизации доступа к буферу используйте мьютексы (sync.Mutex). Это гарантирует, что только одна горутина в данный момент модифицирует буфер.
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
var mu sync.Mutex
buffer := make([]byte, 10)

for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
mu.Lock()
buffer[i] = byte(i)
mu.Unlock()
}(i)
}

wg.Wait()
fmt.Println("Buffer:", buffer)
}


🟠Копирование буфера
Если каждая горутина должна работать с независимой копией буфера, создавайте копии для каждой горутины.
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")

for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
localBuffer := make([]byte, len(buffer))
copy(localBuffer, buffer)
localBuffer[i] = byte(i)
fmt.Printf("Goroutine %d: %s\n", i, localBuffer)
}(i)
}

wg.Wait()
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое WaitGroup?

`WaitGroup` в Go используется для синхронизации горутин, чтобы гарантировать, что основная горутина ждёт завершения всех дочерних горутин. Метод `Add()` увеличивает счетчик горутин, метод `Done()` уменьшает его, а метод `Wait()` блокирует выполнение до тех пор, пока счетчик не станет равен нулю. `WaitGroup` удобен для управления потоками выполнения, особенно когда несколько горутин выполняют параллельные задачи. Это упрощает координацию завершения работы всех горутин в программе.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие типы мьютексов предоставляет stdlib?

🟠sync.Mutex
Используется для обеспечения эксклюзивного доступа к общему ресурсу. Когда мьютекс заблокирован одной горутиной, все другие горутины, пытающиеся заблокировать этот же мьютекс, будут заблокированы до тех пор, пока первый мьютекс не будет разблокирован.

🚩Методы:

🟠Lock()
Блокирует мьютекс. Если мьютекс уже заблокирован, текущая горутина блокируется до тех пор, пока мьютекс не будет освобожден.

🟠Unlock()
Разблокирует мьютекс. Если есть другие горутины, ожидающие блокировки этого мьютекса, одна из них будет разблокирована и сможет продолжить выполнение.
package main

import (
"fmt"
"sync"
)

var mu sync.Mutex
var counter int

func increment() {
mu.Lock()
counter++
mu.Unlock()
}

func main() {
var wg sync.WaitGroup

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

wg.Wait()
fmt.Println("Counter:", counter)
}


🚩sync.RWMutex

sync.RWMutex (мьютекс для чтения и записи) предоставляет два типа блокировок:
Для чтения (RLock): Позволяет нескольким горутинам одновременно читать данные.
Для записи (Lock): Обеспечивает эксклюзивный доступ для записи, блокируя все другие операции чтения и записи.

🚩Методы

🟠RLock()
Блокирует мьютекс для чтения. Если мьютекс уже заблокирован для записи, текущая горутина блокируется до тех пор, пока мьютекс не будет освобожден.

🟠RUnlock()
Разблокирует мьютекс для чтения.

🟠Lock()
Блокирует мьютекс для записи. Если мьютекс заблокирован для чтения или записи, текущая горутина блокируется до тех пор, пока мьютекс не будет освобожден.

🟠Unlock()
Разблокирует мьютекс для записи.
package main

import (
"fmt"
"sync"
)

var rwMu sync.RWMutex
var counter int

func readCounter() int {
rwMu.RLock()
defer rwMu.RUnlock()
return counter
}

func increment() {
rwMu.Lock()
counter++
rwMu.Unlock()
}

func main() {
var wg sync.WaitGroup

// Запуск нескольких горутин для чтения
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Read Counter:", readCounter())
}()
}

// Запуск нескольких горутин для записи
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}

wg.Wait()
fmt.Println("Final Counter:", counter)
}


🚩Сравнение

🟠Эксклюзивность
sync.Mutex: Блокирует доступ ко всем операциям (чтение и запись).
sync.RWMutex: Позволяет одновременное чтение, но блокирует доступ при записи.

🟠Производительность
sync.Mutex: Подходит для сценариев с частыми операциями записи или равномерным распределением чтений и записей.
sync.RWMutex: Подходит для сценариев с частыми операциями чтения и редкими операциями записи.

🟠Применение
sync.Mutex: Используйте, когда необходимо обеспечить эксклюзивный доступ к ресурсу без различий между операциями чтения и записи.
sync.RWMutex: Используйте, когда операции чтения преобладают над операциями записи, чтобы повысить производительность за счет одновременного чтения.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как ООП в Go реализовано?

Go не поддерживает классическое ООП с наследованием классов, но предлагает альтернативу через композицию и интерфейсы. В Go объекты могут включать другие объекты как поля (встраивание структур), что позволяет создавать сложные иерархии без явного наследования. Интерфейсы в Go определяют набор методов, которые должен реализовать тип, но не требуют явной привязки. Это делает Go более гибким и помогает избежать проблем, связанных с жёстким наследованием.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как тесты и TDD влияют на организацию кода?

Они способствуют созданию кода, который является более надежным, поддерживаемым и расширяемым.

🚩Влияние тестов

🟠Повышение надежности
Написание тестов помогает обнаружить ошибки и дефекты на ранних стадиях разработки. Регулярное тестирование кода обеспечивает его корректное функционирование и уменьшает количество ошибок.

🟠Поддерживаемость
Тесты служат документацией, показывая, как должен работать код. Это облегчает понимание кода новым разработчикам и помогает избежать регрессий при внесении изменений.

🟠Декомпозиция и модульность
Для упрощения тестирования разработчики разбивают код на небольшие, легко тестируемые модули и функции. Это приводит к более чистому и структурированному коду.

🟠Четкие интерфейсы
Для облегчения тестирования функции и модули должны иметь четко определенные интерфейсы и зависимости. Это приводит к лучшему дизайну API и модулей.

🟠Снижение сложности
Тесты помогают выявить излишне сложные и трудно тестируемые участки кода, что стимулирует рефакторинг и упрощение кода.

🚩Влияние TDD

Это методология разработки, при которой тесты пишутся перед написанием функционального кода. Этот подход оказывает дополнительное влияние на организацию кода:

🟠Малые итерации
TDD способствует разработке кода малыми итерациями. Каждый цикл включает написание теста, написание кода для его прохождения и рефакторинг. Это улучшает качество кода и позволяет быстро обнаруживать ошибки

🟠Фокус на требованиях
Пишутся только те функции, которые необходимы для прохождения тестов, что помогает сосредоточиться на текущих требованиях и избежать излишней функциональности.

🟠Улучшение дизайна
TDD стимулирует создание кода с хорошим дизайном, так как разработчики изначально проектируют код таким образом, чтобы он был легко тестируемым.

🟠Документирование через тесты
Тесты, написанные в рамках TDD, служат живой документацией, описывающей, как должен работать код. Это облегчает понимание кода другими разработчиками и упрощает процесс внесения изменений.

🚩Пример влияния тестов и TDD на код

1⃣Написание теста
package main

import "testing"

func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, but got %d", result)
}
}


2⃣Написание минимального кода для прохождения теста
package main

func Add(a, b int) int {
return a + b
}


3⃣Рефакторинг кода
В данном случае рефакторинг не требуется, так как код уже является простым и понятным.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Зачем нужна конструкция defer?

`defer` в Go откладывает выполнение функции или выражения до завершения текущей функции. Это особенно полезно для гарантированного освобождения ресурсов, таких как закрытие файлов, сокетов или освобождение блокировок, даже если функция завершится с ошибкой. `defer` улучшает читаемость кода, позволяя явно указать операции очистки рядом с инициализацией ресурсов. Вложенные `defer` выполняются в порядке стека (последний добавленный — первый выполненный).

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Можешь описать принцип работы lock-free?

Алгоритмы обеспечивают доступ к общим ресурсам в многопоточной среде без использования традиционных блокирующих механизмов, таких как мьютексы. Основная идея заключается в том, что хотя один или несколько потоков могут быть временно приостановлены, по крайней мере один поток всегда будет прогрессировать. Это достигается с помощью атомарных операций, которые гарантируют целостность данных и предотвращают состояния гонки.

🚩Принципы

🟠Атомарные операции
Выполняются полностью или не выполняются вообще, без промежуточных состояний. Они обеспечивают безопасность и целостность данных в многопоточной среде.
Compare-And-Swap (CAS)Одна из самых распространенных атомарных операций, которая сравнивает текущее значение переменной с ожидаемым и, если они совпадают, обновляет переменную новым значением.
Fetch-And-Add: Атомарно увеличивает значение переменной и возвращает старое значение.

🟠Минимизация критических секций
Lock-free алгоритмы стараются минимизировать критические секции, то есть участки кода, где потоки взаимодействуют с общими данными, чтобы уменьшить вероятность блокировок и состояний гонки.

🟠Избежание блокировок
В lock-free алгоритмах не используются традиционные механизмы блокировки, такие как мьютексы, что исключает вероятность взаимных блокировок (deadlocks).
package main

import (
"fmt"
"sync/atomic"
"unsafe"
)

type Node struct {
value int
next *Node
}

type Stack struct {
head *Node
}

func (s *Stack) Push(value int) {
newNode := &Node{value: value}
for {
oldHead := s.head
newNode.next = oldHead
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&s.head)), unsafe.Pointer(oldHead), unsafe.Pointer(newNode)) {
break
}
}
}

func (s *Stack) Pop() (int, bool) {
for {
oldHead := s.head
if oldHead == nil {
return 0, false
}
newHead := oldHead.next
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&s.head)), unsafe.Pointer(oldHead), unsafe.Pointer(newHead)) {
return oldHead.value, true
}
}
}

func main() {
stack := &Stack{}
stack.Push(1)
stack.Push(2)
value, ok := stack.Pop()
if ok {
fmt.Println("Popped:", value)
} else {
fmt.Println("Stack is empty")
}
}


🚩Плюсы

Отсутствие взаимных блокировок
Потоки не блокируют друг друга, что исключает проблему deadlock.
Высокая производительность
Параллельный доступ к данным без блокировок улучшает производительность, особенно на многоядерных системах.
Меньшие накладные расходы
Избегание блокировок снижает накладные расходы, связанные с контекстными переключениями и синхронизацией.

🚩Минусы

Сложность разработки
Lock-free алгоритмы сложны в реализации и отладке из-за необходимости работы с атомарными операциями и контроля состояний гонки.
Ограниченная поддержка
Не все языки и платформы предоставляют полную поддержку атомарных операций.
Проблемы с масштабируемостью
Частые повторные попытки атомарных операций могут ухудшить масштабируемость в некоторых сценариях.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие недостатки есть у Go?

Одним из недостатков Go является отсутствие дженериков до версии 1.18, что делало код менее гибким и приводило к повторению однотипных функций для разных типов данных. Go также не поддерживает наследование классов и сложные конструкции ООП, что может ограничивать архитектуру приложений для разработчиков, привыкших к традиционным ООП языкам. Ещё один недостаток — слабая стандартная библиотека для работы с шаблонами и сложными функциональными абстракциями. Однако простота Go делает его эффективным для системного и сетевого программирования.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Почему в TDD тесты пишутся прежде кода?

Этот подход обеспечивает ряд преимуществ, которые способствуют улучшению качества кода, его поддерживаемости и соответствию требованиям.

🚩Причины

🟠Определение требований
Написание тестов перед кодом помогает разработчикам четко определить требования к функции или модулю. Тесты выступают в роли спецификации, описывающей, как должен работать код. Это помогает избежать недоразумений и гарантирует, что все требования учтены с самого начала.

🟠Сосредоточение на функциональности
TDD заставляет разработчиков сосредоточиться на минимальной функциональности, необходимой для прохождения тестов. Это предотвращает избыточное проектирование и разработку ненужных функций. Каждый тест фокусируется на одном аспекте функциональности, что помогает создавать чистый и целенаправленный код.

🟠Более простой и чистый дизайн кода
Написание тестов перед кодом стимулирует создание модульного и хорошо структурированного кода. Поскольку тесты проверяют небольшие и изолированные части кода, это побуждает разработчиков разделять большие задачи на более мелкие и управляемые модули, что улучшает дизайн и архитектуру системы.

🟠Обратная связь и уверенность
Тесты, написанные перед кодом, обеспечивают немедленную обратную связь. Если код не проходит тест, это означает, что он не соответствует требованиям. Это позволяет разработчикам быстро обнаруживать и исправлять ошибки, повышая уверенность в корректности кода.

🟠Поддержка и документация
Тесты служат живой документацией кода. Они показывают, как должен работать код и какие случаи использования поддерживаются. Это облегчает понимание кода другим разработчикам и упрощает его поддержку в будущем.

🟠Улучшение качества кода
Постоянное написание тестов и их прохождение помогает поддерживать высокое качество кода. Тесты выявляют дефекты на ранних стадиях разработки и обеспечивают, что новые изменения не нарушают существующую функциональность.

🚩Пример процесса TDD

1⃣Написание теста
package main

import "testing"

func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, but got %d", result)
}
}


2⃣Запуск теста
На этом этапе тест не пройдет, так как функция Add еще не реализована.

3⃣Реализация кода
package main

func Add(a, b int) int {
return a + b
}


4⃣Запуск теста снова
Теперь тест пройдет, так как функция Add реализована корректно.

5⃣Рефакторинг
При необходимости выполняется рефакторинг кода для улучшения его структуры, при этом тесты продолжают проходить, что гарантирует сохранение функциональности.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое context в Go?

`context` в Go используется для управления временем выполнения горутин, передачи метаданных и отмены операций. Контексты позволяют устанавливать тайм-ауты, дедлайны и передавать отмену сигналов между горутинами, что помогает координировать завершение связанных операций. `context` передаётся как аргумент функций, и это позволяет управлять зависимыми процессами более эффективно. Это особенно полезно в сетевом программировании и запросах, которые могут быть отменены по истечению времени.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Если у кода плохая организация, как это поймёшь?

🟠Высокое сцепление (Tight Coupling)
Модули сильно зависят друг от друга. Изменения в одном модуле требуют изменений в других.
🟠Низкая связанность (Low Cohesion)
Модуль выполняет несколько различных задач. Трудно поддерживать и изменять код.
🟠Большие функции и классы (God Objects)
Содержат слишком много логики. Длинные и сложные для понимания.
🟠Дублирование кода
Один и тот же код повторяется в нескольких местах.
🟠Отсутствие тестов
Код не покрыт тестами. Трудно проверять корректность.
🟠Неразборчивые имена переменных и функций
Имена не отражают назначение. Трудно понять код.
🟠Сложная и запутанная логика
Логика кода трудна для понимания. Много условных операторов.
🟠Отсутствие или недостаток документации
Нет комментариев и документации.

🚩Примеры

Высокое сцепление:
type UserService struct {
userRepository UserRepository
emailService EmailService
}


Низкая связанность:
type UserService struct {
db Database
businessLogic BusinessLogic
notificationService NotificationService
}


Большие функции и классы:
func ProcessData() {
// Много строк кода
}


Дублирование кода:
func CalculateSum(a, b int) int {
return a + b
}

func AddNumbers(x, y int) int {
return x + y
}


Неразборчивые имена:
func DoSomething(x int) int {
return x * 2
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В чем разница TCP и UDP?

TCP (Transmission Control Protocol) — это протокол, который обеспечивает надёжную передачу данных, гарантируя, что все пакеты будут доставлены в правильном порядке и без потерь. TCP устанавливает соединение между клиентом и сервером перед передачей данных, проверяет целостность пакетов и управляет повторной передачей потерянных данных. UDP (User Datagram Protocol) не гарантирует доставку пакетов, не обеспечивает контроль за порядком их получения и не требует установления соединения, что делает его более быстрым, но менее надёжным. UDP предпочтителен для приложений, где скорость важнее надёжности, например, для видеостриминга или онлайн-игр.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие знаешь способы общения между процессами?

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

🚩Методы

🟠Каналы (Pipes)
Однонаправленные каналы между родительским и дочерним процессами. Обмен данными между произвольными процессами.
🟠Очереди сообщений (Message Queues)
Обмен сообщениями через общую очередь. System V, POSIX message queues.
🟠Общая память (Shared Memory)
Совместное использование памяти для обмена данными. POSIX (shm_open), System V shared memory.
🟠Семафоры (Semaphores)
Синхронизация доступа к ресурсам. POSIX (sem_open), System V semaphores.
🟠Сокеты (Sockets)
Обмен данными через сетевые соединения. Unix domain sockets, TCP/UDP.
🟠Сигналы (Signals)
Отправка и обработка событий. kill(), signal() в Unix.
🟠Файлы и базы данных
Обмен данными через файлы или базы данных. Файлы, SQLite.

🚩Примеры

Каналы (Pipes)
package main

import (
"fmt"
"os"
"syscall"
)

func main() {
fd := make([]int, 2)
if err := syscall.Pipe(fd); err != nil {
fmt.Println("Ошибка создания канала:", err)
os.Exit(1)
}

pid, err := syscall.ForkExec("", nil, &syscall.ProcAttr{Files: []uintptr{uintptr(fd[0]), uintptr(fd[1]), uintptr(os.Stderr.Fd())}})
if err != nil {
fmt.Println("Ошибка создания процесса:", err)
os.Exit(1)
}

if pid == 0 { // Child process
syscall.Close(fd[0])
_, err := syscall.Write(fd[1], []byte("Hello"))
if err != nil {
fmt.Println("Ошибка записи в канал:", err)
}
syscall.Close(fd[1])
} else { // Parent process
buffer := make([]byte, 5)
syscall.Close(fd[1])
_, err := syscall.Read(fd[0], buffer)
if err != nil {
fmt.Println("Ошибка чтения из канала:", err)
}
syscall.Close(fd[0])
fmt.Printf("Получено: %s\n", buffer)
}
}


Общая память (Shared Memory)
package main

import (
"fmt"
"os"
"syscall"
)

func main() {
// Создание сегмента разделяемой памяти
segmentID, _, err := syscall.Syscall(syscall.SYS_SHMGET, uintptr(syscall.IPC_PRIVATE), uintptr(4), uintptr(syscall.S_IRUSR|syscall.S_IWUSR))
if err != 0 {
fmt.Println("Ошибка создания сегмента разделяемой памяти:", err)
os.Exit(1)
}

// Присоединение к сегменту
sharedMemory, _, err := syscall.Syscall(syscall.SYS_SHMAT, segmentID, 0, 0)
if err != 0 {
fmt.Println("Ошибка присоединения к разделяемой пам


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое mutex, какие они бывают и как их использовать?

Mutex (mutual exclusion) — это механизм синхронизации, который предотвращает одновременный доступ нескольких потоков к общим ресурсам, таким как данные или память. Основная задача мьютекса — блокировка доступа к ресурсу до тех пор, пока один поток не завершит работу с ним, после чего ресурс становится доступным для других потоков. Мьютексы бывают обычными (нельзя повторно захватывать одним и тем же потоком) и рекурсивными (позволяют одному потоку захватывать мьютекс несколько раз без блокировки). Мьютексы используются для решения проблем многопоточности, таких как гонки данных, и обеспечивают согласованность данных при параллельном доступе.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Чем воспользоваться, если два процесса запрашивают доступ к оперативной памяти, а свободного места только для одного?

🟠Семафоры (Semaphores)
Позволяют синхронизировать доступ к ограниченным ресурсам, в данном случае — к оперативной памяти. Семафор может быть инициализирован значением 1, указывая, что доступен только один слот памяти. Когда один из процессов захватывает семафор, другой процесс будет заблокирован, пока семафор не освободится.
package main

import (
"fmt"
"sync"
"time"
)

var mutex sync.Mutex

func process(id int) {
mutex.Lock() // Захват семафора
fmt.Printf("Process %d: Accessing memory\n", id)
time.Sleep(2 * time.Second) // Имитируем доступ к памяти
fmt.Printf("Process %d: Releasing memory\n", id)
mutex.Unlock() // Освобождение семафора
}

func main() {
var wg sync.WaitGroup
wg.Add(2)

go func() {
defer wg.Done()
process(1)
}()

go func() {
defer wg.Done()
process(2)
}()

wg.Wait()
}


🟠Мьютексы (Mutexes)
Обеспечивают эксклюзивный доступ к критической секции. Один процесс захватывает мьютекс перед доступом к памяти, другой процесс будет заблокирован до освобождения мьютекса.
package main

import (
"fmt"
"sync"
"time"
)

var mutex sync.Mutex

func process(id int) {
mutex.Lock() // Захват мьютекса
fmt.Printf("Process %d: Accessing memory\n", id)
time.Sleep(2 * time.Second) // Имитируем доступ к памяти
fmt.Printf("Process %d: Releasing memory\n", id)
mutex.Unlock() // Освобождение мьютекса
}

func main() {
var wg sync.WaitGroup
wg.Add(2)

go func() {
defer wg.Done()
process(1)
}()

go func() {
defer wg.Done()
process(2)
}()

wg.Wait()
}


🟠Очередь сообщений (Message Queues)
Может быть использована для координации доступа к памяти, позволяя процессам обмениваться сообщениями о доступности ресурса.
package main

import (
"fmt"
"time"
)

type Message struct {
processID int
}

func accessMemory(processID int) {
fmt.Printf("Process %d: Accessing memory\n", processID)
time.Sleep(2 * time.Second) // Имитируем доступ к памяти
fmt.Printf("Process %d: Releasing memory\n", processID)
}

func main() {
msgChannel := make(chan Message)

go func() {
// "Дочерний" процесс
message := Message{processID: 1}
msgChannel <- message
msg := <-msgChannel
accessMemory(msg.processID)
}()

go func() {
// "Родительский" процесс
message := Message{processID: 2}
msgChannel <- message
msg := <-msgChannel
accessMemory(msg.processID)
}()

msg := <-msgChannel // Получаем сообщение от дочернего процесса
msgChannel <- msg // Отправляем обратно для завершения доступа
msg = <-msgChannel // Получаем сообщение от родительского процесса
msgChannel <- msg // Отправляем обратно для завершения доступа

time.Sleep(3 * time.Second) // Подождем завершения горутин
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В чем ключевое различие слайса от массива?

В Go, массивы имеют фиксированный размер, который определяется при компиляции, и не может быть изменен во время выполнения программы. Слайсы являются более гибкими и динамичными структурами данных, которые предоставляют вид на базовый массив. Слайсы поддерживают автоматическое расширение при добавлении элементов, что делает их более удобными для использования во многих сценариях.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM