DNK_C_C++_Go_Rust
45 subscribers
14 photos
45 links
DNK - дневник кодера С и С++
Download Telegram
Channel name was changed to «DNK_C_C++_Go_Rust»
#496_GO_ODP_Q21

Способы поиска проблем производительности на продакшене? Какие проблемы производительности вы знаете?
Может ли быть так, что потребление ресурсов (CPU, RAM, disk/net bandwidth) вполне умеренное, а пользователи жалуются на «тормоза»?
На что, чаще всего, жалуются пользователи, и как это связано с тем, что вы видите в системе?



Способы поиска проблем производительности на продакшене:

Мониторинг метрик системы — включает сбор и визуализацию различных показателей серверов и приложений:
Load Average — средняя нагрузка на систему.
Использование CPU, памяти (RAM), дисков (I/O), сети (bandwidth).
Количество активных процессов, соединений TCP/IP, очередь запросов базы данных и др.

Инструменты мониторинга:
Prometheus + Grafana, Zabbix, New Relic, Datadog.


Логирование ошибок и предупреждений
— анализ лог-файлов помогает выявить узкие места приложения:
Ошибки HTTP-запросов (5xx, 4xx).
Таймауты запросов (request timeouts).
Исключение исключений внутри сервера.
Медленные SQL-запросы.

Средства анализа логов:
ELK Stack (Elasticsearch+Logstash+Kibana), Graylog, Splunk.


Профилирование нагрузки и трассировка запросовпозволяет измерять производительность отдельных компонентов системы:
Tracing — понимание пути выполнения запросов от клиента до бэкенд-сервисов и обратно.
Трассировка конкретных операций (SQL-запросы, внешние API-вызовы).

Средства профилирования:
Zipkin, Jaeger, Pinpoint, OpenTelemetry.


Анализ зависимостей и взаимодействий сервисовпри распределенной архитектуре важно отслеживать зависимости между сервисами:
Задержки передачи данных между микросервисами.
Пропускная способность каналов связи
(сетевые задержки).
Блокировки транзакций в базах данных.


APM (Application Performance Monitoring) — комплексные решения позволяют мониторить сразу несколько аспектов:
Время отклика страниц/API.
Нагрузка на серверы.
Производительность фронтенда.

Примеры инструментов:
AppDynamics, Dynatrace, Lightstep.


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

High Load Average высокая загрузка процессора из-за чрезмерного количества потоков или блокировок (например, lock contention).
Медленная база данныхнедостаточная индексация таблиц, плохие запросы, нехватка ресурсов БД (slow queries, deadlocks).
Нехватка оперативной памяти — большое количество объектов в памяти (memory leaks), недостаточный объем выделенного ресурса (OOM issues).
Недостаточная пропускная способность сети перегруженность сетевых интерфейсов, низкая скорость чтения/записи файлов.
Высокая задержка I/O (disk I/O latency)низкая производительность жестких дисков или SSD, медленное чтение записей.
Проблемы масштабированиянеправильно настроенная балансировка нагрузки, недостаток реплик, плохо подобранное горизонтальное масштабирование.
Ошибка конфигураций некорректные настройки контейнеров Docker/Kubernetes, неправильные тайминги и лимиты.
Зависимость от внешних сервисов — медленный отклик сторонних API, перегрузка CDN-ресурсов, долгие DNS-разрешения.


Умеренные ресурсы и жалобы пользователей («тормоза»).

Такое возможно! Основные причины:
Latency сетиплохое соединение у конечного пользователя (низкий ping, нестабильная связь). Например, клиент находится далеко географически от сервера, и каждый запрос проходит большое расстояние.
Неподходящие браузеры или устройствапользователь работает на слабых устройствах или устаревших версиях браузера.
JavaScript/CSS/HTML проблемы на стороне фронтаизбыточные DOM-операции, неэффективные JS-скрипты, неоптимизированные CSS-правила замедляют рендеринг страницы.
Асинхронность и ожидание ресурсов — долгие загрузки статики (изображения, стили, скрипты), много асинхронных AJAX-запросов.
Кэширование — отсутствие кэшированных ресурсов на стороне клиента (неправильные заголовки Cache-Control, Expires).
Ожидания на стороне бизнес-логики — внутренняя логика приложения имеет высокие ожидания (long running transactions, bulk operations).

.
Жалобы пользователей и видимые симптомы в системе:

«Страницы долго загружаются»
долгое выполнение PHP-кода, долгая отдача статичных ресурсов, медленные операции на сервере (SQL-запросы, вызовы API).

«Система подвисает при работе»длительные вычисления на фронте (JS-циклы, перерисовка UI), ресурсоемкость браузера, тяжелые JavaScript-библиотеки.

«Приложение медленно откликается»высокая задержка на стороне сети, некорректная настройка кеширования, неправильно выбранные индексы в базе данных.

«Заполнение формы тормозит»сложная обработка форм на стороне клиента (валидация полей, Ajax-подсказки), не оптимальные взаимодействия (slow response times from server-side logic).


Чтобы связать такие жалобы с системой, нужно анализировать журналы запросов, сетевую активность, профилировку исполнения и мониторинг фронтенда.
Например, инструмент Lighthouse (Google Chrome DevTools) покажет конкретные моменты тормозов во фронтенде, а серверные метрики дадут подсказки относительно возможной внутренней блокировки или долгих SQL-запросов.


Таким образом, диагностика должна учитывать одновременно и состояние серверов, и поведение клиентов, позволяя комплексно подходить к решению проблемы.
#497_GO_ODP_Q22

Стандартный набор метрик Prometheus в Go -программе?
Оставив Prometeus в стороне — что вообще Go-программа способна о себе рассказать?
Метрики runtime — что это, и откуда берется?



Стандартный набор метрик Prometheus в Go-программе.

Prometheus предлагает стандартный набор библиотек для сбора метрик в Go-приложениях.

Основные типы метрик:

Counter (Счётчик)используются для подсчёта количества событий (например, число обработанных запросов):
var requestsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of processed HTTP requests.",
})


Gauge (Индикатор)показатель текущего значения переменной величины. Может увеличиваться и уменьшаться (например, свободная память):
var freeMemoryBytes = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "free_memory_bytes",
Help: "Amount of available memory in bytes.",
})


Histogram (Гистограмма)используется для измерения распределения значений, таких как продолжительность обработки запросов:
var requestDurationSeconds = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration distribution.",
Buckets: []float64{.005, .01, .025, .05, .1, .25},
},
[]string{"method"},
)


Summary (Краткое описание)обобщённая статистика (среднее значение, медиана, квантили) распределения значений:
var requestLatencyMilliseconds = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "http_request_latency_milliseconds",
Help: "Request processing time summary statistics.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, // percentiles
},
[]string{"endpoint"},
)


Пример стандартного набора метрик для веб-приложения на Go:
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
requestsTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of processed HTTP requests.",
})
requestDurationSeconds = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration histogram.",
Buckets: prometheus.LinearBuckets(0.001, 0.001, 10),
}, []string{"path"})
)

