Prosty Kalkulator w C# — Zadanie rekrutacyjne #2

Czego szuka rekruter: walidacja wejścia, obsługa błędów, ekstrakcja logiki do metod, rozszerzalność
Poziom: Junior
Zadanie
Napisz kalkulator obsługujący cztery podstawowe operacje: +, -, *, /. Program przyjmuje dwie liczby i operację, zwraca wynik. Uwzględnij przypadek dzielenia przez zero.
Czego szuka rekruter
Kalkulator to zadanie o obsłudze błędów i walidacji — nie o matematyce. Rekruter sprawdza:
- Czy kandydat waliduje wejście przed parsowaniem (
double.TryParsevsdouble.Parse) - Jak obsługuje dzielenie przez zero
- Czy wydziela logikę do osobnych metod
- Czy myśli o rozszerzalności (co gdyby dodać
%lub^?)
Najczęstszy błąd: int.Parse(Console.ReadLine()) — rzuca FormatException gdy użytkownik wpisze literę. Rekruter celowo wpisuje “abc” żeby sprawdzić czy program się wysypie.
Edge cases do przemyślenia
- Dzielenie przez zero — wynik matematycznie nieokreślony
- Dzielenie
doubleprzez zero w C# zwracaInfinity— to nie jest błąd rzucający wyjątek - Parsowanie tekstu jako liczby — użytkownik może wpisać “1,5” zamiast “1.5” (locale)
- Nieznany operator — co zwrócić?
Rozwiązanie — wersja z czystą ekstrakcją logiki
static double Calculate(double a, double b, char op)
{
return op switch
{
'+' => a + b,
'-' => a - b,
'*' => a * b,
'/' => b == 0
? throw new DivideByZeroException("Dzielenie przez zero jest niedozwolone.")
: a / b,
_ => throw new ArgumentException($"Nieznany operator: {op}")
};
}
static bool TryReadDouble(string prompt, out double value)
{
Console.Write(prompt);
return double.TryParse(
Console.ReadLine(),
System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture,
out value);
}
static void Main()
{
if (!TryReadDouble("Pierwsza liczba: ", out double a))
{
Console.WriteLine("Nieprawidłowa liczba.");
return;
}
Console.Write("Operator (+, -, *, /): ");
var opStr = Console.ReadLine()?.Trim();
if (opStr?.Length != 1)
{
Console.WriteLine("Nieprawidłowy operator.");
return;
}
if (!TryReadDouble("Druga liczba: ", out double b))
{
Console.WriteLine("Nieprawidłowa liczba.");
return;
}
try
{
double result = Calculate(a, b, opStr[0]);
Console.WriteLine($"Wynik: {result}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Błąd: {ex.Message}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"Błąd: {ex.Message}");
}
}
Alternatywa — pattern z delegatami (rozszerzalna)
static readonly Dictionary<char, Func<double, double, double>> Operations = new()
{
['+'] = (a, b) => a + b,
['-'] = (a, b) => a - b,
['*'] = (a, b) => a * b,
['/'] = (a, b) => b == 0
? throw new DivideByZeroException()
: a / b,
};
// Dodanie nowej operacji bez modyfikacji istniejącego kodu:
// Operations['^'] = (a, b) => Math.Pow(a, b);
Testy jednostkowe
[Theory]
[InlineData(10, 3, '+', 13)]
[InlineData(10, 3, '-', 7)]
[InlineData(10, 3, '*', 30)]
[InlineData(10, 2, '/', 5)]
public void Calculate_ReturnsCorrectResult(double a, double b, char op, double expected)
{
Assert.Equal(expected, Calculate(a, b, op));
}
[Fact]
public void Calculate_DivisionByZero_ThrowsDivideByZeroException()
{
Assert.Throws<DivideByZeroException>(() => Calculate(10, 0, '/'));
}
[Fact]
public void Calculate_UnknownOperator_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => Calculate(10, 5, '%'));
}
Co powiedzieć na rozmowie
“Używam double.TryParse zamiast double.Parse żeby nie rzucać wyjątku przy błędnym wejściu — wyjątki powinny być dla naprawdę wyjątkowych sytuacji, nie dla przewidywalnych błędów użytkownika. Logikę obliczeń wydzielam do osobnej metody Calculate żeby była testowalna bez Console.”
Podsumowanie
Kalkulator testuje głównie walidację wejścia i separację odpowiedzialności. Logika matematyczna to trywialny element — kluczowe jest żeby Calculate był testowalny w izolacji od Console.
1 comment
Dodaj komentarz
Musisz się zalogować, aby móc dodać komentarz.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
Bardzo czytelny kod bez zbędnego pitolenia 🙂