Programowanie Nasze Hobby : c sharp, angular, html, css
dev-hobby.pl // junior .NET 2026
TOP 50 Pytań Rekrutacyjnych Junior .NET Developer
Zebrane z realnych rozmów kwalifikacyjnych. Każde pytanie ma wzorcową odpowiedź — taką, której rekruter naprawdę szuka.
50
pytań
7
kategorii
3
poziomy trudności
C#C# PodstawyQ01–Q10
01Jaka jest różnica między class a struct w C#?łatwe+
class to typ referencyjny — obiekt żyje na stercie (heap), zmienna przechowuje referencję. struct to typ wartościowy — dane leżą na stosie (stack) lub inline w obiekcie nadrzędnym, kopiowane przez wartość przy przypisaniu.
// class — kopiujemy referencję
Point a = new Point(1, 2); // Point to class
Point b = a;
b.X = 99;
Console.WriteLine(a.X); // 99 — ten sam obiekt na stercie
// struct — kopiujemy wartość
PointS c = new PointS(1, 2); // PointS to struct
PointS d = c;
d.X = 99;
Console.WriteLine(c.X); // 1 — niezależna kopia
Kiedy struct? Małe, niemutowalne dane: Vector2, Color, DateTime. Unikaj struct większych niż ~16 bajtów lub mutowalnych — kopiowanie jest kosztowne i podatne na błędy.
02Czym jest nullable reference type i jak działa w C# 8+?łatwe+
Od C# 8 typy referencyjne są domyślnie non-nullable. Kompilator ostrzega, gdy możliwy jest NullReferenceException. Dodanie ? jawnie oznacza, że zmienna może być null.
#nullable enable
string name = null; // ⚠️ warning CS8600
string? name = null; // OK — świadomie nullable
void Greet(string? name)
{
// null-forgiving operator — “kompilatorze, wiem co robię”
Console.WriteLine(name!.Length);
// lepiej — jawny null check
if (name is not null)
Console.WriteLine(name.Length);
}
W projekcie: włącz <Nullable>enable</Nullable> w .csproj. Eliminuje całą klasę błędów runtime zanim trafią na produkcję.
03Co to jest async/await i jak działa pod spodem?średnie+
async/await to lukier składniowy (syntactic sugar) na maszynę stanów generowaną przez kompilator. Przy pierwszym await, który nie jest gotowy, metoda zwraca kontrolę do wywołującego. Gdy operacja I/O się kończy, kontynuacja uruchamia się (domyślnie) na tym samym SynchronizationContext.
// Złe — blokuje wątek i grozi deadlockiem
string json = httpClient.GetStringAsync(url).Result; // ❌ deadlock ryzyko!
// Dobre
string json = await httpClient.GetStringAsync(url);
// Bez powrotu do kontekstu (np. w kodzie bibliotecznym)
string json = await httpClient.GetStringAsync(url)
.ConfigureAwait(false);
Pułapka: nigdy nie mieszaj .Result / .Wait() z async w ASP.NET — prowadzi do deadlocka. Jeśli async, to async przez cały callstack.
04Różnica między IEnumerable<T>, ICollection<T> a IList<T>?łatwe+
IEnumerable<T> — tylko do odczytu, leniwa iteracja, brak Count. ICollection<T> dodaje Count, Add, Remove. IList<T> dodaje dostęp indeksowany oraz Insert/RemoveAt.
// Zasada: używaj najwęższego interfejsu wystarczającego do celu
void Process(IEnumerable<Order> orders) { /* tylko iteracja */ }
void Fill(ICollection<Order> orders) { orders.Add(new Order()); }
void Swap(IList<Order> orders, int i, int j) { (orders[i], orders[j]) = (orders[j], orders[i]); }
Wzorzec: parametry metod deklaruj jako IEnumerable<T> (najwęższy wystarczający kontrakt — nie wymuszasz konkretnej kolekcji na wywołującym), pola prywatne jako List<T> (konkretna implementacja).
05Co to jest LINQ i jaka jest różnica między Select, Where a FirstOrDefault?łatwe+
LINQ to deklaratywny mechanizm zapytań do dowolnych źródeł danych (kolekcje, bazy danych, XML). Operatory są leniwe — wykonują się dopiero przy pierwszej enumeracji.
var orders = new List<Order> { … };
// Where — filtruje, zwraca IEnumerable<T>
var active = orders.Where(o => o.IsActive);
// Select — projekcja/transformacja każdego elementu
var ids = orders.Select(o => o.Id);
// FirstOrDefault — zwraca pierwszy pasujący lub null (nie rzuca wyjątku)
var order = orders.FirstOrDefault(o => o.Id == 5);
// vs First() — rzuca InvalidOperationException gdy brak elementu
Na rozmowie: pokaż świadomość deferred execution — LINQ query nie wykonuje się przy deklaracji, tylko przy enumeracji: foreach, ToList(), Count().
06Czym jest record w C# 9+ i kiedy go używać?średnie+
record to typ referencyjny z wbudowaną semantyką wartości: auto-generowane Equals, GetHashCode i ToString oparte na właściwościach, nie referencji. Kompilator generuje też wyrażenie with do tworzenia niemutowalnych kopii.
record Person(string FirstName, string LastName);
var p1 = new Person(“Jan”, “Kowalski”);
var p2 = new Person(“Jan”, “Kowalski”);
Console.WriteLine(p1 == p2); // true — porównanie wg wartości właściwości
var p3 = p1 with { LastName = “Nowak” }; // niemutowalna kopia z nowym nazwiskiem
// record struct (C# 10) — typ wartościowy + semantyka wartości
record struct Point(double X, double Y);
Kiedy record? DTO, Value Objects w DDD, odpowiedzi z API, dane konfiguracyjne — wszędzie tam, gdzie zależy na niemutowalności i porównaniu wg treści.
07Co to jest delegate, Action, Func i Predicate?średnie+
delegate to typowany wskaźnik na metodę. Action<T>, Func<T,TResult>, Predicate<T> to gotowe, wbudowane delegaty — nie trzeba deklarować własnych.
// Func — zwraca wartość; ostatni parametr generyczny to typ zwracany
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(2, 3)); // 5
// Action — nie zwraca wartości (void)
Action<string> log = msg => Console.WriteLine(msg);
// Predicate — przyjmuje T, zwraca bool
Predicate<int> isEven = n => n % 2 == 0;
// Closure — lambda “zamyka” zmienną z zewnętrznego scope
int threshold = 10;
Func<int, bool> above = n => n > threshold;
threshold = 20; // uwaga: zmiana wpływa na zachowanie closure!
08Jak działa Garbage Collector w .NET i co to są generacje?średnie+
GC w .NET jest generacyjny — obiekty podzielone na Gen0, Gen1, Gen2 oraz Large Object Heap (LOH, ≥85 KB). Gen0 jest zbierana najczęściej i najszybciej (krótko żyjące obiekty). Obiekty, które przeżyją kolekcję, trafiają do Gen1, a następnie do Gen2.
// Finalizer bez IDisposable to antypattern — opóźnia zwolnienie o co najmniej jeden cykl GC
class Resource : IDisposable
{
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // bez tego finalizer odpali się ponownie
}
protected virtual void Dispose(bool disposing) { /* cleanup */ }
~Resource() => Dispose(false); // finalizer — ostatnia deska ratunku
}
Praktyka: używaj using / await using dla IDisposable. Unikaj wymuszania kolekcji (GC.Collect()) — prawie zawsze błąd projektowy.
09Czym różni się string od StringBuilder?łatwe+
string jest niemutowalny — każda konkatenacja tworzy nowy obiekt na stercie. W pętlach prowadzi to do alokacji O(n²). StringBuilder mutuje wewnętrzny bufor — alokacja O(n).
// Złe — tworzy n nowych obiektów string
string result = “”;
foreach (var item in items)
result += item + “, “; // 100 iteracji ≈ 100 alokacji
// Dobre
var sb = new StringBuilder();
foreach (var item in items)
sb.Append(item).Append(“, “);
string result = sb.ToString();
// Dla 2–3 konkatenacji += jest całkowicie w porządku
// C# 10+: rozważ string.Create() lub Span<char> dla hot paths
10Co to jest pattern matching w C# i jakie są jego formy?średnie+
Pattern matching pozwala testować kształt i wartości danych w wyrażeniach is, switch i switch expression. C# 9/10 dodał wzorce relacyjne, logiczne i wzorce właściwości.
object obj = new Order { Status = “Active”, Amount = 150 };
// Type pattern + property pattern
if (obj is Order { Status: “Active”, Amount: > 100 } order)
Console.WriteLine($”Duże zamówienie: {order.Amount}”);
// Switch expression (C# 8+)
string label = order.Amount switch
{
< 50 => “małe”,
>= 50 and < 200 => “średnie”,
>= 200 => “duże”,
_ => “nieznane”
};
OOPOOP & SOLIDQ11–Q20
11Wyjaśnij cztery filary OOP na konkretnych przykładach.łatwe+
Enkapsulacja — ukrywanie stanu, dostęp przez publiczne API. Dziedziczenie — współdzielenie kodu przez hierarchię klas. Polimorfizm — ten sam interfejs, różne zachowanie. Abstrakcja — modelowanie esencji domeny, ukrywanie szczegółów implementacji.
abstract class Shape
{
public abstract double Area();
}
class Circle(double radius) : Shape
{
public override double Area() => Math.PI * radius * radius;
}
class Rectangle(double w, double h) : Shape
{
public override double Area() => w * h;
}
// Polimorficzne wywołanie — konkretny typ nie ma znaczenia
List<Shape> shapes = [new Circle(5), new Rectangle(3, 4)];
double total = shapes.Sum(s => s.Area()); // 98.54…
12Co to jest zasada SRP (Single Responsibility Principle)?łatwe+
Klasa powinna mieć jeden powód do zmiany — jeden obszar odpowiedzialności. “Jeden powód” to jeden aktor biznesowy, który może zażądać zmiany logiki.
// Naruszenie SRP — OrderService robi za dużo
class OrderService
{
public void PlaceOrder(Order order) { /* logika zamówień */ }
public void SendEmail(Order order) { /* wysyłka maila */ } // ← osobna odpowiedzialność
public void SaveToCsv(Order order) { /* eksport danych */ } // ← osobna odpowiedzialność
}
// Po SRP — każda klasa ma jeden powód do zmiany
class OrderService { public void PlaceOrder(Order order) { … } }
class EmailService { public void SendConfirmation(Order order) { … } }
class OrderExporter { public void ExportToCsv(IEnumerable<Order> orders) { … } }
Praktyczna wskazówka: jeśli opisujesz klasę słowem “i” (np. “zapisuje dane I wysyła maila”), SRP jest naruszone.
13Czym jest Open/Closed Principle i jak go stosować w praktyce?średnie+
Kod powinien być otwarty na rozszerzenie, zamknięty na modyfikację. Nowe zachowania dodajemy przez nowe klasy/implementacje, nie przez zmianę istniejącego kodu.
// Naruszenie — każdy nowy typ rabatu wymaga modyfikacji metody
decimal Calculate(Order o, string discountType)
{
if (discountType == “vip”) return o.Total * 0.9m;
if (discountType == “promo”) return o.Total * 0.85m;
// kolejne if’y przy każdej nowej promocji…
return o.Total;
}
// Po OCP — nowy rabat = nowa klasa, stary kod niezmieniony
interface IDiscountPolicy { decimal Apply(decimal total); }
class VipDiscount : IDiscountPolicy { public decimal Apply(decimal t) => t * 0.9m; }
class PromoDiscount : IDiscountPolicy { public decimal Apply(decimal t) => t * 0.85m; }
class OrderService(IDiscountPolicy discount)
{
public decimal Calculate(Order o) => discount.Apply(o.Total);
}
14Co to jest Dependency Inversion Principle i czym różni się od Dependency Injection?średnie+
DIP (zasada) — moduły wysokiego poziomu nie zależą od modułów niskiego poziomu; obie strony zależą od abstrakcji. DI (technika) — mechanizm dostarczania zależności z zewnątrz. DI jest sposobem realizacji DIP.
// Naruszenie DIP — OrderService tworzy konkretną zależność
class OrderService
{
private readonly SqlOrderRepository _repo = new SqlOrderRepository(); // tight coupling!
}
// DIP + DI — zależność od interfejsu, wstrzykiwana z zewnątrz
class OrderService(IOrderRepository repo) // constructor injection
{
public async Task<Order?> GetOrder(int id) => await repo.GetByIdAsync(id);
}
// Rejestracja w kontenerze IoC (ASP.NET Core)
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
Na rozmowie: DIP to zasada projektowa (SOLID), DI to wzorzec implementacyjny. Kontener IoC to narzędzie automatyzujące DI.
15Jaka jest różnica między abstract class a interface? Kiedy użyć którego?łatwe+
Klasa abstrakcyjna może mieć stan, konstruktory i konkretne metody — wyraża relację “jest”. Interfejs definiuje kontrakt zachowania — wyraża relację “potrafi”. Klasa może implementować wiele interfejsów, ale dziedziczyć tylko z jednej klasy.
// Abstract class — wspólna baza + współdzielone zachowanie
abstract class Animal
{
public string Name { get; }
protected Animal(string name) { Name = name; }
public abstract void Speak(); // wymaga nadpisania
public void Breathe() { /* wspólna */ } // dziedziczona
}
// Interface — zdolność/kontrakt (C# 8+ pozwala na domyślne implementacje)
interface ISerializable { string Serialize(); }
// Pies “jest” zwierzęciem ORAZ “potrafi” być serializowany
class Dog : Animal, ISerializable
{
public Dog(string name) : base(name) { }
public override void Speak() => Console.WriteLine(“Woof!”);
public string Serialize() => JsonSerializer.Serialize(this);
}
16Co to jest LSP (Liskov Substitution Principle) i jak sprawdzić, czy jest naruszone?średnie+
Obiekt podklasy powinien być w stanie zastąpić obiekt klasy bazowej bez zmiany poprawności programu. Naruszenie objawia się rzucaniem wyjątków w nadpisanych metodach lub zawężaniem kontraktu.
// Klasyczne naruszenie LSP — Rectangle/Square
class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
public int Area => Width * Height;
}
class Square : Rectangle
{
public override int Width { set { base.Width = base.Height = value; } }
public override int Height { set { base.Width = base.Height = value; } }
}
// Test ujawniający naruszenie
void TestArea(Rectangle r)
{
r.Width = 4;
r.Height = 5;
Debug.Assert(r.Area == 20); // ✅ dla Rectangle, ❌ dla Square (zwraca 25)
}
// Rozwiązanie: nie dziedzicz Square z Rectangle — mają różne kontrakty
Test LSP: czy mogę zastąpić klasę bazową podklasą bez modyfikowania testów jednostkowych napisanych dla klasy bazowej?
17Co to jest ISP (Interface Segregation Principle)?łatwe+
Klient nie powinien być zmuszony do zależności od metod, których nie używa. Duże interfejsy rozbijamy na mniejsze, wyspecjalizowane.
// Naruszenie ISP — “fat interface”
interface IWorker { void Work(); void Eat(); void Sleep(); }
// Robot implementuje interfejs, ale nie je ani nie śpi
class Robot : IWorker
{
public void Work() { /* OK */ }
public void Eat() => throw new NotImplementedException(); // code smell!
public void Sleep()=> throw new NotImplementedException();
}
// Po ISP — segregacja interfejsów
interface IWorkable { void Work(); }
interface IEatable { void Eat(); }
interface ISleepable { void Sleep(); }
class Human : IWorkable, IEatable, ISleepable { /* implementuje wszystkie */ }
class Robot : IWorkable { /* implementuje tylko Work */ }
18Czym jest kompozycja vs dziedziczenie? Kiedy preferować jedno nad drugim?średnie+
Dziedziczenie (is-a) tworzy silne powiązanie hierarchiczne. Kompozycja (has-a) składa obiekt z mniejszych, wymiennych części. Zasada z GoF: “favor composition over inheritance”.
// Dziedziczenie — OK gdy relacja “jest” jest prawdziwa i stabilna
class ElectricCar : Car { /* ElectricCar “jest” Car */ }
// Kompozycja — elastyczność, łatwość testowania i podmiany zależności
class OrderProcessor
{
private readonly IPaymentGateway _payment;
private readonly IInventoryService _inventory;
private readonly IEmailService _email;
public OrderProcessor(IPaymentGateway p, IInventoryService i, IEmailService e)
=> (_payment, _inventory, _email) = (p, i, e);
}
Czerwona flaga: głęboka hierarchia dziedziczenia (3+ poziomy) prawie zawsze sygnalizuje problem projektowy. Spłaszcz hierarchię i zastosuj kompozycję.
19Co to jest klasa sealed i kiedy jej używać?łatwe+
sealed blokuje dziedziczenie z klasy (lub nadpisywanie metody wirtualnej). Daje kompilatorowi i JIT więcej możliwości optymalizacji (devirtualization), dokumentuje intencję projektową i chroni przed nieoczekiwanym rozszerzaniem.
sealed class SqlOrderRepository : IOrderRepository
{
// Nikt nie może dziedziczyć z tej klasy.
// JIT wywołuje metody bezpośrednio (devirtualization = szybciej).
}
// sealed override — blokuje dalsze nadpisywanie
class Base { public virtual void Do() { } }
class Derived : Base { public sealed override void Do() { } }
// class Further : Derived { override void Do() } // ← błąd kompilacji
// Wzorzec: wyjątki domenowe często sealed
sealed class OrderNotFoundException(int orderId)
: Exception($”Order {orderId} not found.”);
20Czym jest override vs new w kontekście metod?średnie+
override — nadpisuje wirtualną metodę klasy bazowej. Wywołanie przez referencję bazową uruchamia wersję z klasy pochodnej (polimorfizm runtime). new — ukrywa metodę bazową. Wywołanie przez referencję bazową uruchamia wersję bazową.
class Animal { public virtual void Speak() => Console.Write(“…”); }
class DogA : Animal { public override void Speak() => Console.Write(“Woof!”); }
class DogB : Animal { public new void Speak() => Console.Write(“Woof!”); }
Animal a1 = new DogA();
Animal a2 = new DogB();
a1.Speak(); // “Woof!” — override: polimorfizm działa
a2.Speak(); // “…” — new: metoda bazowa nie jest nadpisana, tylko ukryta
DogB d = new DogB();
d.Speak(); // “Woof!” — przez konkretny typ działa wersja z DogB
Pułapka:new zamiast override łamie polimorfizm. Używaj new tylko świadomie, gdy celowo chcesz ukryć metodę bez polimorficznego zachowania.
APIASP.NET Core & Web APIQ21–Q30
21Jaki jest cykl życia żądania HTTP w ASP.NET Core? Co to jest middleware?średnie+
Żądanie przechodzi przez pipeline middleware — łańcuch komponentów, z których każdy może przetworzyć żądanie i/lub odpowiedź albo przekazać dalej przez wywołanie next().
// Kolejność middleware ma krytyczne znaczenie!
app.UseExceptionHandler(“/error”); // musi być pierwszy
app.UseHttpsRedirection();
app.UseAuthentication(); // kim jesteś?
app.UseAuthorization(); // czy masz dostęp?
app.UseRateLimiter();
app.MapControllers(); // routing + akcje
// Własny middleware
public class TimingMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext ctx)
{
var sw = Stopwatch.StartNew();
await next(ctx);
sw.Stop();
ctx.Response.Headers[“X-Elapsed-Ms”] = sw.ElapsedMilliseconds.ToString();
}
}
22Różnica między Transient, Scoped i Singleton w DI?łatwe+
Transient — nowa instancja przy każdym pobraniu z kontenera. Scoped — jedna instancja na żądanie HTTP. Singleton — jedna instancja przez całe życie aplikacji.
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>(); // bezstanowe
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>(); // DbContext, UoW
builder.Services.AddSingleton<IConfiguration>(config); // cache, konfiguracja
// PUŁAPKA: Captive Dependency
// Singleton NIE powinien bezpośrednio zależeć od Scoped/Transient.
// Kontener IoC rzuci InvalidOperationException lub Scoped będzie wyciekał.
class BadSingleton(IScopedService scoped) { } // ❌ Scoped “uwięziony” w Singleton
Captive Dependency — najczęstszy błąd DI na rozmowach. Singleton wstrzykuje Scoped → Scoped żyje tak długo jak Singleton. DbContext w Singleton to klasyczny przykład wycieku.
23Jak zwracać właściwe kody HTTP z kontrolera Web API?łatwe+
Używamy IActionResult / ActionResult<T> i pomocniczych metod z ControllerBase.
[ApiController]
[Route(“api/[controller]”)]
public class OrdersController(IOrderService service) : ControllerBase
{
[HttpGet(“{id}”)]
public async Task<ActionResult<OrderDto>> Get(int id)
{
var order = await service.GetByIdAsync(id);
if (order is null)
return NotFound(new { message = $”Order {id} not found.” }); // 404
return Ok(order); // 200 + JSON
}
[HttpPost]
public async Task<ActionResult<OrderDto>> Create(CreateOrderRequest req)
{
var order = await service.CreateAsync(req);
return CreatedAtAction(nameof(Get), new { id = order.Id }, order); // 201
}
[HttpDelete(“{id}”)]
public async Task<IActionResult> Delete(int id)
{
await service.DeleteAsync(id);
return NoContent(); // 204
}
}
24Co to jest walidacja modelu w ASP.NET Core i jak ją rozszerzyć?łatwe+
[ApiController] automatycznie zwraca 400 z detalami, gdy ModelState.IsValid == false. Data Annotations to proste atrybuty wbudowane w .NET — dla złożonej walidacji używamy FluentValidation.
// Data Annotations — proste, wbudowane
public class CreateOrderRequest
{
[Required]
[StringLength(100, MinimumLength = 3)]
public string ProductName { get; set; } = “”;
[Range(1, 10000)]
public decimal Amount { get; set; }
}
// FluentValidation — kompleksowa, testowalna walidacja
public class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
public CreateOrderValidator()
{
RuleFor(x => x.ProductName).NotEmpty().Length(3, 100);
RuleFor(x => x.Amount).GreaterThan(0).LessThanOrEqualTo(10000);
}
}
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();
25Jak działa autentykacja JWT w ASP.NET Core?średnie+
JWT (JSON Web Token) składa się z trzech części: header.payload.signature. Serwer generuje token po zalogowaniu użytkownika; klient wysyła go w nagłówku Authorization: Bearer <token>. ASP.NET Core weryfikuje podpis i claims automatycznie.
// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new()
{
ValidateIssuer = true, ValidateAudience = true,
ValidateLifetime = true, ValidateIssuerSigningKey = true,
ValidIssuer = config[“Jwt:Issuer”],
ValidAudience = config[“Jwt:Audience”],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(config[“Jwt:Key”]!))
};
});
// Generowanie tokenu (np. w LoginHandler)
var credentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config[“Jwt:Key”]!)),
SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: config[“Jwt:Issuer”], audience: config[“Jwt:Audience”],
claims: [new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())],
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: credentials);
string tokenString = new JwtSecurityTokenHandler().WriteToken(token);
26Co to jest IHostedService i BackgroundService?średnie+
IHostedService to interfejs dla usług startujących i zatrzymujących się razem z aplikacją. BackgroundService to klasa bazowa upraszczająca implementację długo działających workerów.
public class OrderCleanupWorker(
IServiceScopeFactory scopeFactory,
ILogger<OrderCleanupWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = scopeFactory.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
await repo.DeleteExpiredAsync();
logger.LogInformation(“Cleanup at {time}”, DateTimeOffset.UtcNow);
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
}
}
builder.Services.AddHostedService<OrderCleanupWorker>();
Wzorzec: hosted services są rejestrowane jako Singleton — zależności Scoped (jak DbContext) wstrzykuj przez IServiceScopeFactory, nigdy bezpośrednio.
27Co to jest Options Pattern i dlaczego jest lepszy od IConfiguration?średnie+
Options Pattern silnie typuje konfigurację, umożliwia walidację przy starcie i ułatwia testowanie — nie przekazujemy surowego IConfiguration w głąb kodu.
// appsettings.json: { “Email”: { “Host”: “smtp.gmail.com”, “Port”: 587 } }
public class EmailOptions
{
public const string Section = “Email”;
[Required] public string Host { get; set; } = “”;
[Range(1, 65535)] public int Port { get; set; }
}
builder.Services
.AddOptions<EmailOptions>()
.BindConfiguration(EmailOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart(); // błędna konfiguracja = crash przy starcie, nie przy pierwszym użyciu
class EmailService(IOptions<EmailOptions> opts)
{
// IOptions — stała wartość, nie reaguje na zmiany pliku
// IOptionsSnapshot — reload per-request (Scoped)
// IOptionsMonitor — reload on-the-fly (Singleton)
}
28Jak działa Problem Details i jak ujednolicić obsługę błędów w API?średnie+
RFC 9457 (Problem Details) to standard odpowiedzi błędów HTTP API. ASP.NET Core 8 wprowadził wbudowany globalny exception handler, zastępując ręczne middleware z try/catch.
// Program.cs (ASP.NET Core 8+)
builder.Services.AddProblemDetails();
app.UseExceptionHandler(); // globalny handler
app.UseExceptionHandler(x => x.Run(async ctx =>
{
var ex = ctx.Features.Get<IExceptionHandlerFeature>()?.Error;
var pd = ex switch
{
NotFoundException e => new ProblemDetails { Status = 404, Title = e.Message },
ValidationException e => new ProblemDetails { Status = 422, Title = “Validation failed” },
_ => new ProblemDetails { Status = 500, Title = “Internal server error” }
};
ctx.Response.StatusCode = pd.Status ?? 500;
await ctx.Response.WriteAsJsonAsync(pd);
}));
// Odpowiedź zgodna z RFC 9457:
// { “type”: “…”, “title”: “…”, “status”: 404, “detail”: “…” }
29Co to są Minimal APIs i kiedy użyć ich zamiast kontrolerów?łatwe+
Minimal APIs (ASP.NET Core 6+) rejestrują endpointy bezpośrednio w Program.cs bez kontrolerów. Mniej boilerplate, lepsza wydajność, świetne dla mikrousług i małych API.
Kiedy kontroler? Duże API z wieloma akcjami i filtrami. Kiedy Minimal API? Proste CRUD, mikrousługa, prototyp — mniej plików, szybszy start.
30Co to jest CORS i jak go skonfigurować w ASP.NET Core?łatwe+
CORS (Cross-Origin Resource Sharing) — przeglądarka blokuje żądania JavaScript do innej domeny. Serwer musi jawnie zezwolić przez nagłówki odpowiedzi.
builder.Services.AddCors(opt =>
{
opt.AddPolicy(“Frontend”, policy =>
policy.WithOrigins(“https://app.dev-hobby.pl”, “http://localhost:4200”)
.AllowAnyMethod().AllowAnyHeader()
.AllowCredentials()); // wymagane przy cookies/Authorization header
opt.AddPolicy(“Public”, policy =>
policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});
// Middleware — MUSI być przed MapControllers
app.UseCors(“Frontend”);
app.MapGet(“/public”, handler).RequireCors(“Public”);
Pułapka:AllowAnyOrigin() + AllowCredentials() = błąd runtime. Przeglądarki nie wysyłają danych uwierzytelniających do wildcard origin.
DBEntity Framework Core & Bazy DanychQ31–Q37
31Co to jest problem N+1 w EF Core i jak go unikać?średnie+
Problem N+1 — zamiast jednego zapytania z JOIN, wykonujemy 1 zapytanie po listę + N dodatkowych zapytań dla każdego elementu. Demoluje wydajność przy większych danych.
// Problem — N+1: 1 query po Orders + 1 query PER zamówienie
var orders = await ctx.Orders.ToListAsync();
foreach (var order in orders)
Console.WriteLine(order.Customer.Name); // lazy load → dodatkowy SELECT!
// Fix 1 — Eager Loading (Include)
var orders = await ctx.Orders
.Include(o => o.Customer).Include(o => o.Items)
.ToListAsync(); // JEDEN SQL z JOIN
// Fix 2 — Select (projekcja) — pobierz tylko to, co potrzebne
var dtos = await ctx.Orders
.Select(o => new OrderDto(o.Id, o.Customer.Name, o.Items.Count))
.ToListAsync();
// Fix 3 — Split query (dla złożonych Include’ów)
var orders = await ctx.Orders
.Include(o => o.Items).ThenInclude(i => i.Product)
.AsSplitQuery() // osobne SQL zamiast kartezjańskiego JOIN
.ToListAsync();
Jak wykryć? Włącz logowanie SQL: optionsBuilder.LogTo(Console.WriteLine). Zobaczysz każdy SELECT.
32Czym są migracje w EF Core i jak zarządzać nimi na produkcji?łatwe+
Migracje to kod C# opisujący zmiany schematu bazy danych. Generowane automatycznie z modelu, mogą być wykonywane przez EF lub ręcznie jako skrypt SQL.
dotnet ef migrations add AddOrderStatusColumn
dotnet ef database update
// Generowanie skryptu SQL (bezpieczne na produkcji)
dotnet ef migrations script –idempotent -o migration.sql
// Automatyczne migracje przy starcie — TYLKO dev/staging, NIE na produkcji!
using var scope = app.Services.CreateScope();
await scope.ServiceProvider
.GetRequiredService<AppDbContext>()
.Database.MigrateAsync();
Produkcja: generuj idempotent script, przejrzyj z DBA, wykonaj w transakcji, zrób backup przed migracją. Nigdy MigrateAsync() bezpośrednio na produkcji.
33Co to jest AsNoTracking() i kiedy go używać?łatwe+
Domyślnie EF Core śledzi zmiany w załadowanych encjach (Change Tracker). AsNoTracking() wyłącza śledzenie — szybsze zapytania, mniejsze zużycie pamięci. Stosuj dla operacji tylko do odczytu.
// Read-only — śledzenie niepotrzebne
var orders = await ctx.Orders
.AsNoTracking()
.Where(o => o.IsActive)
.ToListAsync(); // ~30% szybciej, mniejszy footprint pamięci
// Globalne ustawienie dla całego kontekstu
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
// Tracking jest potrzebny gdy zapisujesz zmiany:
var order = await ctx.Orders.FindAsync(id); // ← tracked
order.Status = “Completed”;
await ctx.SaveChangesAsync();
34Jak działa DbContext i dlaczego powinien być Scoped?średnie+
DbContext nie jest thread-safe i reprezentuje wzorzec Unit of Work — śledzi zmiany w ramach jednej operacji biznesowej. Scoped = jeden kontekst na żądanie HTTP.
// Domyślnie Scoped
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlServer(connectionString));
// NIE używaj Singleton — współdzielony stan między równoległymi żądaniami
// NIE używaj Transient — każde repository dostaje inny kontekst (brak UoW)
// Scoped gwarantuje ten SAM DbContext w całym żądaniu:
public class OrderRepository(AppDbContext ctx) : IOrderRepository { }
public class CustomerRepository(AppDbContext ctx) : ICustomerRepository { }
// ctx w obu = ten sam obiekt dzięki Scoped lifetime
35Co to jest Repository Pattern i czy zawsze warto go stosować z EF Core?średnie+
Repository abstrakcjonizuje dostęp do danych — ułatwia testowanie i pozwala zmienić ORM bez dotykania logiki biznesowej. Kontrowersja: DbContext sam realizuje już wzorce Unit of Work i Repository.
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(int id, CancellationToken ct = default);
Task<List<Order>> GetActiveAsync(CancellationToken ct = default);
void Add(Order order);
void Remove(Order order);
}
public class SqlOrderRepository(AppDbContext ctx) : IOrderRepository
{
public Task<Order?> GetByIdAsync(int id, CancellationToken ct)
=> ctx.Orders.AsNoTracking().FirstOrDefaultAsync(o => o.Id == id, ct);
public void Add(Order order) => ctx.Orders.Add(order);
// SaveChangesAsync wywołuje Unit of Work / Service layer
}
Kompromis: dla małych projektów bezpośredni DbContext w serwisach jest akceptowalny. Dla dużych — Repository + Unit of Work dają testowalność i izolację warstwy danych.
36Kiedy użyć raw SQL zamiast LINQ w EF Core?średnie+
LINQ jest pierwszym wyborem. Raw SQL stosujemy gdy: LINQ generuje nieefektywne zapytanie, potrzebujemy zaawansowanych funkcji SQL (CTE, window functions) lub optymalizujemy krytyczną ścieżkę wydajnościową.
// EF Core 7+ — FromSql z interpolacją (parametryzowane, bezpieczne na SQL Injection)
var orders = await ctx.Orders
.FromSql($”SELECT * FROM Orders WHERE CustomerId = {customerId}”)
.ToListAsync();
// ExecuteSqlRaw — DML (INSERT/UPDATE/DELETE)
await ctx.Database.ExecuteSqlRawAsync(
“UPDATE Orders SET Status = {0} WHERE CreatedAt < {1}”,
“Archived”, DateTime.UtcNow.AddYears(-2));
// Dapper — pełna kontrola SQL + projekcja na dowolne DTO
using var conn = ctx.Database.GetDbConnection();
var dtos = await conn.QueryAsync<OrderSummaryDto>(
“SELECT o.Id, c.Name, SUM(i.Price) AS Total FROM Orders o …”);
Nigdy nie konkatenuj stringa z danymi użytkownika w SQL. Zawsze parametryzuj — EF robi to automatycznie przy interpolacji $"...{variable}" w FromSql.
37Co to jest optymistyczne i pesymistyczne blokowanie współbieżności?trudne+
Optimistic Locking — zakładamy brak konfliktu, weryfikujemy przy zapisie przez RowVersion/ETag. Pessimistic Locking — blokujemy rekord na czas transakcji (SELECT FOR UPDATE). EF Core wspiera optymistyczne blokowanie natywnie przez [Timestamp].
public class Order
{
public int Id { get; set; }
[Timestamp] // EF doda WHERE RowVersion = @original w UPDATE
public byte[] RowVersion { get; set; } = null!;
}
try
{
await ctx.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var dbValues = await entry.GetDatabaseValuesAsync();
// Decyzja: odrzuć, scal lub nadpisz — zależy od reguł biznesowych
throw new ConflictException(“Zamówienie zostało zmienione przez innego użytkownika.”);
}
ARCWzorce Projektowe & ArchitekturaQ38–Q43
38Wyjaśnij wzorzec Repository + Unit of Work.średnie+
Repository izoluje dostęp do danych. Unit of Work grupuje wiele operacji w jedną atomową transakcję — albo wszystkie się udają, albo wszystkie są cofane.
public interface IUnitOfWork : IDisposable
{
IOrderRepository Orders { get; }
ICustomerRepository Customers { get; }
Task<int> SaveChangesAsync(CancellationToken ct = default);
}
public class EfUnitOfWork(AppDbContext ctx) : IUnitOfWork
{
public IOrderRepository Orders { get; } = new SqlOrderRepository(ctx);
public ICustomerRepository Customers { get; } = new SqlCustomerRepository(ctx);
public Task<int> SaveChangesAsync(CancellationToken ct) => ctx.SaveChangesAsync(ct);
public void Dispose() => ctx.Dispose();
}
async Task PlaceOrder(CreateOrderRequest req)
{
var customer = await uow.Customers.GetByIdAsync(req.CustomerId);
var order = Order.Create(customer, req.Items);
uow.Orders.Add(order);
await uow.SaveChangesAsync(); // jeden commit dla całej operacji
}
39Co to jest wzorzec Mediator i jak go używać z MediatR?średnie+
Mediator oddziela nadawców od odbiorców — żądania/zdarzenia przechodzą przez centralny bus. MediatR + CQRS to fundament Clean Architecture w ekosystemie .NET.
// Command (mutuje stan)
public record CreateOrderCommand(int CustomerId, List<OrderItem> Items)
: IRequest<OrderDto>;
// Handler — cała logika biznesowa w jednym miejscu
public class CreateOrderHandler(IUnitOfWork uow, IMapper mapper)
: IRequestHandler<CreateOrderCommand, OrderDto>
{
public async Task<OrderDto> Handle(CreateOrderCommand cmd, CancellationToken ct)
{
var order = Order.Create(cmd.CustomerId, cmd.Items);
uow.Orders.Add(order);
await uow.SaveChangesAsync(ct);
return mapper.Map<OrderDto>(order);
}
}
// Controller — cienki, deleguje logikę do Mediatora
[HttpPost]
public Task<ActionResult<OrderDto>> Create(CreateOrderCommand cmd, IMediator mediator)
=> mediator.Send(cmd);
40Co to jest Clean Architecture i jakie są jej warstwy?trudne+
Clean Architecture (Robert C. Martin) organizuje kod w koncentryczne warstwy z regułą zależności (Dependency Rule): zależności wskazują wyłącznie do środka. Warstwy wewnętrzne nie wiedzą nic o zewnętrznych.
// Warstwy (od środka do zewnątrz):
// Domain — Entities, Value Objects, Domain Events; zero zewnętrznych zależności
// Application — Use Cases, Commands, Queries, Interfejsy; zależy od Domain
// Infrastructure — EF Core, Email, Storage; implementuje interfejsy z Application
// Presentation — Controllers, Minimal API; zależy od Application
// src/
// MyApp.Domain/ Entities/Order.cs, Events/OrderPlaced.cs
// MyApp.Application/ Orders/Commands/CreateOrderCommand.cs
// Interfaces/IOrderRepository.cs
// MyApp.Infrastructure/ Persistence/SqlOrderRepository.cs
// MyApp.Api/ Controllers/OrdersController.cs
// Infrastructure zależy od Application (nie odwrotnie):
class SqlOrderRepository : IOrderRepository // IOrderRepository żyje w Application!
Na rozmowie: wystarczy, że rozumiesz Dependency Rule i potrafisz uzasadnić, dlaczego logika biznesowa nie powinna wiedzieć o SQL ani HTTP.
41Co to jest wzorzec Factory i kiedy go używać?łatwe+
Factory enkapsuluje logikę tworzenia obiektów. Używamy gdy: tworzenie jest złożone lub zawiera reguły biznesowe, musimy wybrać konkretny typ w runtime, lub chcemy ukryć szczegóły konstruktora.
public class Order
{
private Order() { } // prywatny konstruktor — wymuszamy użycie metody fabrycznej
public static Order Create(Customer customer, IEnumerable<OrderItem> items)
{
if (!customer.IsActive) throw new DomainException(“Nieaktywny klient”);
if (!items.Any()) throw new DomainException(“Brak pozycji w zamówieniu”);
return new Order
{
CustomerId = customer.Id,
Items = items.ToList(),
Status = OrderStatus.Pending,
CreatedAt = DateTime.UtcNow
};
}
}
// Abstract Factory — rodzina powiązanych obiektów
public interface INotificationFactory
{
IEmailSender CreateEmailSender();
ISmsSender CreateSmsSender();
}
42Czym jest wzorzec Decorator i gdzie jest stosowany w .NET?średnie+
Decorator owija istniejący obiekt, dodając nowe zachowanie bez modyfikowania klasy bazowej. Doskonały do cross-cutting concerns: caching, logowanie, retry, pomiar czasu.
public class CachedOrderRepository(
IOrderRepository inner,
IMemoryCache cache) : IOrderRepository
{
public async Task<Order?> GetByIdAsync(int id, CancellationToken ct)
{
var key = $”order_{id}”;
if (cache.TryGetValue(key, out Order? cached)) return cached;
var order = await inner.GetByIdAsync(id, ct);
if (order is not null)
cache.Set(key, order, TimeSpan.FromMinutes(5));
return order;
}
// pozostałe metody delegują do inner
}
// Rejestracja dekoratora w DI
builder.Services.AddScoped<SqlOrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
new CachedOrderRepository(
sp.GetRequiredService<SqlOrderRepository>(),
sp.GetRequiredService<IMemoryCache>()));
43Co to jest Result Pattern i kiedy jest lepszy od wyjątków?trudne+
Result Pattern jawnie komunikuje sukces lub błąd przez typ zwracany, zamiast rzucać wyjątki dla przewidywalnych scenariuszy. Wyjątki są kosztowne i utrudniają śledzenie przepływu sterowania.
public class Result<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string? Error { get; }
private Result(T value) { IsSuccess = true; Value = value; }
private Result(string error) { IsSuccess = false; Error = error; }
public static Result<T> Ok(T value) => new(value);
public static Result<T> Fail(string msg) => new(msg);
}
public async Task<Result<OrderDto>> CreateOrderAsync(CreateOrderRequest req)
{
var customer = await repo.GetCustomerAsync(req.CustomerId);
if (customer is null)
return Result<OrderDto>.Fail($”Klient {req.CustomerId} nie istnieje”);
if (!customer.HasCreditLimit(req.Total))
return Result<OrderDto>.Fail(“Przekroczony limit kredytowy”);
return Result<OrderDto>.Ok(dto);
}
// Controller
var result = await service.CreateOrderAsync(req);
return result.IsSuccess ? Ok(result.Value) : BadRequest(result.Error);
Kiedy wyjątki? Sytuacje naprawdę wyjątkowe: brak połączenia z DB, błąd I/O. Kiedy Result? Oczekiwane błędy biznesowe: brak encji, naruszenie reguły domenowej.
TESTTestowanieQ44–Q47
44Co to jest test jednostkowy vs integracyjny vs e2e? Kiedy który?łatwe+
Unit test — testuje izolowaną jednostkę kodu, zależności mockowane. Szybki, deterministyczny. Integration test — testuje współpracę komponentów (np. kod + baza danych). E2E — symuluje użytkownika przez cały system (Playwright, Selenium).
// Unit test — xUnit + FluentAssertions + NSubstitute
public class OrderServiceTests
{
private readonly IOrderRepository _repo = Substitute.For<IOrderRepository>();
private readonly OrderService _sut;
public OrderServiceTests() => _sut = new OrderService(_repo);
[Fact]
public async Task CreateOrder_WhenCustomerInactive_ThrowsDomainException()
{
// Arrange
var customer = new Customer { IsActive = false };
_repo.GetByIdAsync(Arg.Any<int>()).Returns(customer);
// Act
var act = () => _sut.CreateOrderAsync(new CreateOrderRequest());
// Assert
await act.Should().ThrowAsync<DomainException>()
.WithMessage(“*nieaktywny*”);
}
}
Test Pyramid: dużo unit testów (fast, cheap), mniej integracyjnych, bardzo mało e2e (slow, expensive). Złota proporcja: 70/20/10.
45Co to jest mockowanie i jaka jest różnica między Mock, Stub a Fake?średnie+
Stub — zwraca predefiniowane dane, nie weryfikuje wywołań. Mock — weryfikuje, że metoda została wywołana z właściwymi argumentami. Fake — działająca, uproszczona implementacja (np. baza in-memory).
using NSubstitute;
var repo = Substitute.For<IOrderRepository>();
// Stub — konfiguracja zwracanej wartości
repo.GetByIdAsync(1).Returns(new Order { Id = 1 });
// Weryfikacja wywołania (mock behavior)
await svc.DeleteOrderAsync(1);
await repo.Received(1).DeleteAsync(Arg.Is<Order>(o => o.Id == 1));
// Fake — działająca implementacja in-memory
public class InMemoryOrderRepository : IOrderRepository
{
private readonly List<Order> _orders = [];
public Task<Order?> GetByIdAsync(int id, CancellationToken _)
=> Task.FromResult(_orders.FirstOrDefault(o => o.Id == id));
public void Add(Order o) => _orders.Add(o);
}
46Jak testować Web API integracyjnie w ASP.NET Core?trudne+
WebApplicationFactory<T> uruchamia in-process prawdziwy serwer ASP.NET Core — pełny pipeline middleware, DI, routing. Bez mockowania HTTP. Bazę danych wymieniamy na in-memory lub Testcontainers.
public class OrdersApiTests(WebApplicationFactory<Program> factory)
: IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client = factory
.WithWebHostBuilder(b =>
b.ConfigureServices(svc =>
svc.AddDbContext<AppDbContext>(opt =>
opt.UseInMemoryDatabase(“TestDb”))))
.CreateClient();
[Fact]
public async Task GET_Orders_ReturnsOk()
{
var response = await _client.GetAsync(“/api/orders”);
response.StatusCode.Should().Be(HttpStatusCode.OK);
var orders = await response.Content.ReadFromJsonAsync<List<OrderDto>>();
orders.Should().NotBeNull();
}
}
47Co to jest AAA (Arrange-Act-Assert) i jak pisać czytelne testy?łatwe+
AAA to struktura testu: przygotuj dane → wykonaj akcję → zweryfikuj wynik. Czytelny test = żywa dokumentacja zachowania kodu.
[Theory]
[InlineData(100, 0.1, 90)] // kwota, procent rabatu, oczekiwany wynik
[InlineData(200, 0.2, 160)]
public void ApplyDiscount_ReturnsCorrectTotal(
decimal total, double discount, decimal expected)
{
// Arrange
var order = new Order { Total = total };
var policy = new PercentageDiscount(discount);
// Act
var result = policy.Apply(order.Total);
// Assert
result.Should().Be(expected);
}
// Konwencja nazewnictwa: MethodName_Scenario_ExpectedBehavior
// void PlaceOrder_WhenInsufficientStock_ThrowsOutOfStockException()
FluentAssertions poprawiają czytelność i komunikaty błędów: result.Should().Be(90) zamiast Assert.Equal(90, result).
GITGit, DevOps & Dobre PraktykiQ48–Q50
48Jaka jest różnica między git merge a git rebase?średnie+
merge łączy gałęzie, zachowując pełną historię (tworzy merge commit). rebase przepisuje historię tak, jakby branch był stworzony z aktualnego HEAD — liniowa, czysta historia.
# Merge — zachowuje historię, bezpieczny
git checkout main
git merge feature/orders
# Tworzy merge commit widoczny w historii
# Rebase — czysta, liniowa historia
git checkout feature/orders
git rebase main
# Przenosi commity feature na wierzchołek main, potem fast-forward
# Złota zasada rebase:
# NIE rebasuj commitów już opublikowanych na współdzielone branche!
# Rebase stosuj tylko na prywatnych/lokalnych branchach.
W praktyce: rebase feature branch na main przed PR = czysta historia. Merge na main = zawsze (zachowuje punkt integracji).
49Co to jest CI/CD i jak wygląda typowy pipeline w .NET?średnie+
CI (Continuous Integration) — automatyczna weryfikacja kodu przy każdym push (build + testy). CD (Continuous Delivery/Deployment) — automatyczne wdrożenie do środowisk po przejściu CI.
50Jak podejść do code review — co sprawdzasz i jak dawać feedback?łatwe+
Code review to nie polowanie na błędy — to dzielenie się wiedzą i podnoszenie jakości całego zespołu. Dobry review patrzy na czytelność, testy, bezpieczeństwo i zgodność z architekturą.
// Checklist — co sprawdzam w każdym review:
// ✅ Czy kod jest czytelny bez komentarzy? (self-documenting code)
// ✅ Czy nazwy metod i zmiennych wyrażają intencję?
// ✅ Czy są testy — i czy testują właściwe scenariusze?
// ✅ Czy brak N+1, nieobsłużonych wyjątków, podatności SQL Injection?
// ✅ Czy nie ma zbędnych zależności i over-engineeringu?
// ✅ Czy zmiana jest zgodna z architekturą projektu?
// ✅ Czy żadne sekrety/klucze API nie trafiły do kodu?
// Jak dawać konstruktywny feedback:
// ❌ “To jest złe.”
// ✅ “Rozważyłbym tu IEnumerable zamiast List — zewnętrzny kod
// nie powinien modyfikować wewnętrznej kolekcji. Co sądzisz?”
// Prefiksy komentarzy (Conventional Comments):
// nit: drobiazg, nieblokujące
// suggestion: propozycja, nieblokujące
// issue: rzeczywisty problem, blokuje merge
// question: pytanie, nieoceniające
Na rozmowie: pokaż, że traktujesz review jako rozmowę, nie osąd. Pytasz “dlaczego?” zanim zaproponujesz zmianę — to dojrzałość dobrego teamplayera.
Logowanie
The password must have a minimum of 8 characters of numbers and letters, contain at least 1 capital letter
Wyrażam zgodę na przechowywanie i przetwarzanie moich danych przez tę witrynę.
Polityka prywatności