func handler(w http.ResponseWriter, r *http.Request) {
defer func() { requestsTotal.Inc() }()
timer := prometheus.NewTimer(requestDurationSeconds.WithLabelValues(r.URL.Path))
defer timer.ObserveDuration()
w.Write([]byte("Hello World"))
}

func main() {
http.HandleFunc("/", handler)
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}


.
Что вообще Go-программа способна о себе рассказать?

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


Основные категории информации, которую можно собрать из программы на Go:

Метрики среды выполнения (runtime)эти данные предоставляются стандартным пакетом runtime.
Памятьобщий объём используемой памяти, выделенный heap-size, числа объектов GC, stack trace и размеры сегментов.
Процессориспользование процессорного времени, частота переключений контекста.
Сборщик мусора (GC)информация о количестве циклов сборки мусора, объёмах освобождаемой памяти, интервалах запуска.
Threads/Goroutinesобщее количество горутин, потоки выполнения, режим параллелизма.
Heapструктура куч, распределение объектов по типам и состояниям (allocated vs freed objects).

Пример простого вывода метрик среды выполнения:
package main
import (
"fmt"
"log"
"runtime"
)

func main() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc: %v MiB\n", bToMb(m.Alloc)) // Выделено памяти
fmt.Printf("Sys: %v MiB\n", bToMb(m.Sys)) // Общая система памяти
fmt.Printf("NumGC: %d\n", m.NumGC) // Число запусков GC
fmt.Printf("PauseTotalNs: %v ms\n", nsToMs(m.PauseTotalNs)) // Всего пауза GC
log.Println(runtime.NumGoroutine()) // Текущие активные goroutines
}

// Вспомогательные функции преобразования байтов и наносекунд
func bToMb(b uint64) float64 { return float64(b) / 1024 / 1024 }
func nsToMs(ns int64) float64 { return float64(ns) / 1_000_000 }



Другие важные показатели.

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


Откуда берутся метрики runtime?

Метрики среды выполнения собираются автоматически самим Go-интерпретатором и виртуальной машиной.
Эти данные доступны через пакет runtime, предоставляющий доступ к внутренним структурам Go-процесса:

Пакет runtime отслеживает изменения в структуре выполнения, включая работу сборщика мусора, управление памятью и работой планировщика.
Периодические события синхронизации происходят каждые несколько миллисекунд (по умолчанию примерно раз в 10 мс), собирая актуальные сведения о состоянии приложения.
Интерфейс runtime.MemStats предоставляет статистику памяти и другие характеристики среды выполнения.

Методы типа runtime.ReadMemStats() или runtime.NumGoroutine() вызывают внутренний механизм записи текущих состояний и возвращают собранную информацию приложению.


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

Как встроить стандартный профайлер в приложение на Go?
A нужен ли профайлер?
Что там есть вообще полезного?
Почему его нужно встраивать, а не запускать из-под него приложение?


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


Go поставляется со стандартной библиотекой профилировщика (net/http/pprof), позволяющей легко интегрировать профилирование прямо в приложение. Она поддерживает сбор статистики по CPU, памяти, блокировкам и другим метрикам.


Почему встроенный профайлер лучше отдельного запуска приложения под профайлером?
Удобство интеграции — не придется изменять способ запуска приложения каждый раз, когда необходимо посмотреть производительность.
Автоматическое получение результатовзапустив приложение с профайлером, можно получать данные удаленно и удобно анализировать их.
Минимальное влияние на продуктивную среду если профайлинг отключён в production, можно включить его временно, не останавливая работу системы.


Как встроить профайлер в приложение на Go?
Рассмотрим простое веб-приложение на Go, которое расширим возможностью профилирования.

