Organização do programa

Sugestão

Novo no desenvolvimento de software? Começa primeiro pelos tutoriais para começar . Vais aprender sobre organização de programas naturalmente à medida que os teus projetos crescem.

Experiente noutra língua? Se está familiarizado com soluções e projetos no Visual Studio, ou constrói sistemas como Maven ou Cargo, este artigo mapeia esses conceitos para .NET.

À medida que uma aplicação C# cresce, é preciso organizar o código. O .NET fornece uma hierarquia de ferramentas organizacionais — soluções, projetos, assemblies, namespaces e tipos — que trabalham em conjunto para manter grandes bases de código geríveis. As convenções aqui descritas representam um consenso amplo na comunidade .NET. Podes desviar-te por razões específicas, mas seguir estas convenções torna o teu código familiar e navegável para outros programadores .NET.

A hierarquia organizacional

Organize uma aplicação .NET típica em camadas, da mais ampla à mais específica:

Nível Descrição Exemplo
Solução Um contentor que agrupa projetos relacionados. MyApp.slnx
Projeto Uma unidade de construção que produz um único conjunto. MyApp.Web.csproj
Montagem O resultado de compilação .dll ou .exe produzido por um projeto. MyApp.Web.dll
Espaço de nomes Um agrupamento lógico de tipos. MyApp.Web.Controllers
Type Uma classe, estrutura, interface, enum ou delegado. OrderController

Cada nível tem um propósito diferente. As soluções organizam o seu fluxo de trabalho de desenvolvimento. Os projetos definem o que é compilado, e cada projeto produz uma assembly. Os assemblies são a unidade de implementação e versionamento. Os namespaces evitam colisões de nomes e tornam os tipos fáceis de encontrar. Um único assembly pode conter múltiplos namespaces, e um único namespace pode abranger múltiplos assemblies. Os tipos definem o comportamento real e os dados.

Projetos e assembleias

Cada projeto compila-se numa única assembleia: uma biblioteca de classes ou executável. Comece com um único projeto para pequenas aplicações — não divida prematuramente. A principal razão para criar um projeto separado é reutilizar esse código em mais do que uma aplicação. Para além da reutilização, adiciona projetos quando tiveres uma razão concreta:

  • Partilhe código entre aplicações — extraia lógica partilhada para uma biblioteca de classes que várias aplicações referenciam.
  • Preocupações separadas — mantenha o acesso aos dados, lógica de negócio e camadas de apresentação independentes.
  • Dependências de controlo — um projeto só pode usar tipos de projetos que referencia explicitamente.

Um único projeto funciona bem para muitas aplicações. Resista à tentação de criar projetos separados "por precaução". Podes sempre extrair uma biblioteca mais tarde quando uma segunda aplicação precisar do mesmo código.

Combinar namespaces com a estrutura de pastas

Os nomes dos namespaces devem seguir a estrutura de pastas do seu projeto. Quando vês o namespace MyApp.Services.Payments, sabes que deves procurar na Services/Payments pasta o código-fonte dos tipos definidos nesse namespace. O SDK .NET suporta esta convenção, e é tão amplamente seguida que violá-la confunde ativamente outros programadores:

// 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}";
}

O seu espaço de nomes raiz é automaticamente definido como o nome do ficheiro do seu projeto. Os tipos nas subpastas não recebem automaticamente subnamespaces — declara-se o namespace explicitamente em cada ficheiro — mas devem ser sempre mantidos sincronizados.

Sugestão

Podes alterar o namespace raiz definindo <RootNamespace> no ficheiro do teu projeto.

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

Organize os namespaces por característica, não por tipo de tipo

Agrupar tipos relacionados em espaços de nomes por característica ou responsabilidade. Junte uma interface, as suas implementações e tipos de suporte:

// 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);

A organização baseada em funcionalidades mantém tudo o que precisa num só lugar, tornando o código mais fácil de navegar e de raciocinar.

Modificadores de acesso e montagens

Os modificadores de acesso funcionam com o projeto e a estrutura de montagem para controlar a acessibilidade:

Utilize internal por padrão para tipos de que outros projetos não precisam. Esta prática esconde detalhes da implementação e dá-lhe liberdade para refatorar sem prejudicar os consumidores. É especialmente importante para bibliotecas partilhadas:

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);
}
  • Nomeie os namespaces de forma consistente. Usa CompanyName.ProductName.Feature como padrão de nomes. Por exemplo, use Contoso.Inventory.Shipping. A nomeação consistente ajuda os programadores a encontrar tipos sem necessidade de pesquisar.
  • Mantenha os projetos focados. Cada projeto deve ter uma responsabilidade única e clara. Quando um projeto lida com demasiadas preocupações não relacionadas, divide-o.
  • Utilize espaços de nomes com escopo de ficheiro. A namespace MyApp.Services; sintaxe reduz a indentação e é o estilo recomendado. Use-o em código totalmente novo.
  • Definir para internal por padrão. Só assinala os tipos public quando outros conjuntos realmente precisam deles. Podes sempre alargar o acesso mais tarde; Restringi-la é uma mudança decisiva.