Skip to content

Slepoyi/.NetInterviewQuestions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 

Repository files navigation

Вопросы с собеседований на .NET-разработчика

Здесь собраны вопросы, которые могут задать разработчику .NET на собеседовании. На каждый вопрос есть краткий (или не очень) ответ. Ответ не обязательно корректен/полон на 100%. Буду благодарен за исправления/дополнения.

Легенда

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

Платформа .NET

Как устроена память в C# Область памяти в каждом домене приложения делится на системную:
  1. Таблица типов заполнена специальными объектами, наследниками RuntimeType. Каждый тип состоит из статической и экземплярной части. При первом упоминании типа в выполняемом виртуально машиной выражении, его статическая часть размещается в таблице

  2. Список блоков синхронизации набор системных блоков синхронизации. При необходимости синхронизации многопоточный работы, CLR создает блок синхронизации. Блок содержит слабую ссылку на объект синхронизации (слабую, потому что объект может быть удален GC несмотря на ссылку) и ссылку на монитор. Именно эти блоки и лежат в таблице. Для большинства объектов блок синхронизации не создается в принципе.

  3. Пул строк заполнен интернированными строками и строковыми константами

  4. Пул потоков пул потоков виртуальной машины

  5. Прочее некоторые заранее выделенные переменные (OutOfMemoryException), отображение системных ресурсов

И пользовательскую:

  1. Small Object Heap куча, на которой живут объекты меньше 85к байт. Разделена на 3 поколения. При достижении лимита памяти для поколения происходит сборка мусора.

  2. Large Object Heap куча больших объектов (больше 85к байт), не разделена на поколения, собирается по правилам поколения 2.

  3. Pinned Object Heap куча для закрепленных объектов. В текущей версии поддерживает только выделение на ней массивов «непреобразуемых» типов. Специфична, используется очень редко, как таковая внимания не требует.

  4. Стек тут выделяется память под стек приложения

Есть типы двух видов - ссылочные и значимые. Значимые лежат на стеке (стек быстро работает). Ссылочные хранятся на куче (куча медленнее), а на стеке лежит ссылка на область памяти в куче. Стек метода очищается после завершения метода. Куча очищается сборщиком мусора.

Ключевое слово static и его особенности в .NET Ключевое слово static помечает член типа статическим. Статические члены типа не принадлежат экземпляру, а принадлежат типу целиком. 1 раз создаются и живут до конца жизни приложения (если не присвоить им null, тогда их соберет ```GC```). Статический класс может содержать только статические члены. Экземпляр такого класса создать нельзя. Статический конструктор используется для инициализации любых статических данных или для выполнения определенного действия, которое требуется выполнить только один раз. Он вызывается автоматически перед созданием первого экземпляра или ссылкой на какие-либо статические члены. Статический конструктор будет вызываться не более одного раза.
Сборка мусора в .NET Это процесс освобождения памяти сборщиком мусора (Garbage Collector, GC). Запускается тогда, когда CLR посчитает это необходимым (недостаточно физической памяти в системе, используемая память в управляемой куче вышла за текущий порог), но можно вызвать вручную (GC.Collect). Т.к. куча сильно фрагментирована, после очистки памяти происходит уплотнение (сжатие) кучи и обновление ссылок. Куча LOH обычно не сжимается, т.к. это будет долго.

Всего 3 поколения сборки мусора. Если объект пережил сборку мусора, то он переходит на поколение выше (пока есть куда). Сборщик начинает с поколения 0, часть очищает, часть переводит в поколение 1. Если память все еще нужна, GC берется за первое поколение.

Сборщик мусора проходится по корням (участки памяти, в силу определенных причин доступные всегда и содержащие ссылки на объекты, созданные программой), затем просматривает объекты, на которые они ссылаются, и помечает их живыми. Когда все живые объекты определены, все остальное можно очистить, а кучу уплотнить.

Примечание: объекты LOH собираются как объекты поколения 2.

Корни сами по себе не объекты, а ссылки на них. Любой объект, на который ссылается корень, переживет следующую сборку мусора. Корнями являются:

  1. Локальные переменные ссыльного типа в текущем методе
  2. Статические поля
  3. Управляемые объекты, переданные через Interop
  4. Ссылки на объекты с финализатором
В чем разница между процессом и потоком? Процесс - контейнер, в котором запущено приложение

Поток - воркер, который работает внутри процесса и выполняет непосредственную работу; независимый путь выполнения, который может выполняться одновременно с другими

Что такое AppDomain? Это контейнер, внутри которого работает приложение, и который изолирует ее в рантайме; 1 процесс размещает 1 домен (обычно)
Что такое утечка памяти? Как получить ее в .NET Утечка памяти - неконтролируемый рост потребления памяти приложением.

Глобально 2 причины:

  1. Неиспользуемые объекты, на которые остались ссылки

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

    • не освобожденная неуправляемая память
    • не вызванный Dispose у IDisposable (решением является паттерн реализации Dispose)

Базовый функционал

