Organizacja programu

Napiwek

Dopiero zaczynasz programować oprogramowanie? Najpierw zacznij od samouczków Wprowadzenie . W miarę rozwoju projektów poznasz naturalnie organizację programów.

Czy masz doświadczenie w pracy w innym języku? Jeśli znasz rozwiązania i projekty w programie Visual Studio lub systemy kompilacji, takie jak Maven lub Cargo, ten artykuł mapuje te pojęcia na platformę .NET.

W miarę rozwoju aplikacji w języku C# musisz zorganizować kod. Platforma .NET udostępnia hierarchię narzędzi organizacyjnych — rozwiązań, projektów, zestawów, przestrzeni nazw i typów — które współpracują ze sobą, aby zapewnić możliwość zarządzania dużymi bazami kodu. Konwencje opisane tutaj reprezentują szeroki konsensus w społeczności platformy .NET. Możesz odejść z określonych powodów, ale przestrzeganie tych konwencji sprawia, że twój kod jest znany i nawigowalny dla innych deweloperów platformy .NET.

Hierarchia organizacyjna

Organizuj typową aplikację .NET w warstwach, od najszerszych do najbardziej specyficznych:

Level Opis Przykład
Rozwiązanie Kontener, który grupuje powiązane projekty. MyApp.slnx
Projekt Jednostka kompilacji, która tworzy jeden zestaw. MyApp.Web.csproj
Zestawu Kompilowany .dll lub .exe generowany przez projekt. MyApp.Web.dll
Namespace Logiczne grupowanie typów. MyApp.Web.Controllers
Typ Klasa, struktura, interfejs, wyliczenie lub delegat. OrderController

Każdy poziom służy innego celu. Rozwiązania organizują przepływ pracy programowania. Projekty definiują, co jest kompilowane razem, a każdy projekt tworzy jeden zestaw. Zestawy są jednostką wdrażania i przechowywania wersji. Przestrzenie nazw uniemożliwiają kolizje nazw i ułatwiają znajdowanie typów. Pojedynczy zestaw może zawierać wiele przestrzeni nazw, a jedna przestrzeń nazw może obejmować wiele zestawów. Typy definiują rzeczywiste zachowanie i dane.

Projekty i zestawy

Każdy projekt kompiluje się w jeden zestaw: bibliotekę klas lub plik wykonywalny. Zacznij od pojedynczego projektu dla małych aplikacji — nie należy przedwcześnie dzielić. Głównym powodem utworzenia oddzielnego projektu jest ponowne użycie tego kodu w więcej niż jednej aplikacji. Poza ponownym użyciem dodaj projekty, gdy masz konkretny powód:

  • Udostępnianie kodu między aplikacjami — wyodrębnianie udostępnionej logiki do biblioteki klas, do której odwołuje się wiele aplikacji.
  • Oddzielne obawy — zapewnij niezależny dostęp do danych, logikę biznesową i warstwy prezentacji.
  • Zależności sterujące — projekt może używać tylko typów z projektów, do których jawnie się odwołuje.

Pojedynczy projekt dobrze sprawdza się w przypadku wielu aplikacji. Oprzyj się pokusie tworzenia osobnych projektów „na wszelki wypadek.” Zawsze możesz wyodrębnić bibliotekę później, gdy druga aplikacja będzie potrzebować tego samego kodu.

Dopasowywanie przestrzeni nazw do struktury folderów

Nazwy przestrzeni nazw powinny odpowiadać strukturze folderów w Twoim projekcie. Gdy zostanie wyświetlona przestrzeń nazw MyApp.Services.Payments, musisz wyszukać w Services/Payments folderze kod źródłowy typów zdefiniowanych w tej przestrzeni nazw. Zestaw .NET SDK obsługuje tę konwencję, która jest tak szeroko stosowana, że jej naruszenie aktywnie dezorientuje innych deweloperów.

// File: Services/OrderService.cs
// Namespace mirrors the folder path
using MyApp.Core;

