# System.IO - abstrakcja czy iluzja?

# Wprowadzenie

Pakiet `System.IO` dla .NET znacznie uproszcza zarządzanie plikami. Przykładowo, pozwala kopiować je jedną linijką kodu. Działa świetnie na lokalnym dysku. Niestety, gdy plik leży na zdalnym serwerze czy network share – proste API `System.IO` przestaje być tylko wygodną abstrakcją i zaczyna przypominać iluzję. Ukrywa bowiem rzeczywiste wyzwania integracji między systemami.

# Złożoność (complexity) schowana w `System.IO`

`System.IO` to prawdziwy triumf abstrakcji. Jeden prosty interfejs do zarządzania plikami:

```csharp
string content = File.ReadAllText("dokument.txt");
File.WriteAllText("kopia.txt", content);
```

Pod fasadą kryje się cała masa skomplikowanych operacji których nie chcemy obsługiwać tworząc nowe rozwiązania dla biznesu. Między innymi należą do nich:

* automatyczne wykrywanie kodowania znaków,
    
* buforowanie danych w pamięci,
    
* zarządzanie uchwytami do plików (handles),
    
* synchronizacja dostępu między procesami.
    

Wszystko działa świetnie w lokalnym środowisku Windows. Iluzja pojawia się, gdy używamy `System.IO` z zasobami przechowywanymi w sieci. Jest to nowa złożoność, której nie da się już zaprogramować na jeden sposób za prostą fasadą. Obsługa jej wymaga dodatkowej, decyzyjnej logiki od programisty.

# Iluzja `System.IO` w sieci

Proste API `System.IO` sprawia wrażenie, że operacje na plikach są zawsze przewidywalne. Jednak w przypadku zasobów sieciowych to założenie z abstrakcji szybko zamienia się w iluzję.

### Podstawowe operacje na pliku

Na lokalnym dysku zapis czy odczyt zwykle są szybkie i niezawodne:

```csharp
// Zapis pliku – zwykle szybka operacja lokalnie
File.WriteAllText("plik.txt", content);
```

Na udziale sieciowym ta sama instrukcja może trwać sekundy lub nawet minuty. Pod spodem nie ma już zapisu na lokalny dysk tylko faktyczna transmisja danych przez protokół – SMB, NFS, FTP czy inny mechanizm udostępniania plików. To one decydują o tym, czy zapis będzie atomowy, czy zerwie się w połowie, jak obsłużone będą blokady i w jaki sposób raportowane są błędy. Fasada ukrywa całą tę złożoność, dając iluzję prostoty.

## Zbyt długi czas operacji

W sieci, nawet prosta operacja może zająć więcej czasu. By zabezpieczyć system przed takimi przypadkami warto użyć metod asynchronicznych. W nich można przekazać CancellationToken i w ten sposób kontrolować timeout:

```csharp
// Zapis pliku metodą asynchroniczną - obsługa części problemów przez przekazanie cancellationToken
cts.CancelAfter(3500);
WriteAllTextAsync("plik.text", content, cts.CancellationToken);
```

Użycie async nie rozwiązuje jednak problemu zanikającego połączenia czy potrzeby ponowienia operacji.

## Ponowienie i zapis pośredni

Retry w przypadku niestabilnego połączenia to standard w komunikacji HTTP – w .NET zapewnia to np. biblioteka Polly. W przypadku integracji file transfer mechanizmy ponowienia pozostają jednak w gestii programisty.

Przykładowa implementacja prostego retry:

```csharp
// Przykładowa implementacja ponawiania zapisu
int retries = 3;
for (int i = 0; i < retries; i++)
{
    try
    {
        File.WriteAllText(@"\\server\plik.txt", content);
        break;
    }
    catch (IOException) when (i < retries - 1)
    {
        Thread.Sleep(1000); // prosty backoff
    }
}
```

Drugim częstym wyzwaniem w protokołach SMB, NFS czy FTP jest brak jednoznacznej informacji o kompletności pliku. Jeśli drugi system reaguje na pojawienie się nowego zasobu na dysku sieciowym, często robi to jeszcze przed pełnym zakończeniem zapisu i zwolnieniem pliku. Prowadzi to do wyjątków przy próbie odczytu.

Typowym rozwiązaniem jest tworzenie pliku “.done” po zakończeniu operacji lub zapis do pliku tymczasowego “.tmp“, a dopiero na koniec zmiana nazwy na docelową:

```csharp
// Zapis do pliku tymczasowego
File.WriteAllText("plik.txt.tmp", content);

// Dopiero po pełnym sukcesie – zmiana nazwy na docelową
File.Move("plik.txt.tmp", "plik.txt");
```

Opisane sytuacje pokazują, że przy pracy z plikami w sieci to programista musi świadomie przejąć kontrolę nad logiką i rozumieć złożoność ukrytą pod fasadą `System.IO`.

# Podsumowanie

`System.IO` jest świetnym przykładem siły fasady, dopóki działamy w prostym, lokalnym środowisku. Wystarczy jednak przenieść pliki do sieci, by ta wygodna abstrakcja zaczęła przypominać iluzję. Problem nie leży tak naprawdę w samej bibliotece .NET, lecz w protokołach działających pod spodem – SMB, NFS czy FTP – które próbują symulować zachowanie lokalnego dysku, choć w rzeczywistości wykonują zawodny i kosztowny transfer sieciowy. To one decydują, czy zapis potrwa dwie sekundy czy dwie minuty, czy operacja zakończy się atomowo, czy w połowie, i jak zostaną obsłużone blokady.

W takich scenariuszach deweloper musi rozumieć ukrytą złożoność: świadomie nadpisać wybory podjęte pod fasadą albo opakować kod dodatkowymi mechanizmami (retry, zapis pośredni, kontrola timeoutów). Przykład `System.IO` pokazuje, że żadna abstrakcja nie potrafi całkowicie ukryć działania niższych warstw. Projektując własne interfejsy warto dążyć do maksymalnego uproszczenia, ale nie kosztem decyzji, które powinny pozostać w gestii programisty. Tam, gdzie próbujemy je ukryć, zamiast mocnej abstrakcji tworzymy jedynie iluzję.