Records - что это, для чего ввели Ключевые особенности: - сравнение работает по свойствам а не ссылкам - можно создавать с помощью with - позиционность (public record A(int B) создаёт конструктор и деконструктор) - ToString выводит отформатированный объект

Разница между абстрактным классом и интерфейсом

Абстрактный класс Интерфейс
Множественное наследование/реализация В C# нет множественного наследования В C# можно реализовать множество интерфесов
"Абстрактные" методы Абстрактный класс может содержать абстрактные методы По сути все методы интерфейса "абстрактны" как контракты, хоть и не содержат ключевого слова "abstract". Однако, начиная с C# 8 интерфейсы могут иметь реализацию по умолчанию.
Состояние Может иметь поля и свойства Может иметь только свойства (нет полей)
Наличие конструктора + -
Возможность наличия статических членов + -
Модификаторы доступа Может содержать разные модификаторы доступа Все методы и свойства интерфейса публичны
Строгость контракта Обязательно переопределить все абстрактные методы, виртуальные по желанию Обязательно реализовать все методы интерфейса
Когда применять Абстрактный класс используется для выделения общего поведения, классов одной иерархии Интерфейс используется для выделения общего функционала в классах разной иерархии

Статический класс vs singleton vs DI singleton

Статический класс Singleton DI singleton
Ленивость Нет Есть, но можно потерять (положив инстанс в DI) Есть
Работа с зависимостями Проблемно Проблемно Легко
Управление состоянием Сложно следить Нормально Нормально
Реализация интерфейса - + +
Наследование - + +
Выводы:
Статические классы следует использовать только для утилитарных вещей (например, методы расширений). Для всего остального лучше использовать синглтон (меньше шансов отстрелить себе ногу).
(J, M*) Какие базовые методы есть в типе ```object``` GetHashCode, Equals, ToString, GetType
Какой тип возвращает GetHashCode Int
Что работает быстрее - класс или структура. Во-первых, «быстрее» будет не наверняка. Например, если нужно передавать в метод данные, то не факт, что передача ссылки на класс будет медленнее, чем копирование структуры.

Во-вторых, если нужен просто доступ к данным небольшого локального объекта, то структура будет быстрее за счет хранения на стеке (стек работает быстрее кучи)