namespace MyApp.Services;

public class OrderService
{
    public Order CreateOrder(string product, int quantity, decimal price) =>
        new() { ProductName = product, Quantity = quantity, UnitPrice = price };

    public string FormatSummary(Order order) =>
        $"{order.Quantity}x {order.ProductName} = {order.Total:C}";
}

Główna przestrzeń nazw jest automatycznie ustawiana na nazwę pliku projektu. Typy w podfolderach nie są automatycznie przypisywane przestrzenie nazw podrzędnych — deklarujesz przestrzeń nazw jawnie w każdym pliku, ale zawsze utrzymuj je zsynchronizowane.

Napiwek

Przestrzeń nazw katalogu głównego można zmienić, ustawiając wartość <RootNamespace> w pliku projektu.

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
   <RootNamespace>MyCompany.MyApp</RootNamespace>
 </PropertyGroup>
</Project>

Organizowanie przestrzeni nazw według funkcji, a nie według typu

Grupuj powiązane typy w przestrzenie nazw według funkcji lub odpowiedzialności. Umieść razem interfejs, jego implementacje i typy pomocnicze:

// Good: group by feature
namespace MyApp.Payments;

public interface IPaymentProcessor
{
    bool ProcessPayment(decimal amount);
}

public class CreditCardProcessor : IPaymentProcessor
{
    public bool ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing credit card payment of {amount:C}");
        return true;
    }
}

public record PaymentResult(bool Success, string? TransactionId);

Organizacja oparta na funkcjach przechowuje wszystko, czego potrzebujesz w jednym miejscu, dzięki czemu kod jest łatwiejszy do nawigowania i rozumowania.

Modyfikatory dostępu i moduły

Modyfikatory dostępu współpracują ze strukturą projektu i zestawu w celu kontrolowania dostępności:

  • public — dostępny z dowolnego zestawu, który odwołuje się do tego zestawu.
  • internal — dostępny tylko w ramach tego samego zestawu (ustawienie domyślne dla typów najwyższego poziomu).
  • private, protected, private protected, protected internal — są dostępne w zależności od typu zawierającego, modułu lub typów pochodnych.

internal Ustawienie domyślne dla typów, których nie potrzebują inne projekty. Ta praktyka ukrywa szczegóły implementacji i zapewnia swobodę refaktoryzacji bez przerywania pracy użytkowników. Jest to szczególnie ważne w przypadku bibliotek udostępnionych:

namespace MyApp.Inventory;

// Public — other projects can use this type
public class InventoryService
{
    public int GetStockLevel(string productName) =>
        StockDatabase.Lookup(productName);
}

// Internal — only visible within this assembly
internal static class StockDatabase
{
    private static readonly Dictionary<string, int> _stock = new()
    {
        ["Widget"] = 42,
        ["Gadget"] = 17
    };

    internal static int Lookup(string productName) =>
        _stock.GetValueOrDefault(productName);
}
  • Nazywaj przestrzenie nazw spójnie. Użyj CompanyName.ProductName.Feature jako wzorca nazewnictwa. Użyj na przykład nazwy Contoso.Inventory.Shipping. Spójne nazewnictwo ułatwia deweloperom znajdowanie typów bez wyszukiwania.
  • Zachowaj koncentrację projektów. Każdy projekt powinien mieć jedną, wyraźną odpowiedzialność. Gdy projekt obsługuje zbyt wiele niepowiązanych problemów, podziel go.
  • Użyj przestrzeni nazw o zakresie plików. Składnia namespace MyApp.Services; zmniejsza wcięcie i jest zalecanym stylem. Użyj go we wszystkich nowych kodach.
  • Domyślnieinternal. Oznaczaj typy public jedynie wtedy, gdy inne zespoły faktycznie ich potrzebują. Zawsze możesz poszerzyć dostęp później; zawężenie jest zmianą powodującą niezgodność.