Общие вопросы

Что такое SOLID?

SOLID — это акроним, представляющий пять основных принципов объектно-ориентированного программирования и дизайна, которые помогают разработчикам создавать более устойчивые, удобные в обслуживании и расширяемые системы. Эти принципы были популяризированы Робертом Мартином (Uncle Bob) и часто используются для улучшения качества программного обеспечения. Вот краткое описание каждого из принципов SOLID:

  1. Single Responsibility Principle (Принцип единственной ответственности): Каждый класс должен иметь только одну причину для изменения. Это означает, что класс должен выполнять только одну задачу или иметь одну область ответственности. Это упрощает управление классом и минимизирует влияние изменений в одной части программы на другие.

  2. Open/Closed Principle (Принцип открытости/закрытости): Программные сущности (классы, модули, функции и т.д.) должны быть открыты для расширения, но закрыты для модификации. Это означает, что можно добавлять новые функциональности, не изменяя существующий код.

  3. Liskov Substitution Principle (Принцип подстановки Барбары Лисков): Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. Например, в классе-наследнике должно быть возможно использовать методы базового класса без искажения функциональности.

  4. Interface Segregation Principle (Принцип разделения интерфейса): Клиенты не должны быть вынуждены зависеть от интерфейсов, которые они не используют. Этот принцип направлен на уменьшение нефункциональных зависимостей между классами.

  5. Dependency Inversion Principle (Принцип инверсии зависимостей): Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей должны зависеть от абстракций (например, интерфейсов). Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

Можно ли на go писать соблюдая принципы SOLID?

Да, на Go можно писать код, соблюдая принципы SOLID. Хотя Go не является объектно-ориентированным языком в традиционном смысле, он поддерживает интерфейсы и композицию, что позволяет применять принципы SOLID для создания гибкого, поддерживаемого и расширяемого кода.

Принципы SOLID

  1. Single Responsibility Principle (SRP): Принцип единственной ответственности

  2. Open/Closed Principle (OCP): Принцип открытости/закрытости

  3. Liskov Substitution Principle (LSP): Принцип подстановки Барбары Лисков

  4. Interface Segregation Principle (ISP): Принцип разделения интерфейсов

  5. Dependency Inversion Principle (DIP): Принцип инверсии зависимостей

Примеры применения принципов SOLID в Go

Single Responsibility Principle (SRP)

Каждый модуль или класс должен иметь одну и только одну причину для изменения.

package main

import "fmt"

// User хранит информацию о пользователе
type User struct {
    Name  string
    Email string
}

// UserService отвечает за управление пользователями
type UserService struct{}

func (us *UserService) CreateUser(name, email string) *User {
    return &User{Name: name, Email: email}
}

// EmailService отвечает за отправку email
type EmailService struct{}

func (es *EmailService) SendEmail(user *User, message string) {
    fmt.Printf("Sending email to %s: %s\n", user.Email, message)
}

func main() {
    userService := &UserService{}
    emailService := &EmailService{}

    user := userService.CreateUser("John Doe", "john@example.com")
    emailService.SendEmail(user, "Welcome to our service!")
}

Open/Closed Principle (OCP)

Программные сущности должны быть открыты для расширения, но закрыты для модификации.

package main

import "fmt"

// NotificationSender определяет интерфейс для отправки уведомлений
type NotificationSender interface {
    SendNotification(user *User, message string)
}

// EmailSender реализует отправку уведомлений по email
type EmailSender struct{}

func (es *EmailSender) SendNotification(user *User, message string) {
    fmt.Printf("Sending email to %s: %s\n", user.Email, message)
}

// SMSSender реализует отправку уведомлений по SMS
type SMSSender struct{}

func (ss *SMSSender) SendNotification(user *User, message string) {
    fmt.Printf("Sending SMS to %s: %s\n", user.Name, message)
}

func main() {
    user := &User{Name: "John Doe", Email: "john@example.com"}

    var sender NotificationSender

    sender = &EmailSender{}
    sender.SendNotification(user, "Welcome to our service!")

    sender = &SMSSender{}
    sender.SendNotification(user, "Your verification code is 1234")
}

Liskov Substitution Principle (LSP)

Объекты в программе должны быть заменяемы экземплярами их подтипов без изменения правильности выполнения программы.

package main

import "fmt"

// Shape определяет интерфейс для геометрических фигур
type Shape interface {
    Area() float64
}

// Rectangle реализует интерфейс Shape
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Circle реализует интерфейс Shape
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

func printArea(s Shape) {
    fmt.Printf("Area: %f\n", s.Area())
}