Если тупо заменить класс на структуру (слово class на struct в декларации) и попытаться скомпилировать - будут ли ошибки? Если да, то какие Код не скомпилируется, если:
  • есть наследование от класса (т.к. наследовать от структуры нельзя)
  • есть деструктор (финализатор) класса (в структурах их нет)
  • есть конструктор без параметров, которые запрещены в структурах в старых версиях C# (хотя сейчас можно)
  • указаны значения полей по умолчанию (в структурах нельзя указывать значение по умолчанию для полей в старых версиях C# (сейчас можно))
Делегаты, события Делегаты - это объекты, указывающие на методы. Методы имеют определенную делегатом сигнатуру, один делегат может указывать на много методов. Под капотом - класс, содержащий в себе сигнатуру метода.

Делегаты можно объединять (определена операция +, есть и -). Если делегат возвращает значение, то значение вернется из последнего метода в списке методов делегата. Если попытаться вызвать делегат, в котором нет методов, получим исключение.

Есть делегаты трех типов - Action, Func и Predicate:

  • Action - действие, которое ничего не возвращает
  • Predicate - принимает 1 параметр и возвращает bool
  • Func - возвращает результат действия, принимает параметры.

Событие - объект, "представляющий" делегат. В событие добавляются обработчики типа, который определен делегатом. Под капотом - класс с методами Add и Remove, а также полем типа делегата. Методы Add и Remove добавляют и удаляют методы в делегат.

С точки зрения программиста отличия такие:

  • событие может быть вызвано только в том классе, где объявлено
  • события не могут быть локальной переменной, а делегаты могут
Как работает финализатор Точное время вызова не определено. ```GC``` смотрит, поддерживает ли объект ```Finalize```. Если да, то помещает указатель на него в специальную очередь финализации. В момент сборки мусора ```GC``` видит, что объект нужно уничтожить и, если у него есть ```Finalize```, то он копируется в еще одну таблицу и будет уничтожен только при следующей сборке мусора.
Статьи
  1. Делегаты func, action, predicate - Метанит
  2. Делегаты и события - StackOverflow
  3. Делегаты и события - StackOverflow

Коллекции

IEnumerable - что это, как работает Интерфейс, который используется для простого перебора коллекции (проход в одну сторону).

Реализует метод GetEnumerator, который возвращает Enumerator. Enumerator в свою очередь предоставляет свойство Current, а также методы MoveNext и Reset для движения по коллекции.

Как реализован словарь? Внутри лежит 2 массива: ```Entry``` и ```buckets```. При добавлении элемента вычисляется индекс корзины, в которую его добавят: ```(hashcode & 0x7fffffff) % capacity```.

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

Если происходит коллизия (в buckets есть элемент с индексом), то новый элемент добавляется в коллекцию, его индекс пишется в корзину, а индекс старого - в поле next нового.

Если число коллизий велико (больше 100), происходит перехэширование с выбором нового генератора хэш-кодов.

Как работает Array.Resize Создает новый массив нужной длины и копирует туда текущий
Как реализован List Под капотом лежит массив и счетчик. При добавлении элемента элемент записывается в свободную ячейку массива и счетчик увеличивается.

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

ORM, LINQ

IQueryable - что это, как работает Интерфейс, используемый для работы с данными в источнике данных. Расширяет возможности IQueryable (реализует его) Конструирует expression tree, которое выражает запрос, и передает его LINQ-провайдеру, транслирующему дерево в запрос непосредственно к источнику данных.

Важно: один и тот же запрос может быть корректно оттранслирован одним провайдером и не оттранслирован другим; узнать об этом можно только в рантайме.

IEnumerable vs IQueryable ```IEnumerable``` работает в памяти; при фильтрации ```IEnumerable``` отфильтрует записи по предикату в цикле ```foreach```. Запрос выполняется «в лоб». В методах расширения уже есть логика обработки данных.

IQueryable конструирует запрос, отправляет его в источник данных и отдает полученные данные. Запрос будет оптимизирован. Логики обработки данных в расширениях нет.

Плюсы и минусы EF Core **Плюсы:** - Linq - удобный способ построения запросов, при этом есть возможность конструирования запросов из «сырого» SQL - Провайдеры для разных источников данных - Change tracker для удобного отслеживания изменений в сущностях - Транзакции - Подходы code-first и database-first

Минусы:

  • Получение большого кол-ва данных только для чтения медленно, нужно писать дополнительные методы и отключать change tracker
  • Производительность в целом не всегда на высоте
  • Использование Contains по коллекции вызывает постоянную перекомпиляцию запроса (бьет и по серверу БД, т.к. надо перестраивать и кэшировать запрос)
  • Конфликт миграций при параллельной модификации одной сущности
  • Может не оптимальным образом транслировать LINQ в SQL-запрос

Отличия Dapper от EF Core

Разницу наглядно демонстрирует таблица:

EF Core Dapper
Функционал Feature-rich ORM Micro-ORM
Запросы LINQ, чистый SQL, возможность комбинировать Чистый SQL
Использование Много фич, из-за чего имеет свои особенности, которые надо знать, чтобы им полноценно пользоваться Близок к обычному SQL, легко внедрить
Типы Сильно привязывается к схеме БД, более высокий уровень абстракции Не использует сильную типизацию, просто маппит данные в объекты, более низкий уровень абстракции
Compile-time ошибки Базовые ошибки (например, типов) могут быть отловлены в compile-time, но т.к. построение дерева выражения происходит в рантайме, некоторые проблемы могут возникать и в рантайме Работает с SQL, все проблемы с запросами ловим в рантайме
Быстродействие Работает значительно медленнее из-за трансляции запросов и (самое затратное) change tracker’a. При отключении трекинга EF Core все еще медленнее, но разница становится не такой значительной Стабильно быстрее и менее прожорлив по памяти, чем EF Core
Поддержка СУБД MS SQL, PostgreSQL, MySQL, Oracle DB, SQLite, MariaDB, Azure SQL, Db2 и другие MS SQL, PostgreSQL, MySQL, Oracle DB, SQLite
Миграции + -

Многопоточное/асинхронное программирование

В чем разница между Task и Thread?

Поток - низкоуровневая абстракция, непосредственно поток выполнения (путь выполнения), который можно переиспользовать.

Задача - высокоуровневая абстракция, «обещание» выполнения переданного кода. Код выполняется на потоке. О выполнении заботится TaskScheduler.

Что такое критическая секция

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

Monitor, lock, Mutex

Monitor - класс, реализующий идею критической секции.

lock - синтаксический сахар над Monitor.Enter и Monitor.Exit, как и using, разворачивается компилятором в try..finally.

Mutex - объект операционной системы, который можно использовать для межпроцессной синхронизации.

Внутри блокировки Monitorlock) нельзя использовать await, потому что код после await совсем не обязательно будет выполнен на том же поток, на котором код до. Следовательно, Enter будет вызван одним потоком, Exit - другим, получим исключение синхронизации.

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

Блокировка на this, Type, string

Блокировка на this: Проблема в том, что ссылка на this доступна извне вашего объекта. Можно получить проблемы, если кто-то другой возьмет сылку на ваш объект и начнет блокировать по ней. Это приведет к тормозам, в теории может привести к дедлоку.

Блокировка на string: Все строковые константы (и вычислимые строки типа "a" + "b") интернируются. Поэтому, если в разных частях программы написать "Hello, world!", то ссылки будут вести на один объект в пуле интернированных строк. Проблемы могут возникнуть, т.к. это работает даже между доменами приложений. К тому же, если не объявить строку как readonly или const, ее можно будет изменить (путем конкатенации добавить что-то), после чего ссылка на объект поменяется и в lock можно будет попасть повторно.

Блокировка на Type: Проблема аналогична одновременно с this и строками. Во-первых, вы не владеете объектом Type и кто угодно может на нем заблокироваться. Во-вторых, иногда Type является разделяемым между доменами приложений, что также приведет к проблеме.