Шаг 1: Подключение HTTP-профайлерачтобы добавить поддержку профайлинга, достаточно зарегистрировать обработчики маршрутов /debug/pprof следующим образом:
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
})
log.Println("Starting server at :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}


При таком подходе доступ к маршруту /debug/pprof позволит увидеть стандартные страницы профилирования Go, включая статистику по процессору, памяти и горутинам.

Шаг 2: Сбор профилей вручнуютакже можно собрать профили вручную, используя API пакета runtime/pprof :
// Запись профиля ЦПУ
package main
import (
"log"
"os"
"runtime/pprof"
"time"
)

func main() {
f, err := os.Create("cpu.pprof")
if err != nil {
log.Fatal(err)
}
defer f.Close()

// Начинаем запись профиля ЦПУ
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()

time.Sleep(time.Second * 10) // Моделируем длительную операцию
}


Пример создаст файл cpu.pprof, содержащий профиль потребления CPU приложением за 10 секунд.


Стандартный профайлер Go предоставляет следующие типы профилей:
Профиль CPU (/debug/pprof/profile)позволяет видеть, какие функции тратят больше всего времени процессора.
Профиль памяти (/debug/pprof/heap)показывает распределение памяти между функциями, помогает обнаружить утечки памяти.
Горутинный стек (/debug/pprof/goroutine)демонстрирует количество активных горутин и их состояние.
Аллокатор блоков памяти (/debug/pprof/block)помогает диагностировать блокировки потоков ожидания выделения памяти.
Блокировки каналов синхронизации (/debug/pprof/threadcreate)анализирует создание новых потоков ОС, вызванное конкурентностью внутри приложения.


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


Использование встроенного профайлера значительно упрощает диагностику проблем производительности и повышает удобство эксплуатации ваших приложений на Go.
#499_GO_ODP_Q24

Overhead от стандартного профайлера?
Что такое «семплирующий профайлер» и почему это хорошо?



Использование встроенных инструментов профилирования, предоставляемых пакетом net/http/pprof, практически не оказывает значительного негативного влияния на производительность приложения.
Это связано с особенностями реализации профайлеров в Go:

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

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

Тем не менее, рекомендуется выключать профайлер в productive среде, если он не нужен постоянно. Хотя overhead низкий, лишние расходы ресурсов всё же присутствуют.


Семплирующие профайлеры и их преимущества.
Семплирующими называются профайлеры, которые собирают данные не непрерывно, а периодическими выборками ("samples").
Они измеряют потребление ресурсов лишь в определённые моменты времени, а не каждую инструкцию.

Преимущества семплирующих профайлеров:
Низкое влияние на производительностьтак как информация собирается не постоянно, а эпизодически, нагрузка на систему существенно ниже.
Достаточная точностьнесмотря на выборочный характер измерений, собранных данных обычно хватает для выявления узких мест в приложении.
Простота использованияпростое включение профайлера даёт почти мгновенную обратную связь относительно текущего состояния приложения.
Масштабируемостьподходит для мониторинга больших распределённых систем, поскольку требования к ресурсам остаются низкими.

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


Профайлер особенно эффективен в следующих ситуациях:
Выяснение причины медленной работы отдельных функций или операций.
Поиск утечек памяти или чрезмерного расхода ресурсов.
Определение неэффективных алгоритмов или структур данных.
Мониторинг активности большого числа параллельных процессов
(горутин).



Встроенный профайлер Go сочетает простоту использования, высокую эффективность и минимальное воздействие на общую производительность приложения.
#500_GO_ODP_Q25

Почему в Go используется встраивание (композиция), а не наследование?
Что означает буква L в аббревиатуре SOLID?



Композиция (также известная как встраивание типов) является основным способом повторного использования поведения и организации структуры объектов в Go.


Причины отказа от традиционного наследования классов в Go:

Простота и ясность дизайнаGo стремится минимизировать сложность и избыточность, делая акцент на простой и понятной структуре кода.
Наследование может приводить к запутанным иерархиям классов, сложностям в поддержке и тестировании.
Гибкость и композиция интерфейсоввместо строгого дерева наследования, композиция позволяет свободно комбинировать различные функциональные элементы и создавать новые объекты путем объединения существующих компонентов.
Этот подход соответствует принципам проектирования Go, где предпочтение отдаётся легкости изменения и расширения функциональности без усложнения базовой архитектуры.
Уменьшение сложности зависимостейкомпозиция уменьшает вероятность возникновения ошибок, связанных с конфликтующими методами и свойствами, которые возникают при множественном наследовании.
Гораздо проще управлять поведением объекта, состоящего из независимых элементов, чем объектом, зависящим от множества предков.
Совместимость с принципами программирования на Goструктуры и интерфейсы в Go специально разработаны для лёгкого взаимодействия друг с другом посредством композиции, что делает этот подход естественным и эффективным.

Пример композиции в Go:
type Animal struct {
Name string
Sound string
}

type Dog struct {
Animal
Breed string
}

func NewDog(name, breed string) *Dog {
return &Dog{
Animal: Animal{Name: name, Sound: "Woof"},
Breed: breed,
}
}

func main() {
dog := NewDog("Rex", "Labrador")
fmt.Printf("%s is a %s and says %s\n", dog.Name, dog.Breed, dog.Sound)
}


Cтруктура Dog включает структуру Animal, что позволяет использовать её поля и методы непосредственно.


SOLID — набор принципов проектирования программного обеспечения, предложенных Робертом Мартином («Uncle Bob»).

S: Single Responsibility Principle (Принцип единственной ответственности)
O: Open/Closed Principle (Принцип открытости-закрытости)
L: Liskov Substitution Principle (Принцип замещения Барбары Лисков)
I: Interface Segregation Principle (Принцип разделения интерфейса)
D: Dependency Inversion Principle (Принцип инверсии зависимости)


Liskov Substitution Principle (Принцип замещения Барбары Лисков) гласит, что подклассы должны быть взаимозаменяемыми с базовыми классами.
То есть, если есть тип T, и cоздаётся производный тип S, то любой экземпляр типа S должен быть допустимым аргументом везде, где ожидается аргумент типа T.

Формулировка принципа была дана Барбарой Лисков:
Функциональные спецификации дочерних классов не должны противоречить контрактам родительских классов.

Пример нарушения принципа:
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height

@property
def area(self):
return self._width * self._height

class Square(Rectangle):
def __init__(self, side_length):
super().__init__(side_length, side_length)

# Нарушение контракта родителя, изменение ширины меняет высоту
def set_width(self, value):
self._width = value
self._height = value


Квадрат нарушает контракт прямоугольника, потому что метод set_width() одновременно устанавливает ширину и высоту, что недопустимо для прямоугольников общего вида.

Правильный подход состоит в том, чтобы избежать ситуаций, когда подкласс ведёт себя иначе, чем ожидалось бы от суперкласса.
#501_GO_ODP_Q26

Какие средства обобщенного программирования есть в Go?
Что такое map-reduce? Как его реализовать в Go? А без interface{}?
Что такое кодогенерация и как ее можно использовать в этой задаче?



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

Инструменты обобщённого программирования в Go:
Интерфейсыописывают поведение, а не конкретные типы данных.
Любой тип, удовлетворяющий интерфейсному контракту, может использоваться в качестве параметра метода или возвращаемого значения.
Джентики (Generics)в Go 1.18, появились дженерики, позволяющие объявлять универсальные функции и структуры, работающие с любыми типами данных, соответствующими заданным ограничениям:
// Пример интерфейса Sorter
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}

// Реализация сортировки массива чисел
type IntArray []int

func (a IntArray) Len() int { return len(a) }
func (a IntArray) Less(i, j int) bool { return a[i] < a[j] }
func (a IntArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// Обобщённая функция сортировки
func Sort(data Sorter) {
for i := range data {
for j := i + 1; j < data.Len(); j++ {
if data.Less(j, i) {
data.Swap(i, j)
}
}
}
}

// Пример с использованием дженериков (начиная с Go 1.18)
func Min[T any](values ...T) T {
var min T
first := true
for _, v := range values {
if first || less(v, min) {
min = v
first = false
}
}
return min
}

func less[T comparable](a, b T) bool {
return a < b
}



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

Процесс разделяется на две фазы:

Map-фазаисходные данные разбиваются на отдельные фрагменты, обрабатываемые параллельно.
Reduce-фазарезультаты обработки объединяются в единый итоговый результат.

Пример реализации MapReduce в Go:
Допустим, нужно посчитать сумму всех значений в большом массиве чисел:

package main
import (
"fmt"
"sync"
)

// Функция мапа, применяемая к каждому элементу
func mapper(chunks [][]int) chan int {
outCh := make(chan int)
go func() {
for _, chunk := range chunks {
sum := 0
for _, val := range chunk {
sum += val
}
outCh <- sum
}
close(outCh)
}()
return outCh
}

// Функция reduce, собирающая промежуточные суммы
func reducer(sumChans []chan int) int {
totalSum := 0
wg := sync.WaitGroup{}
for _, ch := range sumChans {
wg.Add(1)
go func(c chan int) {
for s := range c {
totalSum += s
}
wg.Done()
}(ch)
}
wg.Wait()
return totalSum
}

func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
chunkSize := 2
chunks := make([][]int, 0)

// Разбиение исходных данных на чанки
for i := 0; i < len(numbers); i += chunkSize {
end := i + chunkSize
if end > len(numbers) {
end = len(numbers)
}
chunks = append(chunks, numbers[i:end])
}

// Создаем каналы для каждой порции данных
mapChannels := make([]chan int, len(chunks))
for i := range chunks {
mapChannels[i] = mapper([][]int{chunks[i]})
}

// Собираем итоги
result := reducer(mapChannels)
fmt.Println(result) // Выведет 55
}


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

.
Кодогенерация в Go.

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


Инструменты для кодогенерации в Go включают:
go generateкоманда компилятора Go, позволяющая запускать скрипты для автоматической генерации кода.
Templating toolsбиблиотеки вроде text/template и html/template, позволяющие динамически формировать тексты или HTML-контент.

Пример простого генератора интерфейсов:
//gen_interface.go
//+build ignore
package main
import (
"fmt"
"go/types"
"strings"
)

const template = `
type MyInterface interface {
Get%s() %s
Set%s(%s)
}`