func main() {
    r := Rectangle{Width: 10, Height: 5}
    c := Circle{Radius: 7}

    printArea(r)
    printArea(c)
}

Interface Segregation Principle (ISP)

Клиенты не должны зависеть от интерфейсов, которые они не используют.

package main

import "fmt"

// Printer определяет интерфейс для печати
type Printer interface {
    Print()
}

// Scanner определяет интерфейс для сканирования
type Scanner interface {
    Scan()
}

// MultiFunctionDevice объединяет интерфейсы Printer и Scanner
type MultiFunctionDevice interface {
    Printer
    Scanner
}

// MultiFunctionPrinter реализует интерфейсы Printer и Scanner
type MultiFunctionPrinter struct{}

func (mfp MultiFunctionPrinter) Print() {
    fmt.Println("Printing document...")
}

func (mfp MultiFunctionPrinter) Scan() {
    fmt.Println("Scanning document...")
}

func main() {
    var device MultiFunctionDevice = MultiFunctionPrinter{}

    device.Print()
    device.Scan()
}

Dependency Inversion Principle (DIP)

Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.

package main

import "fmt"

// NotificationSender определяет интерфейс для отправки уведомлений
type NotificationSender interface {
    SendNotification(user *User, message string)
}

// User хранит информацию о пользователе
type User struct {
    Name  string
    Email string
}

// UserService отвечает за управление пользователями и отправку уведомлений
type UserService struct {
    sender NotificationSender
}

func (us *UserService) CreateUser(name, email string) *User {
    user := &User{Name: name, Email: email}
    us.sender.SendNotification(user, "Welcome to our service!")
    return user
}

// EmailSender реализует отправку уведомлений по email
type EmailSender struct{}

func (es *EmailSender) SendNotification(user *User, message string) {
    fmt.Printf("Sending email to %s: %s\n", user.Email, message)
}

func main() {
    emailSender := &EmailSender{}
    userService := &UserService{sender: emailSender}

    userService.CreateUser("John Doe", "john@example.com")
}

Заключение

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

Что такое DRY?

DRY — это принцип разработки программного обеспечения, который означает "Don't Repeat Yourself" (Не повторяйся). Этот принцип подчеркивает важность устранения повторений в коде, чтобы уменьшить вероятность ошибок и упростить процесс обслуживания программы.

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

Применение принципа DRY помогает:

  • Уменьшить количество кода, что облегчает его понимание и поддержку.

  • Снизить вероятность ошибок, так как изменения в логике требуют корректировки только в одном месте.

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

Принцип DRY часто используется вместе с другими методологиями и практиками разработки, такими как модульное программирование и объектно-ориентированное проектирование, для создания более чистого, эффективного и управляемого кода.

Что такое KISS?

KISS - это принцип программирования, который расшифровывается как "Keep It Simple, Stupid" или "Держи это простым, глупец". Он гласит, что большинство систем работают лучше, если они остаются простыми, а не усложняются[2].

Основные идеи KISS:

  • Избегайте усложнения там, где это не нужно. Простые решения часто оказываются лучшими[2].

  • Не добавляйте функциональность, которая вам пока не нужна (принцип YAGNI - "You Ain't Gonna Need It"). Реализуйте только то, что требуется на данный момент[2].

  • Используйте простые языки программирования, фреймворки и библиотеки, если они подходят для решения задачи. Не усложняйте систему ради использования новых технологий[3].

  • Пишите понятный, читаемый код. Комментируйте сложные места, но не перекомментируйте весь код[2].

  • Используйте единый аналитический движок. Это значительно сокращает код, обслуживание и обновления[3].

Таким образом, KISS призывает к простоте и минимализму в программировании. Сложность должна появляться только тогда, когда это действительно необходимо для решения задачи. Простые решения обычно лучше работают и легче поддерживаются[2][3].

Citations: [1] https://vsenauka.ru/free_books_cache/predskazyemaya_urracuonalnost.pdf [2] https://temofeev.ru/info/articles/podrobnoe-obyasnenie-printsipa-kiss-v-programmnom-obespechenii/ [3] https://se.moevm.info/lib/exe/fetch.php/courses:smart_data:%D0%BB%D0%B5%D0%BA%D1%86%D0%B8%D1%8F-5-big_data_and_data_storages_docx_.pdf [4] https://www.studystack.com/flashcard-3854491 [5] https://freelance.habr.com/freelancers?page=28&q=%5B%D0%BD%D0%BE%D0%B2%D0%B5%D0%B9%D1%88%D0%B8%D0%B5+%D1%82%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8%5D