Согласно гайдлайну Microsoft, блокировка на всех трех вышеуказанных объектах не рекомендуется.

Потокобезопасные коллекции

Лежат в пространстве имен System.Collections.Concurrent.

ConcurrentDictionary - словарь на эффективных блокировках.

ConcurrentStack, ConcurrentQueue - неблокирующие синхронизации.

BlockingCollection - потокобезопасная коллекция элементов с эффективной синхронизацией.

Также потокобезопасными являются неизменяемые коллекции из System.Collections.Immutable, т.к. доступны только на чтение.

Для чего используется async/await и Task.Run

async/await используется для IO-bound операций (ожидание ввода-вывода: ответа на запрос, получения данных из базы).

Task.Run используется для CPU-bound операция (вычисления на процессоре).

Различия связаны с механизмом работы. Task.Run берет новый поток из пула и говорит ему, что нужно делать; если работа является низкоинтенсивной, поток все равно будет занят. async/await же компилируется в конечный автомат (машину состояний), которая может пойти по двум веткам выполнения - синхронной и асинхронной; если выполнение пошло по асинхронному пути, то машина позволяет потоку обслуживать другие задачи во время ожидания.

Для чего используется ValueTask, в чем отличие от Task

Т.к. Task - это класс, его использование ведет к выделению памяти на куче и влечет дополнительную работу GC. Среда умная и умеет кэшировать возвращаемые значения, оборачивая их в таск, но кэширование большого количества значений невозможно (например, если возвращаются числа типа int). Следовательно, возвращая простые (уже вычисленные) значения из задач, мы имеем выделение большого количества памяти на куче и тормозим работу приложения GC. Отсутствие выделения памяти в случае синхронного выполнения таска - благо, особенно когда работаем с высоконагруженными сервисами и все максимально файнтюнится.

Для решения проблемы придумали ValueTask. Это структура, которая, если Task уже завершился, просто обернет TResult, в результате чего никакого выделения на куче не будет вообще; если же выполнение идет асинхронным путем, Task будет размещен, а ValueTask его обернет.

Semaphore и SemaphoreSlim

Семафор - примитив синхронизации, предоставляющий доступ к ресурсу множеству потоков (количество варьируется).

Обычный семафор работает на семафорах ядра Windows, Slim работает на SpinWait и классе Monitor.

Обычный семафор можно именовать (межпроцессная синхронизация), Slim нет (внутрипроцессная синхронизация).

Slim считается более легковесной частью, рекомендуется использовать его, когда время ожидания мало (1/4 микросекунды).

ReaderWriterLock и ReaderWriterLockSlim

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

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

Mutex

Примитив синхронизации, который предоставляет монопольный доступ к ресурсу. Работает на уровне процесса. Именованный mutex работает на уровне ОС. Именованные мьютексы поддерживаются не на всех ОС (точно нет на MacOS, при попытке создать именованный семафор получим исключение в рантайме).

(M) Проблемы многопоточных приложений

Deadlock - взаимоблокировка потоков, дальнейшее выполнение невозможно.

Race condition - состояние гонки, когда результат выполнения программы не всегда детерминирован из-за того, что параллельные потоки влияют друг на друга.

Thread starvation - «потоков голод», программа отобрала слишком много рабочих потоков, в результате чего работать стало некому.

Busy-wait - проблема, когда слишком много потоков хотят получить доступ к ресурсу, а выполняют вычисление над ресурсом мало потоков (или 1), в результате чего большинство потоков занято просто ожиданием.

Как получить deadlock в C#

Вариантов несколько. Например, вот классический вариант: Поток 1 занял ресурс А. Поток 2 занял ресурс Б. Поток 1 пытается занять ресурс Б, а поток 2 пытается занять ресурс А. Потоки пытаются занять ресурсы, занятые друг другом, в результате чего происходит взаимоблокировка.

SynchronizationContext - что такое, зачем нужен, как использовать.

  • Это класс-контракт, используемый для синхронизации (коммуникации) потоков
  • SynchronizationContext.Current является синглтоном в рамках потока
  • Для разработчика работает как очередь сообщений, отправляя делегат асинхронно с помощью Post или синхронно с помощью Send
  • Отсутствует в ASP .NET Core
  • Нужен, например, чтобы при желании изменить UI в десктопе из другого потока, нужно было лишь передать контекст синхронизации

Статьи:

  1. Базовые понятия многопоточности - Habr
  2. Гайдлайн Microsoft по lock
  3. Примитивы синхронизации - Habr
  4. Потокобезопасные коллекции - StackOverflow
  5. Потокобезопасные коллекции - Habr
  6. Когда использовать Task.Run и async/await - StackOverflow

Веб-программирование

Middleware в ASP .NET Core, какой паттерн реализует

Chain of responsibility

Socket exhaustion при работе с HttpClient и решение проблемы (HttpClientFactory и как она решает проблему)

Суть проблемы: Если избавиться от HttpClient, то при завершении работы он освобождает ресурс со своей стороны, но с другой стороны мы оставляем сокет в статусе TIME_WAIT и ждем (вроде на 240с)