func main() {
types := []string{"Int", "String"}
for _, t := range types {
getter := fmt.Sprintf(template, strings.Title(t), t, strings.Title(t), t)
fmt.Println(getter)
}
}


Команда go generate выполнит скрипт и сгенерирует нужный интерфейс.


Обобщенное программирование в Go реализуется через интерфейсы и дженерики.
Парадигма MapReduce полезна для параллельной обработки больших объёмов данных.
Кодогенерация позволяет упростить рутинные задачи и ускорить разработку сложных систем.
#502_GO_ODP_Q27

Какие технологические преимущества есть у языка Go?
Для каких задач Go подходит идеально?
Для каких задач Go не очень подходит, но может быть полезен?
Чем отличается goroutine от OS thread?
Как устроен сетевой ввод-вывод в Go?



Golang
разработан Google с целью решения ряда практических проблем, возникающих при разработке крупных проектов.

Основные технические достоинства Go:
Высокая производительность и компактность двоичных файловкомпиляция в нативный машинный код обеспечивает высокую скорость исполнения.
Минималистичный дизайн библиотеки стандартных пакетов способствует созданию небольших исполняемых файлов.
Эффективная работа с многопоточностью — goroutines предоставляют эффективный механизм асинхронного выполнения задач, позволяя обрабатывать тысячи одновременных запросов с минимальным расходом ресурсов.
Встроенная поддержка каналов (channels) упрощает взаимодействие между горутинами и обмен данными.
Сборка мусора (GC)автоматическая очистка неиспользуемых объектов снижает нагрузку на разработчика и улучшает устойчивость приложений.
Сборщик мусора оптимизирован для высокой пропускной способности и низкой задержки.
Статическая типизация с простым синтаксисомтипы проверяются на этапе компиляции, снижая риск ошибок во время выполнения.
Легко читаемый и лаконичный синтаксис облегчает понимание и сопровождение кода.
Приятная экосистема и стандарты качествабогатая стандартная библиотека покрывает большинство базовых потребностей (сетевые протоколы, базы данных, криптография и т.п.).
Инструментарий Go (go fmt, go vet, go test) поощряет аккуратный стиль написания кода и дисциплину тестирования.
Кросс-компиляциявозможность сборки бинарных файлов для различных платформ и архитектур (Windows, Linux, macOS, ARM и т.д.) одним нажатием кнопки.
Простота изучения и поддержки проектачеткая философия
"Less is more"меньше ключевых слов, минимум магии, отсутствие перегрузки операторов.
Отсутствие сложной объектно-ориентированной модели упрощает архитектуру и делает проекты легко поддерживаемыми.


Go отлично подходит для следующего набора задач:

Разработка серверных приложений и микросервисоввысокая производительность и простота развертывания делают Go идеальным выбором для backend-разработки.
Поддерживает RESTful API, RPC-сервисы, webhooks и интеграционные сервисы.

Работа с большими объемами данных и вычислительные задачиблагодаря эффективной работе с памятью и быстрым goroutines, Go прекрасно справляется с задачами обработки больших объемов данных.

Инфраструктура и DevOps-инструментытакие утилиты, как Docker, Kubernetes, Terraform, изначально написаны на Go благодаря его эффективности и удобству кросс-компиляции.

API-серверы и шлюзыGo способен быстро обрабатывать запросы и эффективно взаимодействовать с внешними сервисами.

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


Хотя Go широко используется, существуют ситуации, когда его особенности становятся недостатками:

Численные вычисления (HPC)приложения, требующие экстремальных математических расчетов (научные исследования, физика частиц, симуляции), часто выигрывают от специализированных языков (Python, Julia, Fortran).

Интерактивные графические интерфейсы (GUI)платформы GUI вроде Qt, Electron или JavaFX поддерживают языки с богатыми фреймворками (C++, Python, JavaScript). Графические интерфейсы на чистом Go возможны, но требуют дополнительной инфраструктуры.

Параллелизм на уровне железа (GPU) — gorutines ориентированы на CPU, тогда как GPU-вычисления требуют низкоуровневых технологий (OpenCL, CUDA), поддерживаемых преимущественно на Си и C++.

Однако в перечисленных сценариях Go может играть вспомогательную роль (например, обработка сетей и логика сервера для HPC-проектов, контроль логики приложений на стороне клиента в GUI-проектах).
Отличия goroutines от OS threads.

Threads (потоки ОС) создаются и управляются ОС.
Каждому потоку выделяется кусок оперативной памяти, и стоимость создания потока высока.
Goroutinesлегкие потоки управления, создаваемые языком Go.
Их исполнение мультиплексируется поверх небольшого количества настоящих потоков ОС, что сильно экономит ресурсы и позволяет запускать десятки тысяч gorutines.

Стоимость запуска:
Создание потока ОС требует сотни килобайт памяти и занимает значительное время.
Горутины инициализируются с небольшим стеком (~2KB) и начинают выполняться мгновенно.

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

Контекст переключений:
Переключение контекста между потоками ОС дорого обходится (сохранение регистров, обновление кэшей и т.д.).
Switching context в рамках горутин осуществляется намного эффективнее, так как они сами организуют собственную очередь выполнения.


Организация сетевого ввода-вывода в Go.
Сетевое взаимодействие в Go построено вокруг концепций неблокирующего ввода-вывода и синхронного/асинхронного выполнения операций:
Неблокирующий ввод-вывод (non-blocking I/O) операции чтения и записи сети выполняются асинхронно через механизм неблокирующих сокетов, предоставляя эффективный механизм обработки множества соединений.
Синхронизация с помощью каналов (channels) — каналы обеспечивают удобный способ обмена данными между горутинами и синхронизируют доступ к общим ресурсам.
Модель событийной петли (event loop) внутренний диспетчер (scheduler) управляет выполнением горутин и планирует выполнение операций в очередях событий.
Шаблоны буферизации и мультиплексинга — пакеты net/http, bufio, tcp, udp предоставляют удобные способы работы с соединениями и пакетами данных.

Типичная схема сетевого взаимодействия выглядит примерно так:
listener, _ := net.Listen("tcp", ":8080") // слушаем входящие соединения
for {
conn, _ := listener.Accept() // принимаем новое соединение
go handleConnection(conn) // отправляем в отдельную горутину
}

func handleConnection(conn net.Conn) {
buf := bufio.NewReader(conn) // создаем буферированный reader
for {
line, err := buf.ReadBytes('\n') // читаем строку из соединения
if err != nil {
break
}
processData(line) // обрабатываем полученные данные
}
}


Такой подход позволяет создать эффективные, высокопроизводительные серверы, способные обслуживать большое количество клиентов одновременно.
#503_GO_ODP_Q28

Какие технологические недостатки есть у языка Go?
Для каких задач были бы полезны дженерики?
Как превратить []io.ReadWriter в []io.Reader?



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

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