Что такое YAGNI?

YAGNI - это принцип программирования, который расшифровывается как "You Aren't Gonna Need It" или "Вам это не понадобится". Он гласит, что разработчик не должен реализовывать какую-либо функциональность до тех пор, пока она не понадобится[1][2][3].

Основные идеи YAGNI:

  • Не добавляйте функциональность в код, которая вам пока не нужна. Реализуйте только то, что требуется на данный момент[1][3].

  • Планирование и проектирование должно быть минимальным. Не тратьте время на реализацию того, что может понадобиться в будущем[1][2].

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

  • Худший код - это код, который не используется. Поэтому не пишите ничего лишнего[1].

YAGNI тесно связан с принципом KISS (Keep It Simple, Stupid) - старайтесь писать простой код, избегая преждевременной оптимизации и усложнения[2][4].

Таким образом, YAGNI призывает разработчиков фокусироваться на текущих потребностях, избегать преждевременной реализации функциональности и писать простой, минимально необходимый код[1][3].

Citations: [1] https://habr.com/ru/articles/176813/ [2] https://temofeev.ru/info/articles/podrobnoe-obyasnenie-printsipa-kiss-v-programmnom-obespechenii/ [3] https://skillbox.ru/media/code/eto-klassika-eto-znat-nado-dry-kiss-solid-yagni-i-drugie-poleznye-sokrashcheniya/ [4] https://swiftbook.ru/post/tutorials/the-kiss-principle-in-coding-for-swift-developers/ [5] https://freelance.habr.com/freelancers?page=28&q=%5B%D0%BD%D0%BE%D0%B2%D0%B5%D0%B9%D1%88%D0%B8%D0%B5+%D1%82%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8%5D

Что такое императивный и декларитивный язык программирования?

Императивное и декларативное программирование - это две основные парадигмы программирования, которые определяют стиль написания кода и подход к решению задач.

Императивное программирование:

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

  • Используются переменные, операторы присваивания и составные выражения.

  • Программа похожа на набор приказов, где каждая инструкция выполняется последовательно.

  • Примеры императивных языков программирования: Java, Python, JavaScript, C, C++[2][4].

Декларативное программирование:

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

  • Программы пишутся в виде ограничений и правил, без явного указания последовательности действий.

  • Логические языки, такие как Пролог, позволяют описывать ограничения в виде фактов и правил.

  • Декларативные языки программирования не используют понятия состояния, переменных и операторов присваивания.

  • Примеры декларативных языков: SQL, HTML[3][5].

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

Citations: [1] https://habr.com/ru/companies/timeweb/articles/569422/ [2] https://tproger.ru/experts/imperative-and-declarative-programming [3] https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BB%D0%B0%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5 [4] https://ru.wikipedia.org/wiki/%D0%98%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5 [5] https://tproger.ru/translations/imperative-declarative-programming-concepts

К каким языкам относится GoLang - императивным или к декларативным?

Go является императивным языком программирования[1][3][4].

Императивное программирование - это парадигма, основанная на составлении алгоритма действий (инструкций/команд), которые изменяют состояние (информацию/данные/память) программы[1][4]. В императивных языках, таких как Go, C, C++, Java, программа пишется в виде последовательности инструкций, которые нужно выполнить для достижения результата[1][2].

В отличие от императивных, декларативные языки описывают желаемый результат, не указывая явно, как его получить[1][4]. Примерами декларативных языков являются SQL и HTML[4].

Таким образом, Go, будучи императивным языком, позволяет описывать алгоритмы в виде последовательности команд, изменяющих состояние программы, в отличие от декларативного подхода, ориентированного на описание результата[1][3][4].

Citations: [1] https://folko.gitbook.io/goland/voprosy-sobesedovaniya/bazovye-voprosy-po-golang [2] https://kata.academy/article/voprosy-po-go-na-sobesedovanii [3] https://golangreview.ru/docs/questions-from-reviews-golang/overall-questions/ [4] https://habr.com/ru/articles/658623/ [5] https://ru.wikipedia.org/wiki/Go

Сравни Golang и node.js преимущества, недостатки

Go (Golang) и Node.js — это два популярных инструмента для разработки серверных приложений, каждый из которых имеет свои уникальные преимущества и недостатки. Выбор между ними зависит от конкретных требований проекта, опыта команды и других факторов. Ниже приведено сравнение Go и Node.js по различным аспектам.

Go (Golang)