Как работает фабрика:

  • при вызове CreateClient создаётся и настраивается новый HttpClient
  • под капотом создаются обработчики HttpMessageHandler, которые живут 2 минуты (время можно настроить)
  • экземпляры HttpMessageHandler объединяются в пулы
  • проблема устаревания DNS решается путем регулярной утилизации экземпляров HttpMessageHandler

HSTS

Это заголовок HTTP, информирующий браузер, что попытки обращения по HTTP должны быть конвертированы в HTTPS. Нужен для того, чтобы избежать атаки man-in-the-middle, которая работает с включенным https redirection.

Что такое куки и как они работают

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

  • персонализации
  • управление сеансами
  • трекинга и т.д.

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

Что такое DNS

Система доменных имен, которая переводит IP-адрес в понятное буквенное имя (домен). Работает благодаря DNS-серверам, которых в интернете множество.

Что такое JWT-токен, как устроен

JWT - Json Web Token - стандарт, применяемый для создания токенов доступа на основе JSON. В основном используется для передачи данных об аутентификации на сервер веб-приложения.

Состоит из трех частей: заголовок, пейлоад и подпись. Как правило, представляется в компактном виде, где заголовок и пейлоад закодированы в base64_url, после чего к ним добавляется подпись и все части разделяются точками.

В заголовке 1 необходимое поле alg - алгоритм шифрования подписи. В пейлоаде идет пользовательская информация, обязательных полей нет.

Что такое REST, какие требования

Набор правил по организации написания кода. Работает поверх HTTP 1.1.

Требования:

  1. Модель клиент-сервер (обмен данными инициирован запросом клиента)
  2. Отсутствие состояния (вся требуемая для запроса информация поступает с запросом)
  3. Кэширование (для простоты ответы сервера можно кэшировать)
  4. Унифицированый интерфейс (HATEOAS - отправка клиенту не только запрошенной информации, но и связей с другими ресурсами и действий)
  5. Многослойная архитектура (ни клиент, ни сервер не знают всю цепочку вызова, максимум своих ближайших соседей)
  6. (опционально) Код по требованию (сервер может передать клиенту код для выполнения)

Глаголы HTTP, назначение и особенности

GET - глагол, предназначенный для получения ресурса. Не содержит тела, является идемпотентным. POST - глагол, предназначенный для отправки данных на сервер. Может содержать тело, не является идемпотентным. PUT - глагол, предназначенный для создания или замены ресурса. Может содержать тело, является идемпотентным. PATCH - предназначен для частичного обновления ресурса. Может содержать тело, не является идемпотентным. DELETE - глагол, предназначенный для удаления ресурса. Может содержать тело, является идемпотентным.

Жизненный цикл запроса в ASP .NET Core

  1. request
  2. middleware
  3. routing
  4. controller init
  5. action method exec
  6. action result exec
  7. middleware
  8. response

HttpContext

Ключевые особенности:

  • Контекст текущего запроса, содержащий информацию о нем
  • В современном ASP .NET Core нет контекста синхронизации и нет HttpContext.Current, единственным способом получения контекста будет свойство в контроллере или внедрение зависимости, оба метода вернут один и тот же объект
  • Не является потокобезопасным: доступ к контексту из разных потоков может привести к неожиданному поведению
  • Не должен быть захвачен потоками

Что такое gRPC

RPC - удаленный вызов процедур. Использует protobuf для обмена данными. Поток данных как однонаправленный в виде запрос-ответ, так и двунаправленный с помощью стримов. Быстрее, чем REST по бенчмаркам.

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

Что такое GraphQL, как работает

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

Вместо работы с множеством «глупых» эндпоинтов, которые отдают только то, что знают, предлагается 1 «умный» эндпоинт, который обрабатывает запрос на выборку данных и отдает их. Основан на трех компонентах: schema, queries, resolvers

Когда мы просим данные - мы выполняем запрос. Распознаватель - помощник, который определяет, как и где взять данные для указанного поля. Не обязательно лезть в бд, поле можно вообще выдумать.

Схема - унифицированный язык запросов, благодаря которому все это работает

SQL

Что такое внешний ключ?

Ограничение, используемое для обеспечения целостности связей между таблицами. Является ссылкой на первичный ключ другой таблицы. При вставке значения в «дочернюю» таблицу проверяется наличие соответствующего первичного ключа в «родительской» Если значения нет - ошибка.

В чем отличие HAVING и WHERE?

HAVING фильтрует на уровне сгруппированных данных, WHERE на уровне исходных.

Что такое ACID, уровни изоляции транзакции

ACID - atomicity, consistency, isolation, durability - набор требований к системе транзакций, обеспечивающий ее наиболее надежную работу.

Атомарность - транзакции выполняется целиком либо не выполняется.

Согласованность - каждая транзакции фиксирует только допустимые результаты (вообще говоря лежит на плечах прикладного ПО).

Изоляция - сокрытие изменений других транзакции при возникновении race condition (параллельные транзакции не должны оказывать влияния на выполнение другой транзакции).