Отсутствие перегрузки методов и операторов разработчик вынужден использовать разные имена для методов с одинаковой сигнатурой, что увеличивает объем кода и ухудшает читаемость.
Нет возможности переопределять операторы (+, -, *, /), что затрудняет реализацию высокооптимизированных математических моделей.

Недостаточность рефлексии (reflection) отражение в Go ограничено возможностями статической проверки типов, что препятствует глубокому использованию мета-данных и динамических возможностей, присутствующих в других языках (например, Python, Ruby).

Система памяти (memory model) концептуальная простота работы с памятью скрывает детали оптимизации, такие как pinning и garbage collector, что может привести к неоправданным потерям производительности в критичных приложениях.

Фиксированная структура пакета (vendor directory) в ранних версиях Go возникали трудности с управлением зависимостями, хотя ситуация улучшилась с появлением модуля управления зависимостями (Go modules).

Неполноценная поддержка макросов и AOP (Aspect-Oriented Programming) макросы отсутствуют в Go, что ограничивает применение техники аспектно-ориентированного программирования (AOP), удобной для внедрения сквозных механизмов логирования, безопасности и транзакционности.


Эти аспекты показывают, что Go не является панацеей и не решает абсолютно все задачи одинаково эффективно.


Где полезны дженерики (generics)?

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

Коллекции и контейнеры:
Дженерики облегчают написание списков, карт, деревьев и прочих структур данных, работающих с произвольными типами.
Алгоритмы обработки данных:
Алгоритмы сортировки, фильтрации, отображения данных (map/reduce) работают с несколькими типами данных, позволяя повторно использовать тот же самый код.
Интерфейсы и классы с общими операциями:
Можно создать обобщённые интерфейсы и абстрактные классы, которые используются разными типами данных.

До Go 1.18 приходилось либо копипастить код, либо применять рефлексию и преобразование типов через interface{}, что увеличивало накладные расходы и уменьшало безопасность.

Теперь дженерики решают проблему повторения однотипного кода, обеспечивая большую гибкость и улучшая качество кода.
Превращение []io.ReadWriter в []io.Reader

Часто возникает необходимость конвертации срезов интерфейсов одного типа в другой. Рассмотрим конкретный пример преобразования среза интерфейсов ReadWriter в срез интерфейсов Reader:
package main
import (
"io"
"fmt"
)

func convertToReaders(rw []io.ReadWriter) []io.Reader {
readers := make([]io.Reader, len(rw)) // Создаем новый срез нужного размера
for i, rwItem := range rw {
readers[i] = io.Reader(rwItem) // Присваиваем интерфейс Reader каждому элементу
}
return readers
}

func main() {
rwSlice := []io.ReadWriter{ /* заполните необходимыми объектами */ }
readerSlice := convertToReaders(rwSlice)
fmt.Println(readerSlice)
}


Объект io.ReadWriter реализует оба интерфейса: Reader и Writer.
Поскольку Reader является частью ReadWriter, его можно безопасно кастовать обратно к типу Reader, не теряя функциональность.

Такая техника конверсии полезна, когда нужно применить общие операции чтения данных к объектам, обладающим дополнительными возможностями (запись данных).
#504_Cpp_IdPt

Рекомендации С.Мэйерса из книги (издательство ДМК 2006 г):
"Эффективное использование С++. 55 верных советов улучшить структуру и код ваших программ".


1. Относитесь к С++ как к конгломерату языков.
Чтобы увидеть смысл в С++, нужно распознавать его основные подъязыки:
— С. В глубине своей С++ все еще основан на С.
— Объектно-ориентированный С++.
— С++ с шаблонами.
— STL.

CЛЕДУЕТ ПОМНИТЬ:

— Правила эффективного программирования меняются в зависимости от части С++, которую вы используете.



2. Предпочитайте const, enum и inline использованию
#define.
CЛЕДУЕТ ПОМНИТЬ:

— Директиве #define следует предпочесть константные объекты и перечисления (enum).

— Вместо имитирующих функции макросов, определенных через #define, лучше применять встроенные функции (inline).



3. Везде, где только можно, используйте const.
CЛЕДУЕТ ПОМНИТЬ:
— Объявление чего-либо с модификатором const помогает компиляторам обнаруживать ошибки.
const можно использовать с объектами в любой области действия, с параметрами функций и возвращаемых значений, а также с функциями-членами в целом.

— Компиляторы проверяют побитовую константность, но вы должны программировать, применяя логическую константность.

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



4. Прежде чем использовать объекты, убедитесь, что они инициализированы.

CЛЕДУЕТ ПОМНИТЬ:
— Всегда вручную инициализируйте объекты встроенных типов, по
скольку С++ делает это не всегда.

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

— Избегайте проблем с порядком инициализации в разных единицах трансляции, заменяя нелокальные статические объекты локальными статическими объектами.



5. Какие функции С++ создает и вызывает молча?
CЛЕДУЕТ ПОМНИТЬ:
— Компилятор может неявно генерировать для класса конструктор по умолчанию, конструктор копирования, оператор присваивания и деструктор.



6. Явно запрещайте компилятору генерировать функции, которые вам не нужны.
CЛЕДУЕТ ПОМНИТЬ:

— Чтобы отключить функциональность, автоматически предоставляемую компилятором, объявите соответствующую функцию-член закрытой и не включайте ее реализацию. 
Наследование базовому классу типа UncopyaЬle - один из способов сделать это.
— Начиная с версии С++11 конструктор копирования и оператор присваивания можно исключить с помощью слова delete



7. Объявляйте деструкторы виртуальными в полиморфном базовом классе.
CЛЕДУЕТ ПОМНИТЬ:
— Полиморфные базовые классы должны объявлять виртуальные деструкторы.
Если класс имеет хотя бы одну виртуальную функцию, он должен иметь виртуальный деструктор.

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



8. Не позволяйте исключениям покидать деструкторы.
CЛЕДУЕТ ПОМНИТЬ:
— Деструкторы никогда не должны возбуждать исключений.
Если функция, вызываемая в деструкторе, может это сделать, то деструктор обязан перехватывать все исключения, а затем "проглатывать" их либо прерывать программу.

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



9. Никогда не вызывайте виртуальные функции в конструкторе
или деструкторе.

CЛЕДУЕТ ПОМНИТЬ:
— Не вызывайте виртуальные функции во время работы конструкторов и деструкторов, потому что такие вызовы никогда не дойдут до производных классов, расположенных в иерархии наследования ниже того, который сейчас конструируется или уничтожается.



10. Операторы присваивания должны возвращать ссылку на *this.
CЛЕДУЕТ ПОМНИТЬ:
— Пишите операторы присваивания так, чтобы они возвращали ссылку на *this.


.
11. В operator= осуществляйте проверку на присваивание самому себе.
CЛЕДУЕТ ПОМНИТЬ:
— Убедитесь, что operator= правильно ведет себя, когда объект присваивается самому себе. Для этого можно сравнить адреса исходного и целевого объектов, аккуратно упорядочить предложения или применить идиому копирования обменом.

— Убедитесь, что все функции, оперирующие более чем одним объектом, ведут себя корректно при совпадении двух или более объектов.



