🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

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.TryParse vs double.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 double przez zero w C# zwraca Infinity — 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.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak COIG, Kamsoft czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

1 comment

Dodaj komentarz

czytanie to początek

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ę →