Эффекты, связанные с изоляцией:

  1. Потерянное обновление (транзакции обновляют одни и те же данные, не учитывая другие транзакции) Транзакция А изменила значение 1000, добавив к нему 500. До фиксации изменения транзакцией А транзакции Б прочитала баланс, вычла из него 600. Итоговая сумма 400 вместо 900.
  2. Грязное чтение (чтение данных незавершенных транзакции) Транзакция А изменила значение 1000, вычла 1000. Транзакции Б проверила значение 0 и отработала. Транзакции А отменилась.
  3. Неповторяемое чтение (считывание одной и той же строки 2 раза, получение разного результата) Пусть значение не может уйти в минус. Транзакция А считала значение 1000. Транзакция Б прочитала уменьшила значение на 1000 и зафиксировала изменение. Транзакция А, видя значение 1000, также отнимает 1000, значение уходит в минус.
  4. Фантомное чтение (набор данных соответствует предикату поиска, но не отображается сразу) Например, нельзя иметь больше трех счетов. Для открытия счета транзакция А проверяет все счета клиента, видит 2. В этот момент транзакция Б открывает еще один счет.

Для решения проблем есть уровни изоляции:

  1. Read uncommitted
  2. Read committed
  3. Repeatable read
  4. Serializable

Устойчивость - изменения успешно завершенной транзакции остаются в системе даже после последующего сбоя

Оконные функции

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

Партиции - набор строк, указанные для оконной функции.

Хранимые процедуры, функции, триггеры

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

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

Функции - код SQL, обязательно возвращающий значение определенного типа.

Что такое индекс в SQL? Какие структуры данных лежат в основе индексов? Какие типы индексов есть?

Тут в зависимости от грейда ожидается своя широта знаний об индексах.

Индекс - специальная структура данных, ускоряющая поиск в таблице.

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

Некластеризованный индекс хранит в себе значения индекса и ссылки на строки данных в таблице. Кластеризованный индекс хранит в себе строки целиком. Можно сказать, что кластеризованные индекс - это способ хранения данных в таблице. Следовательно, кластерный индекс может быть 1, некластеризованных много (в районе 1000).

Индекс может быть:

  1. Составным (содержит более 1 столба, но не более 16, или длина не более 900 байт)
  2. Уникальным (обеспечивает уникальность значений в столбце; автоматически создается при задании первичного ключа или ограничения UNIQUE)
  3. Покрывающим (позволяет получить нужные данные сразу с листьев индекса без обращения к записям таблицы)

В качестве структуры данных чаще всего используется сбалансированное дерево (b-tree), но могут и другие (например, bitmap).

Принципы построения индексов:

  1. Чем меньше индексов, тем лучше
  2. Чем меньше полей в индексе, тем лучше
  3. Уникальность значений влияет на индекс
  4. Для составного индекса внимание на порядок
  5. Обдумать целесообразность введения индекса (если данные часто обновляются, а поиска мало, то индекс не нужен)

Какие минусы есть у внешнего ключа? Всегда ли нужно его использовать?

Ключевые проблемы:

  • при больших нагрузках внешний ключ начинает тормозить выполнение запросов (но система должна быть действительно сильно нагружена).
  • поведение ON DELETE фактически является перекладыванием бизнес-логики на хранилище, что с точки зрения system design рассматривается как некорректное.
  • при массовых вставках в таблицу проверка целостности внешнего ключа может привести к взаимоблокировкам транзакции.

DELETE FROM VS TRUNCATE TABLE

DELETE FROM удаляет записи из таблицы по одной, блокируя записи по одной и занося каждое удаление в журнал. TRUNCATE TABLE блокирует таблицу целиком, не заносит удаление каждой записи в журнал, потому работает быстрее.

ООП, проектирование

Какие паттерны проектирования использовал? Опишите юзкейсы. Где в .NET можно встретить эти паттерны?

Стратегия - когда мы хотим изменять внутренний алгоритм работы в зависимости от каких-то условий путем делегирования функционала вовне. Например, стратегия сравнения элементов в LINQ IEqualityComparer.

Состояние - когда поведение объекта зависит от его внутреннего состояния. Например, состояния Task.

Фабрика - абстрагирование от создания объектов, внесение полиморфизма в создание объектов. Статические фабрики в TimeSpan, HttpClientFactory, Task.Factory, фасадная фабрика File.Create.

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

Адаптер - когда мы совмещаем два несовместимых интерфейса. LINQ-провайдеры, TextReader/TextWriter являются адаптерами над Stream.

Фасад - представление унифицированного интерфейса вместо набора интерфейсов подсистем. File.Create

Посредник - позволяет реализовать общение объектов без необходимости ссылаться друг на друга. Любая форма в WinForms есть посредник, в паттерне MVC контроллер есть посредник.

Команда - позволяет определять команду на выполнение действия как объект. Помогает реализовать потоковую обработку, отмену запросов и т.п. Task принимает делегат Func, который будет использован для получения результата задачи.

Шаблонный метод - переопределение этапов алгоритма без изменения его структуры, а также вынесение общей части алгоритма в шаблонный метод. ChannelBase в WCF

