Разработчик C#
Чтобы принять участие в стажировке вам нужно заполнить анкету откликнувшись по ссылке https://forms.gle/b2783JeCqFPRWsdg8
Последняя часть вопросов в анкете отведена для ответов на задачи, которые описаны ниже на этой странице. Чаще всего в поле ответа нужно указать ссылку на gist с текстом ответа, либо ссылку на репозиторий, обращайте внимание на то, что нужно оставить в анкете в качестве результата. Если вместо требуемой ссылки вы приведете что-то другое, например текст ответа, ваш ответ с высокой вероятностью не будет засчитан.
Обязанности
- Разработка серверной части web-приложений (.NET Core, PostgreSQL, gRPC)
Требования
- Базовые знания C# и .NET Core.
- Теоретические знания хотя бы одного из фреймворков для разработки веб-приложений (можно не на .NET Core).
- Готовность к изучению множества технологий разработки одновременно в свое личное время.
- Приветствуется владение чем-нибудь из перечисленного: SQL, HTML, CSS (также lesscss, sass), Linux CLI, Docker, git, VS Code (или другие IDE/редакторы).
- Приветствуется базовая грамотность в Computer Science, включая базы данных, сетевые технологии, технологические стеки построения веб-приложений, устройства операционных систем (в первую очередь семейства Linux). Ориентиры: https://yollection.ru/road/backend, https://yollection.ru/road/frontend.
Задание 1. Разработать функцию определения счета в игре
Задача
В примере кода ниже генерируется список фиксаций состояния счета игры в течение матча.
Разработайте функцию Game.getScore(offset), которая вернет счет на момент offset.
Нужно суметь понять суть написанного кода, заметить нюансы, разработать функцию вписывающуюся стилем в существующий код, желательно адекватной алгоритмической сложности.
namespace Task1
{
class App
{
static void Main(string[] args)
{
Game.task1();
}
}
public struct Score
{
public int home;
public int away;
public Score(int home, int away)
{
this.home = home;
this.away = away;
}
}
public struct GameStamp
{
public int offset;
public Score score;
public GameStamp(int offset, int home, int away)
{
this.offset = offset;
this.score = new Score(home, away);
}
}
public class Game
{
const int TIMESTAMPS_COUNT = 50000;
const double PROBABILITY_SCORE_CHANGED = 0.0001;
const double PROBABILITY_HOME_SCORE = 0.45;
const int OFFSET_MAX_STEP = 3;
GameStamp[] gameStamps;
public Game()
{
this.gameStamps = new GameStamp[] { };
}
public Game(GameStamp[] gameStamps)
{
this.gameStamps = gameStamps;
}
GameStamp generateGameStamp(GameStamp previousValue)
{
Random rand = new Random();
bool scoreChanged = rand.NextDouble() > 1 - PROBABILITY_SCORE_CHANGED;
int homeScoreChange = scoreChanged && rand.NextDouble() > 1 - PROBABILITY_HOME_SCORE ? 1 : 0;
int awayScoreChange = scoreChanged && homeScoreChange == 0 ? 1 : 0;
int offsetChange = (int)(Math.Floor(rand.NextDouble() * OFFSET_MAX_STEP)) + 1;
return new GameStamp(
previousValue.offset + offsetChange,
previousValue.score.home + homeScoreChange,
previousValue.score.away + awayScoreChange
);
}
static Game generateGame()
{
Game game = new Game();
game.gameStamps = new GameStamp[TIMESTAMPS_COUNT];
GameStamp currentStamp = new GameStamp(0, 0, 0);
for (int i = 0; i < TIMESTAMPS_COUNT; i++)
{
game.gameStamps[i] = currentStamp;
currentStamp = game.generateGameStamp(currentStamp);
}
return game;
}
public static void task1()
{
Game game = generateGame();
game.printGameStamps();
}
void printGameStamps()
{
foreach (GameStamp stamp in this.gameStamps)
{
Console.WriteLine($"{stamp.offset}: {stamp.score.home}-{stamp.score.away}");
}
}
public Score getScore(int offset)
{
// continue the function's implementation
}
}
}
Результат
- Ссылка на gist с исходным кодом функции.
Задание 2. Разработать тесты для функции определения счета в игре
Задача
Для разработанной в предыдущем задании функции Game.getScore(offset) разработайте unit-тесты на базе библиотеки XUnit.
Тесты должны учитывать все возможные случаи использования функции, концентрироваться на проверке одного случая, не повторяться, название тестов должно отражать суть выполняемой проверки.
Результат
- Ссылка на gist с исходным кодом тестов.
Задание 3. Разработать gRPC сервис биллинга
Задача
Разработайте сервис биллинга, с возможностью эмиссии внутренней валюты (Coins, или Монеты) приложения, передачи валюты от пользователя к пользователю, находить монету с самой длинной историей перемещения.
Прото-файл сервиса
# billing.proto
syntax = "proto3";
package billing;
option csharp_namespace = "Billing";
service Billing {
rpc ListUsers (None) returns (stream UserProfile);
rpc CoinsEmission (EmissionAmount) returns (Response);
rpc MoveCoins (MoveCoinsTransaction) returns (Response);
rpc LongestHistoryCoin (None) returns (Coin);
}
message None {
}
message UserProfile {
string name = 1;
optional int64 amount = 2;
}
message MoveCoinsTransaction {
string src_user = 1;
string dst_user = 2;
int64 amount = 3;
}
message Response {
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_OK = 1;
STATUS_FAILED = 2;
}
Status status = 1;
string comment = 2;
}
message EmissionAmount {
int64 amount = 1;
}
message Coin {
int64 id = 1;
string history = 2;
}
- Billing.ListUsers() – перечисляет пользователей в сервисе. При инициализации создайте в сервисе следующих пользователей, поле rating потребуется при эмиссии:
[
{
"name": "boris",
"rating": 5000
},
{
"name": "maria",
"rating": 1000
},
{
"name": "oleg",
"rating": 800
}
]
- Billing.CoinsEmission() – распределяет по пользователям amount монет, учитывая рейтинг. Пользователи получают количество монет пропорциональное рейтингу, при этом каждый пользователь должен получить не менее 1-й монеты. Каждая монета имеет свой id и историю перемещения между пользователями, начиная с эмиссии.
- Billing.MoveCoins() – перемещает монеты от пользователя к пользователю если у пользователя-источника достаточно монет на балансе, в противном случае возвращает ошибку.
- Billing.LongestHistoryCoin() – возвращает монету с самой длинной историей перемещения между пользователями.
Замечания к реализации
Для разработки серверной части следует использовать .NET Core 6.
Интегрировать базу данных в этом задании не стоит, достаточно хранить информацию в памяти сервиса, инициализируя его данными пользователей при каждом запуске.
Результат
- Ссылка на gist с результатом работы следующего скрипта:
#!/usr/bin/env bash
GRPCSERVER=localhost:5238
PROTODIR=../Protos
echo "List users. Empty balances."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/ListUsers
echo "Emission 10 coins."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{"amount": "10"}' $GRPCSERVER billing.Billing/CoinsEmission
echo "List users. After the emission."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/ListUsers
echo "Move 5 coins."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{"amount":"5","dst_user":"maria","src_user":"boris"}' $GRPCSERVER billing.Billing/MoveCoins
echo "List users. After the coins movement."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/ListUsers
echo "A coin with the longest history."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/LongestHistoryCoin
- Ссылки на git-репозитории с исходными кодами решения.