12. Копируйте все части объекта.
CЛЕДУЕТ ПОМНИТЬ:
— Копирующие функции должны гарантировать копирование всех членов-данных объекта и частей его базовых классов.

— Не пытайтесь реализовать одну из копирующих функций в терминах другой. Вместо этого поместите общую функциональность в третью функцию, которую вызовут обе.



13. Используйте объекты для управления ресурсами.

CЛЕДУЕТ ПОМНИТЬ:
— Чтобы предотвратить утечку ресурсов, используйте объекты RAII, которые захватывают ресурсы в своих конструкторах и освобождают в деструкторах.

— Два часто используемых класса RAII - это tr1::shared_ptr и auto_ptr.
Обычно лучше остановить выбор на классе tr1::shared_ptr, потому что его поведение при копировании соответствует интуитивным ожиданиям.
Что касается auto_ptr, то после копирования он уже не указывает ни на какой объект.



14. Тщательно продумывайте поведение при копировании классов,
управляющих ресурсами.

CЛЕДУЕТ ПОМНИТЬ:
— Копирование RАII-объектов влечет за собой копирование ресурсов, которыми они управляют, поэтому поведение ресурса при копировании определяет поведение RАII-объекта.

— Обычно при реализации RАII-классов применяется одна из двух схем: запрет копирования или подсчет ссылок, но возможны и другие варианты.



15. Предоставляйте доступ к самим ресурсам из управляющих
ими классов.

CЛЕДУЕТ ПОМНИТЬ:
— Программные интерфейсы (API) часто требуют прямого обращения к ресурсам. Именно поэтому каждый RAII-клacc должен предоставлять возможность получения доступа к ресурсу, которым он управляет.

— Доступ может быть обеспечен посредством явного либо неявного преобразования. Вообще говоря, явное преобразование безопаснее, но не явное более удобно для пользователей.



16. Используйте одинаковые формы new и delete.
CЛЕДУЕТ ПОМНИТЬ:
— Если вы используете [] в выражении new, то должны применять [] и в соответствующем выражении delete. Если вы не используете квадратные скобки [] в выражении new, то не должны использовать их и в соответствующем выражении delete.



17. Помещение в "интеллектуальный" указатель объекта, выделенного с помощью new, лучше располагать в отдельном предложении.

CЛЕДУЕТ ПОМНИТЬ:
— Помещайте объекты, выделенные оператором new, в "интеллектуальные" указатели в отдельном предложении.
В противном случае такие вызовы могут привести к утечкам ресурсов, если возникнет исключение.



18. Проектируйте интерфейсы так, чтобы их легко было использовать правильно и трудно - неправильно.

CЛЕДУЕТ ПОМНИТЬ:
— Хорошие интерфейсы легко использовать правильно и трудно использовать неправильно. 
Нужно стремиться обеспечить эти характеристики в интерфейсах.

— Для обеспечения корректного использования интерфейсы должны быть согласованы и совместимы со встроенными типами.

— Для предотвращения ошибок применяют следующие способы:
- создание новых типов;
- ограничение допустимых операций над этими типами;
- ограничение допустимых значений;
- освобождение пользователя от обязанностей по управлению ресурсами.

— Класс tr1::shared_ptr поддерживает пользовательские функции-чистильщики. Это снимает "проблему нескольких DLL" и может быть использовано для автоматического освобождения мьютекса (см. правило 14).


.
19. Рассматривайте проектирование класса как проектирование типа.

Проектирование почти любого класса ставит перед разработчиком вопросы, ответы на которые часто ограничивают спектр возможных решений:

Как должны создаваться и уничтожаться объекты нового типа?
От ответа на этот вопрос зависит дизайн конструкторов и деструкторов, а равно функций распределения и освобождения памяти (оператор new, оператор пеw[], оператор delete и оператор delete[]), если вы собираетесь их переопределить.

Чем должна отличаться инициализация объекта от присваивания значе ний?
Ответ на этот вопрос определяет разницу в поведении между конструкторами и операторами присваивания.
Важно не путать инициализацию с присваиванием, потому что им соответствуют разные вызовы функций (см. правило 4).


Что означает для объектов нового типа быть переданными по значению?
Помните, что конструктор копирования определяет реализацию передачи по значению для данного типа.

Каковы ограничения на допустимые значения вашего нового типа?
Обычно только некоторые комбинации значений данных-членов класса являются правильными.
Эти комбинации определяют
инварианты, которые должен поддерживать класс.
А инварианты уже диктуют, как следует контролировать ошибки в функциях-членах, в особенности в конструкторах, операторах присваивания и функциях установки значений
("setter" functions).
Могут быть также затронуты исключения, которые возбуждают ваши функции, и спецификации этих исключений.

Укладьmается ли ваш новыii тип в граф наследования?
Наследуя классы от других, нужно следовать ограничениям, налагаемым базовыми классами.
В частности, нужно учитывать, как объявлены в них функции-члены: виртуальньши или нет
(см. правила 34 и 36).
Если необходимо, чтобы класс могли наследовать другие, то нужно тщательно продумать, какие функции объявить виртуальны.ми; в особенности это относится к деструктору (правило 7).

Какие варианты преобразования типов допустимы для нового типа?
Новый тип существует в море других типов, поэтому должны ли быть предусмотрены варианты преобразования между проектируемым типом и другими?
Если необходимо разрешить неявное преобразование объекта типа Т1 в объект типа Т2, придется либо написать функцию преобразования в классе Т1 (то есть орегаtог Т2), либо неявный конструктор в классе Т2, который может быть вызван с единственным аргументом.
Если же необходимо разрешить только явные преобразования, то нужно будет написать специальные функции, но ни в коем случае не делать их операторами преобразования или не-explicit конструкторами с одним аргументом.

Какие операторы и функции имеют смысл для нового тнпа?
Ответ на этот вопрос определяет набор функций, которые необходимо объявить в новом классе. Некоторые из них будут функциями-членами, другие - нет (см. правила 23, 24 и 46).

Какие стандартные функции должны стать недоступными?
Их нужно будет объявить, закрытыми (правило 6).

Кто должен получить доступ к членам вашего нового типа?
Ответ на этот вопрос помогает определить, какие члены должны быть открытыми (public), какие — защищенными (protected) и какие — закрытыми (private).
Также преlстоит решить, какие классы и/или функции должны быть
друзьями класса, а также когда имеет смысл вложить один класс внутрь другого.


Что такое "необъявленный иmерфейс" нового типа?
Какого рода гарантии мoryт быть предоставлены относительно производительности, безопасности относительно исключений (см. правило 29) и использования ресурсов (например, блокировок и динамической памяти)?
Такого рода гарантии определяют ограничения на реализацию нового класса.

Насколько общий новый тип?
Возможно, в действительности не нужно определять новый тип.
Возможно, необходимо определить целое семейство типов. Если это так, то нужно определять не новый класс, а новый шаблон классa.
Действительно ли новый тип представляет собой то, что нужно?
Если определение нового производного класса только расширяет функциональность существующего класса, возможно, этой цели
лучше достичь простым определением одной или более
функций-нечленов либо шаблонов.