Цепочка обязанностей - позволяет выстраивать получателей в цепочку, каждый из получателей обработает запрос и передаст дальше. Событие Closing в Windows Forms.

Основные принципы ООП

Инкапсуляция - скрытие внутреннего состояния и функций объекта и предоставление доступа через открытый набор методов.

Наследование - возможность создания новых абстракций на основе существующих.

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

Абстракция - моделирование требуемых атрибутов сущности реального мира в виде сущности кода с достаточным уровнем точности.

SOLID

Набор правил, сформулированных как рекомендации для написания «чистого» кода

S - Single responsibility - единственная ответственность

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

В «чистом коде» формулирует так: Модуль должен иметь только одну причину для изменения. Иначе говоря, модуль должен обслуживать только одну заинтересованную группу.

O - Open/closed - открытость-закрытость Объекты должны быть открыты для расширения и закрыты для модификации (интерфейсы)

L - Liscov substitution - подстановка Лисков Рекомендация по определению иерархии объектов. При подстановке дочерней сущности вместо родительской ничего не должно ломаться. Если сломалось - ошибка в иерархии.

I - Interface segregation - разделение интерфейсов Объект не должен зависеть от интерфейса, который он не реализует (если в классе есть методы интерфейса, которые не используются - интерфейс можно и нужно разделить на 2 или более)

D - Dependency inversion - инверсия зависимостей Модули верхнего уровня не зависят от модулей нижнего уровня, все они зависят от абстракций. Абстракции не зависят от деталей - детали зависят от абстракций.

DRY, KISS, YAGNI, BDUF

Аббревиатуры, обозначающие один из принципов: DRY - Don’t Repeat Yourself - не повторяйся (дублирование кода - это плохо, придется поддерживать его в двух и более местах одинаковый).

KISS - Keep It Simple, Stupid - будь проще (зачем делать сложно если можно сделать просто?).

YAGNI - You Aren’t Gonna Need It - тебе это не понадобится (отказ от избыточной функциональности).

BDUF - Big Design Up Front - глобальное проектирование прежде всего (если неполностью спроектировать систему, то почти наверняка придется решать проблемы проектирования кодом - костыли, кривые решения).

Монолит, микросервисы и сервис-ориентированная архитектура: плюсы и минусы

Ключевые различия в таблице:

Монолит Сервис-ориентированная Микросервисы
Отказоустойчивость Одна ошибка может положить все приложение Сляжет сервис, что больнее, чем на микросервисах, но лечге, чем на монолите Ошибка уложит один сервис, не повлияв на остальные
Язык программирования Пишется на одном языке Один язык в рамках одного сервиса Один язык в рамках одного сервиса
Управление релизами Сложное, требуется полная перепубликация Средняя, перепубликация сервиса не сложна Легкая, перепубликация сервиса очень быстра
Проектирование Легкое Легкое Бывает весьма трудным
Поддержка контрактов Не требуется Не требуется Требуется
Распределенные транзакции Не требуется Не требуется Если да, то это головная боль
Поиск ошибок Легкий Легкий Как правило затруднен
Скорость отклика Высокая Высокая Низкая
Трудность развертывания Низкая Средняя Высокая
Стоимость разработки/поддержки Низкая Средняя Высокая
Гибкость Низкая Средняя Высокая
Масштабируемость Низкая Средняя Высокая

Виды архитектуры приложений

Трехслойная архитектура Содержит три слоя:

  1. Доступ к данным - модели данных, миграции, иногда добавляются интерфейсы репозиториев
  2. Бизнес-логика - все сервисы и бизнес-модели
  3. Клиентская логика - UI или контроллеры, middleware Все зависимости идут сверху вниз, зависимости снизу вверх отсутствуют (например, слой данных не зависит от бизнес логики).

Чистая архитектура Также разделяется на 3 слоя:

  1. Слой ядра приложения - содержит модели данных, а также доменные сервисы, события и т.п.
  2. Инфраструктурный слой - кэши данных, доступ к хранилищу данных, прочие сервисы (например, сервис по отправке электронных писем)
  3. Клиентский слой - UI или контроллеры, middleware Зависимости также идут сверху вниз, доменный код не зависит от инфраструктурного кода. В таком случае, для доменного кода легко писать тесты, т.к. фактически нет зависимостей от других слоев.

Vertical slices Вместо использования слоев, в которых может находиться много несвязанного между собой кода (речь про не связанные доменные модели/сервисы), предлагается использовать срезы, которые расположены перпендикулярно слоям. Срез содержит в себе все, что требуется для ответа на запрос клиента. В таком подходе отпадает необходимость во введении большого количества абстракций - внутри среза можно выстроить наиболее быстрый/удобный путь выполнения запроса. Часто используется совместно с CQRS.

Каким образом микросервисы могут общаться между собой?

Синхронное общение (запрос-ответ):

  1. REST
  • легко написать
  • легко вызвать
  • легко для понимания
  • если сервис-получатель запроса лежит - ошибка, нельзя отложить выполнение запроса
  • нет схемы данных
  • текстовый формат порождает много лишних данных (ключ-значение в json)
  1. gRPC
  • отправка запроса выглядит как вызов метода в коде
  • есть схема данных
  • бинарный формат данных protobuf
  • есть поддержка стриминга
  1. SOAP
  • xml