Преимущества

  1. Высокая производительность:

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

  2. Простота и лаконичность:

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

  3. Встроенная поддержка конкурентности:

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

  4. Статическая типизация:

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

  5. Единая бинарная сборка:

    • Go компилирует все зависимости в один исполняемый файл, что упрощает развертывание и распространение приложений.

Недостатки

  1. Ограниченная стандартная библиотека:

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

  2. Меньше библиотек и фреймворков:

    • Хотя экосистема Go растет, она все еще меньше по сравнению с Node.js, особенно в области веб-разработки.

  3. Отсутствие дженериков:

    • До версии Go 1.18 язык не поддерживал дженерики, что могло усложнять разработку некоторых типов данных и алгоритмов. В версии 1.18 дженерики были добавлены, но их использование все еще может быть ограничено.

Node.js

Преимущества

  1. Асинхронная модель ввода/вывода:

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

  2. Богатая экосистема:

    • Node.js имеет огромную экосистему модулей и пакетов, доступных через npm (Node Package Manager), что упрощает разработку и ускоряет процесс создания приложений.

  3. Единый язык для фронтенда и бэкенда:

    • Использование JavaScript как на клиентской, так и на серверной стороне позволяет разработчикам использовать один язык для всего стека разработки, что упрощает обмен знаниями и кодом между командами.

  4. Высокая скорость разработки:

    • Благодаря богатой экосистеме и асинхронной модели, разработка на Node.js может быть очень быстрой, особенно для прототипирования и создания MVP (Minimum Viable Product).

Недостатки

  1. Однопоточная модель:

    • Node.js работает в однопоточном режиме, что может стать узким местом для задач, требующих интенсивных вычислений. Хотя можно использовать кластеры и воркеры для параллелизма, это добавляет сложности.

  2. Проблемы с обратной совместимостью:

    • Иногда обновления Node.js могут нарушать обратную совместимость, что требует дополнительных усилий для обновления и тестирования приложений.

  3. Отсутствие статической типизации:

    • JavaScript является динамически типизированным языком, что может привести к ошибкам, которые обнаруживаются только на этапе выполнения. Использование TypeScript может смягчить этот недостаток, но это добавляет дополнительный уровень сложности.

  4. Проблемы с производительностью:

    • Хотя Node.js хорошо справляется с задачами ввода/вывода, его производительность может быть ниже по сравнению с языками, компилируемыми в машинный код, такими как Go, особенно для задач, требующих интенсивных вычислений.

Заключение

Выбор между Go и Node.js зависит от конкретных требований вашего проекта:

  • Go: Подходит для высоконагруженных систем, требующих высокой производительности и надежности. Отличный выбор для микросервисов, систем с низкой задержкой и приложений, требующих интенсивных вычислений.

  • Node.js: Подходит для приложений реального времени, таких как чаты и игры, а также для быстрого прототипирования и разработки веб-приложений. Отличный выбор для проектов, где важна скорость разработки и богатая экосистема модулей.

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

CAP - теорема

CAP теорема - это утверждение о том, что распределенная система баз данных может одновременно гарантировать только два из трех свойств[1][2][3][4]:

  1. Согласованность (Consistency)

  • Все клиенты видят одни и те же данные в один и тот же момент времени[2][3]

  • Отсутствие конфликтов при записи данных[4]

  1. Доступность (Availability)

  • Каждый запрос получает непустой ответ[2][3]

  • Система продолжает работать даже при отказе отдельных узлов[4]

  1. Устойчивость к разделению сети (Partition tolerance)

  • Система продолжает работать при сбоях сети[2][3]

  • Данные доступны, даже если часть узлов недоступна[4]

Согласно теореме, в случае сбоя сети в распределенной системе можно гарантировать только два из этих свойств[1][3]:

  • CP (Consistency + Partition tolerance): система сохраняет согласованность, но может стать недоступной при сбоях сети[3][4]

  • AP (Availability + Partition tolerance): система сохраняет доступность, но данные могут быть не согласованы при сбоях сети[3][4]

  • CA (Consistency + Availability): система сохраняет согласованность и доступность, но не устойчива к разделению сети[3][4]

Таким образом, CAP теорема демонстрирует неизбежные компромиссы при проектировании распределенных систем[1][2][3][4]. Разработчики должны выбирать, какие свойства являются наиболее важными для их приложения.

Citations: [1] https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_CAP [2] https://hazelcast.com/glossary/cap-theorem/ [3] https://www.geeksforgeeks.org/the-cap-theorem-in-dbms/ [4] https://www.influxdata.com/glossary/cap-theorem/ [5] https://www.bmc.com/blogs/cap-theorem/

Last updated