CЛЕДУЕТ ПОМНИТЬ:

— Проектирование класса - это проектирование типа.
Прежде чем определять новый тип, убедитесь, что рассмотрены все вопросы, которые обсуждаются в настоящем правиле.



20. Предпочитайте передачу по ссылке на const передаче по значению.

CЛЕДУЕТ ПОМНИТЬ:
— Передаче по значению предпочитайте передачу по ссылке на константу.
Обычно это более эффективно и позволяет избежать проблемы "срезки".

— Это правило не касается встроенных типов, итераторов и функциональных объектов STL.
Для них передача по значению обычно подходит больше.



21. Не пытайтесь вернуть ссылку, когда должны вернуть объект.

CЛЕДУЕТ ПОМНИТЬ:
— Никогда не возвращайте указатель или ссылку на локальный объект, ссылку на объект, распределенный в "куче", либо указатель или ссылку на локальный статический объект, если есть шанс, что понадобится более, чем один экземпляр такого объекта. (в правиле 4 приведен пример ситуации, когда возврат ссылки на локальный статический объект имеет смысл, по крайней мере, в однопоточных средах).



22. Объявляйте данные-члены закрытыми.

CЛЕДУЕТ ПОМНИТЬ:
— Объявляйте данные-члены закрытыми (private).
Это дает клиентам синтаксически однородный доступ к данным, обеспечивает возможность тонкого управления доступом, позволяет гарантировать инвариантность и предоставляет авторам рсализации классов гибкость.

— Защищенные члены не более инкапсулированы, чем открытые.



23. Предпочитайте функциям-членам функции, не являющиеся ни членами, ни друзьями класса.

CЛЕДУЕТ ПОМНИТЬ:
— Предпочитайте функциям-членам функции, не яnляющиеся ни членами, ни друзьями класса.
Это повышает степень инкапсулянии н расширяемости, а также гибкость "упаковки" функциональности.



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

CЛЕДУЕТ ПОМНИТЬ:
— Если преобразование типов должно быть применимо ко всем параметрам функции (включая и скрытый параметр this), то функция не должна быть членом класса.



25. Подумайте о поддержке функции swap, не возбуждающей исключений.

CЛЕДУЕТ ПОМНИТЬ:
— Предоставьте функцию-член swap, если std::swap работает с новым типом неэффективно.
Убедитесь, что она не возбуждает исключений.

— Если вы предоставляете функцию-член swap, то также предоставьте свободную функцию, вызывающую функцию-член. Для классов (не шаблонов) специализируйте также std::swap.

— Когда вызывается swap, используйте using-объявление, вводящее std::swap в область видимости, и вызывайте swap без квалификатора пространства имен.

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



26. Откладывайте определение переменных насколько возможно.

CЛЕДУЕТ ПОМНИТЬ:
— Откладывайте определение переменных насколько возможно. Это делает программы яснее и повышает их эффективность.



27. Не зпоупотребпяйте приведением типов.

CЛЕДУЕТ ПОМНИТЬ:
— Избегайте насколько возможно приведений типов, особенно dynamic_cast, в критичном по производительности коде.
Если дизайн требует приведения, попытайтесь разработать альтернативу, где такой необходимости не возникает.

— Когда приведение типа необходимо, постарайтесь скрыть его внутри функции. Тогда пользователи смогут вызывать эту функцию вместо помещения приведения в их собственный код.

— Предпочитайте приведения в стиле С++ старому стилю. Их легче увидеть, и они более избирательны.



28. Избегайте возвращения "дескрипторов" внуrренних данных.

CЛЕДУЕТ ПОМНИТЬ:
— Избегайте возвращать «дескрипторы» (ссылки, указатели, итераторы) внутренних данных объекта.
Это повышает степень инкапсуляции, помогает константным функциям-членам быть константными и минимизирует вероятность появления «висячих дескрипторов».


.
29. Стремитесь, чтобы программа была безопасна относительно исключений.
CЛЕДУЕТ ПОМНИТЬ:
— Безопасные относительно исключений функции не допускают утечки ресурсов и повреждения структур данных, даже в случае возбуждения исключений.
Такие функции предоставляют базовую гарантию, строгую гарантию либо гарантию полного отсутствия исключений.

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

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



30. Тщательно обдумывайте использование встроенных функций.
CЛЕДУЕТ ПОМНИТЬ:
— Делайте встраиваемыми только небольшие, часто вызываемые функции.
Это облегчит отладку, даст возможность выполнять обновления библиотек на двоичном уровне, уменьшит эффект "разбухания" кода и поможет повысить быстродействие программы.

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



31. Уменьшайте зависимости файлов при компиляции.
CЛЕДУЕТ ПОМНИТЬ:
— Основная идея уменьшения зависимостей на этапе компиляции состоит в том, чтобы заменить зависимость от определения зависимостью от объявления.
Эта идея лежит в основе двух подходов: классов-дескрипторов и интерфейсных классов.

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



32. Используйте открытое наследование для моделирования отношения «является».

CЛЕДУЕТ ПОМНИТЬ:
— Открытое наследование означает «является». Все, что применимо к базовому классу, должно быть применимо также и к производным от него классам, потому что каждый объект производного класса является также объектом базового класса.



33. Не скрывайте унаследованные имена.
CЛЕДУЕТ ПОМНИТЬ:
— Имена в производных классах скрывают имена из базовых классов.
При открытом наследовании это всегда нежелательно.

— Чтобы сделать скрытые имена видимыми, используйте using-объявления либо перенаправляющие функции.



34. Различайте наследование интерфейса и наследование реализации.
CЛЕДУЕТ ПОМНИТЬ:
— Наследование интерфейса отличается от наследования реализации.
При открытом наследовании производные классы всегда наследуют интерфейсы базовых классов.

— Чисто виртуальные функции означают, что наследуется только интерфейс.

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

— Невиртуальные функции означают, что наследуются интерфейс и обязательная реализация.



35. Рассмотрите альтернативы виртуальным функциям.

CЛЕДУЕТ ПОМНИТЬ:
— К числу альтернатив виртуальным функциям относятся идиома NVI и различные формы паттерна проектирования "Стратегия".
Идиома NVI сама по себе - это пример реализации паттерна "Шаблонный Метод".

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

— Объекты tr1::function работают как обобщенные указатели на функции.
Такие объекты поддерживают все вызываемые сущности, совместимые с сигнатурой целевой функции.



36. Никогда не переопределяйте наследуемые невирrуальные функции.

CЛЕДУЕТ ПОМНИТЬ:
— Никогда не переопределяйте наследуемые невиртуальные функции.



37. Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию.

CЛЕДУЕТ ПОМНИТЬ:
— Никогда не переопределяйте наследуемые значения аргументов по умолчанию, потому что аргументы по умолчанию связываются статически, тогда как виртуальные функции (а только их и можно переопределять) динамически.