Асинхронное общение:

  1. Месседжинг (RabbitMQ)
  • асинхронный формат общения
  • работает по принципу проталкивания сообщения читателям
  • удаляет сообщение после обработки
  • может реализовать сложную архитектуру маршрутизации сообщений
  1. Стриминг (Kafka)
  • асинхронный формат общения
  • работает по принципу вытягивания сообщения из топика читателями
  • перерабатывает титанические объемы данных, хорошо масштабируется
  • хранит сообщения на диске до момента плановой очисти журнала

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

Микросервисы создаются по таким паттернам:

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

Паттерны распила монолита на микросервисы:

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

Статьи:

  1. SOLID - Habr (Переход на микросервисы - Mail)[https://mcs.mail.ru/blog/26-osnovnyh-patternov-mikroservisnoj-razrabotki]
  2. микросервисы vs монолит vs сервис-ориентированная архитектура - Skillbox
  3. Общение микросервисов - Habr
  4. Kafka & RabbitMQ - TProger

Отвлеченные темы

TCP & UDP

TCP:

  1. устанавливает соединение перед отправкой (рукопожатием)
  2. контроль порядка пакетов
  3. надежность передачи данных
  4. медленнее UDP
  5. из-за надежности требует больше данных в заголовке UDP:
  6. не требует рукопожатия
  7. не контролирует порядок или доставку пакетов
  8. быстрее TCP
  9. меньше заголовок

Симметричное, асимметричное шифрование, хэширование

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

Асимметричные - шифрование и дешифрование используют разные ключи.

Хэширование - не используется ключ, кодирование происходит с помощью математических операций.

Хорошие и плохие алгоритмы шифрования

MD5 - алгоритм хэширование, предпосылки к взлому появились еще в 90-ых, сейчас популярность падает.

SHA1/2 - алгоритм хэширования, генерирующий 160-битное значение хэша. Лежит в основе многих алгоритмов и протоколов.

Rijnadel (AES) - симметричный алгоритм блочного шифрования. Размер блока 128 бит, 128/192/256 бит ключи. Популярный алгоритм шифрования и в наши дни.

DES - симметричный блочный алгоритм шифрования с блоком 64 бита. Сейчас считается небезопасным.

RC4 - потоковый шифр, широко применяющийся в различных криптосистемах и защите информации (SSL/TLS). Быстро работает, поддерживает переменный размер ключа. Уязвим к не случайному ключу или переиспользованию ключевого потока. Сейчас использовать не рекомендуется.

Диффи-Хеллман - один из первых асимметричных алгоритмов. Сейчас не используется, т.к. Были обнаружены уязвимости типа «человек посередине».

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

ECC - криптография с эллиптическими кривыми. Может предложить уровень стойкости RSA при гораздо меньшем размере ключей.

Статьи:

  1. TCP/UDP - Habr
  2. TCP/UDP - cloud4y
  3. Устаревание RSA - Habr
  4. Хорошие и плохие алгоритмы шифрования - proverkassl

Задачи на код:

Задача 1.

Дан класс. Описать, что он делает, что можно поменять.

public class MyClass
{
    public string filePath;

    public void set_path(string path)
    {
        filePath = path;
    }

    public string read_file_utf8()
    {
        var result = "";
        var reader = new StreamReader(filePath, Encoding.UTF8);
        while (!reader.EndOfStream)
        {
            result += reader.ReadLine();
            result += "\n";
        }
        return result;
    }

    public string read_file_utf32()
    {
        var result = "";
        var reader = new StreamReader(filePath, Encoding.UTF32);
        while (!reader.EndOfStream)
        {
            result += reader.ReadLine();
            result += "\n";
        }
        return result;
    }
}

Проблемы:

  1. Имя класса не говорящее
  2. Именование методов не из мира C#
  3. Имеем 2 одинаковых метода, отличающиеся параметром кодировки. Лучше сделать 1 метод, который принимает котировку как параметр.
  4. В публичное поле (которому не надо быть публичным) устанавливается значение не конструктором, а void-методом, что неправильно, нужен конструктор. При этом если оставить 1 конструктор и 1 метод, то пользоваться классом будет не очень удобно. Проще оставить 1 статический метод, который принимает 2 параметра: кодировку и путь до файла.
  5. Если файл большой, то конкатенация строк в результат может быть долгой, лучше использовать StringBuilder
  6. StreamReader следует обернуть в using.
  7. Не на всех ОС перенос строки реализуется с помощью «\n», иногда это другой символ. Лучше заменить на Environment.NewLine

Итог:

public class FileReader
{
    public static string ReadFileWithEncoding(string filePath, Encoding encoding)
    {
        var result = new StringBuilder();
        using (var reader = new StreamReader(filePath, encoding))
        {
            while (!reader.EndOfStream)
            {
                result.Append(reader.ReadLine());
                result.Append(Environment.NewLine);
            }
        }
        return result.ToString();
    }
}

About

.NET interview questions asked at different grades

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published