38. Моделируйте отношение «содержит» или «реализуется посредством» с помощью композиции.

CЛЕДУЕТ ПОМНИТЬ:
— Семантика композиции кардинально отличается от семантики открытого наследования.

— В предметной области композиция означает "содержит".
В области реализации она означает "реализовано посредством".
39. Продумывайте подход к использованию закрытого наследования.
CЛЕДУЕТ ПОМНИТЬ:
— Закрытое наследование означает "реализован посредством".
Обычно этот вариант хуже композиции, но все же приобретает смысл, когда производный класс нуждается в доступе к защищенным членам базового класса или должен переопределять унаследованные виртуальные функции.

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



40. Продумывайте подход к использованию множественного наследования.

CЛЕДУЕТ ПОМНИТЬ:
— Множественное наследование сложнее одиночного.
Оно может привести к неоднозначности и необходимости применять виртуальное наследование.

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

— Множественное наследование вполне законно.
Один из сценариев включает комбинацию открытого наследования интерфейсного класса и закрытого наследования класса, помогающего в реализации.



41. Разберитесь в том, что такое неявные интерфейсы и полиморфизм на этапе компиляции.

CЛЕДУЕТ ПОМНИТЬ:
— И классы, и шаблоны поддерживают интерфейсы и полиморфизм.

— Для классов интерфейсы определены явно и включают главным образом сигнатуры функций.
Полиморфизм проявляется во время исполнения - через виртуальные функции.

— Для параметров шаблонов интерфейсы неявны и основаны на корректных выражениях.
Полиморфизм проявляется во время компиляции - через конкретизацию и разрешение перегрузки функций.



42. Усвойте оба значения ключевого слова typename.

CЛЕДУЕТ ПОМНИТЬ:
— В объявлениях параметров шаблона ключевые слова class и typename взаимозаменяемы.

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



43. Необходимо знать, как обращаться к именам в шаблонных базовых классах.

CЛЕДУЕТ ПОМНИТЬ:
— В шаблонах производных классов ссылки на имена из шаблонов базовых классов осуществляются с помощью префикса "this->". using-объявления либо посредством явного указания базового класса.



44. Размещайте независимый от параметров код вне шаблонов.

CЛЕДУЕТ ПОМНИТЬ:
— Шаблоны генерируют множество классов и функций, поэтому любой встречающийся в шаблоне код, который не зависит от параметров шаблона, приводит к разбуханию кода.

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

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



45. Разрабатывайте шаблоны функций-членов так, чтобы они принимали "все совместимые типы".

CЛЕДУЕТ ПОМНИТЬ:
— Используйте шаблонные функции-члены для генерации функций, принимающих все совместимые типы.

— Если вы объявляете шаблоны обобщенных конструкторов копирования или обобщенного оператора присваивания, то по-прежнему должны объявить обычный конструктор копирования и оператор присваивания.



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

CЛЕДУЕТ ПОМНИТЬ:
— Когда пишете шаблон класса, в котором есть функции, нуждающиеся в неявных преобразованиях типа для всех параметров, определяйте такие функции как друзей внутри шаблона класса.



47. Используйте классы-характеристики для предоставления информации о типах.

CЛЕДУЕТ ПОМНИТЬ:
— Классы-характеристики делают доступной информацию о типах во время компиляции.
Они реализованы с применением шаблонов и их специализаций.

— В сочетании с перегрузкой классы-характеристики позволяют проверять типы во время компиляции.
48. Изучите метапрограммирование шаблонов.
CЛЕДУЕТ ПОМНИТЬ:
— Метапрограммирование шаблонов позволяет перенести часть работы со стадии исполнения на стадию компиляции. 
За счет этого можно раньше обнаружить ошибки и повысить производительность программ.

— Технология ТМР может быть использована для генерации кода на основе комбинации политик, а также чтобы предотвратить генерацию кода, некорректного для определенных типов данных.



49. Разберитесь в поведении обработчика new.

CЛЕДУЕТ ПОМНИТЬ:
— set_new_handler позволяет указать функцию, которая должна быть вызвана, если запрос на выделение памяти не может быть удовлетворен.

— Полезность nothrow new ограничена, поскольку эта форма применима только для выделения памяти;
последующие вызовы конструктора могут по-прежнему возбуждать исключения.



50. Когда имеет смысл заменять new и delete.

CЛЕДУЕТ ПОМНИТЬ:
— Есть много причин для написания специальных версий new и delete, включая повышение производительности, отладку ошибок при работе с кучей, а также сбор информации об использовании памяти.



51. Придерживайтесь принятых соглашений при написании new и delete.

CЛЕДУЕТ ПОМНИТЬ:
— Оператор new должен содержать бесконечный цикл, который пытается выделить память, должен вызывать функцию-обработчик new, если не удается удовлетворить запрос на выделение памяти, и должен обрабатывать запрос на выделение нуля байтов.
Версии оператора new уровня класса должны обрабатывать запросы на выделение блоков большего размера, чем ожидается.

— Оператор delete не должен ничего делать при передаче ему нулевого указателя.
Версии оператора delete уровня класса должны обрабатывать запросы на освобождение блоков, которые больше, чем ожидается.



52. Если вы написали оператор new с размещением, напишите и соответствующий оператор delete.

CЛЕДУЕТ ПОМНИТЬ:
— Когда пишете размещающую версию оператора new, убедитесь, что не забыли о соответственном размещающем операторе delete.
Если его не будет, то в программе могут возникать тонкие, трудноуловимые утечки памяти.

— Объявляя размещающие версии new и delete, позаботьтесь о том, чтобы нечаянно не скрыть нормальных версий этих функций.



53. Обращайте внимание на предупреждения компилятора.

CЛЕДУЕТ ПОМНИТЬ:
— Принимайте всерьез предупреждения компилятора и старайтесь добиться того, чтобы ваш код вообще не вызывал предупреждений, даже при задании максимального уровня диагностики.

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



54. Ознакомьтесь со стандартной библиотекой, включая TR1.

CЛЕДУЕТ ПОМНИТЬ:
—  Основная функциональность стандартной библиотеки С++ состоит из STL, потоков iostream и локалей. Также включена стандартная библиотека С99.

— TR1 добавляет поддержку "интеллектуальных" указателей (например, tr1::shared_ptr), обобщенных указателей на функции (tr1::function), кэшированных контейнеров, регулярных выражений и еще 10 компонентов.

— Отчет TR1 сам по себе - всеrо лишь спецификация.
Чтобы воспользоваться преимуществами TR1, понадобится реализация. Одним из источников реализаций компонентов TR1 является проект Boost.



55. Познакомьтесь с Boost
.
CЛЕДУЕТ ПОМНИТЬ:
— Boost - сообщество и WеЬ-сайт для разработки бесплатных библиотек на С++ с открытыми исходными текстами, подверrающихся публичному обсуждению.
Boost оказывает немалое влияние на процедуру стандартизации С++.

— Boost предоставляет реализацию многих компонентов TR1, но - кроме того - и множество друrих библиотек.