팁 (조언)
소프트웨어 개발이 새로운가요? 먼저 시작하기 자습서부터 시작하세요. 프로젝트가 성장함에 따라 자연스럽게 프로그램 조직에 대해 알아봅니다.
다른 언어로 경험하신 적 있나요? Visual Studio의 솔루션 및 프로젝트에 익숙하거나 Maven 또는 Cargo와 같은 시스템을 빌드하는 경우 이 문서에서는 이러한 개념을 .NET에 매핑합니다.
C# 애플리케이션이 증가함에 따라 코드를 구성해야 합니다. .NET은 대규모 코드베이스를 관리하기 위해 함께 작동하는 조직 도구(솔루션, 프로젝트, 어셈블리, 네임스페이스 및 형식)의 계층 구조를 제공합니다. 여기에 설명된 규칙은 .NET 커뮤니티에서 광범위한 합의를 나타냅니다. 특별한 이유가 있을 때는 규칙을 따르지 않을 수도 있지만, 이러한 규칙을 따르면 코드는 다른 .NET 개발자들에게 친숙하고 이해하기 쉽고 코드 탐색에 도움을 줄 수 있습니다.
조직 계층 구조
가장 광범위한 계층에서 가장 구체적인 계층으로 일반적인 .NET 애플리케이션을 구성합니다.
| 수준 | 설명 | 예시 |
|---|---|---|
| Solution | 관련 프로젝트를 그룹화할 컨테이너입니다. | MyApp.slnx |
| 프로젝트 | 하나의 어셈블리를 생성하는 빌드 단위입니다. | MyApp.Web.csproj |
| 어셈블리 | 프로젝트에서 컴파일하여 .dll 또는 .exe 산출된 결과물입니다. |
MyApp.Web.dll |
| 네임스페이스 | 형식의 논리적 그룹화입니다. | MyApp.Web.Controllers |
| 유형 | 클래스, 구조체, 인터페이스, 열거형 또는 델리게이트 | OrderController |
각 수준은 다른 용도로 사용됩니다. 솔루션은 개발 워크플로를 구성합니다. 프로젝트는 함께 컴파일되는 항목을 정의하고 각 프로젝트는 하나의 어셈블리를 생성합니다. 어셈블리는 배포 및 버전 관리 단위입니다. 네임스페이스는 이름 충돌을 방지하고 형식을 쉽게 찾을 수 있도록 합니다. 단일 어셈블리에는 여러 네임스페이스가 포함될 수 있으며 단일 네임스페이스는 여러 어셈블리에 걸쳐 있습니다. 형식은 실제 동작 및 데이터를 정의합니다.
프로젝트 및 어셈블리
각 프로젝트는 클래스 라이브러리 또는 실행 파일과 같은 단일 어셈블리로 컴파일됩니다. 작은 애플리케이션에 대한 단일 프로젝트로 시작합니다. 조기에 분할하지 마세요. 별도의 프로젝트를 만드는 주된 이유는 둘 이상의 애플리케이션에서 해당 코드를 다시 사용하는 것입니다. 재사용 외에도 구체적인 이유가 있는 경우 프로젝트를 추가합니다.
- 애플리케이션 간에 코드 공유 - 여러 앱이 참조하는 클래스 라이브러리에 공유 논리를 추출합니다.
- 별도의 문제 - 데이터 액세스, 비즈니스 논리 및 프레젠테이션 계층을 독립적으로 유지합니다.
- 종속성 제어 — 프로젝트는 명시적으로 참조하는 프로젝트의 타입만 사용할 수 있습니다.
단일 프로젝트는 많은 애플리케이션에서 잘 작동합니다. "만의 경우"에 따라 별도의 프로젝트를 만들려는 충동에 저항하십시오. 두 번째 애플리케이션에 동일한 코드가 필요한 경우 나중에 항상 라이브러리를 추출할 수 있습니다.
네임스페이스를 폴더 구조에 맞추다
네임스페이스 이름은 프로젝트의 폴더 구조를 따라야 합니다. 네임스페이스 MyApp.Services.Payments가 표시되면 해당 네임스페이스에 정의된 형식의 소스 코드를 찾기 위해 Services/Payments 폴더를 확인해야 합니다. .NET SDK는 이 규칙을 지원하며, 이를 위반하면 다른 개발자들이 혼동하게 됩니다.
// 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}";
}
루트 네임스페이스는 자동으로 프로젝트 파일의 이름으로 설정됩니다. 하위 폴더의 형식은 하위 네임스페이스를 자동으로 가져오는 것이 아니라 각 파일에서 네임스페이스를 명시적으로 선언하지만 항상 동기화 상태로 유지합니다.
팁 (조언)
프로젝트 파일에서 설정 <RootNamespace> 하여 루트 네임스페이스를 변경할 수 있습니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>MyCompany.MyApp</RootNamespace>
</PropertyGroup>
</Project>
형식 종류가 아닌 기능별로 네임스페이스 구성
기능 또는 책임별로 관련 형식을 네임스페이스로 그룹화합니다. 인터페이스, 해당 구현 및 지원 형식을 함께 배치합니다.
// 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);
기능 기반 조직은 필요한 모든 항목을 한 곳에서 유지하므로 코드를 보다 쉽게 탐색하고 추론할 수 있습니다.
액세스 한정자 및 어셈블리
액세스 한정자는 프로젝트 및 어셈블리 구조와 함께 작동하여 접근성을 제어합니다.
-
public— 이 어셈블리를 참조하는 모든 어셈블리에서 액세스할 수 있습니다. -
internal- 동일한 어셈블리 내에서만 액세스할 수 있습니다(최상위 형식의 기본값). -
private,protected,private protectedprotected internal- 포함하는 형식, 어셈블리 또는 파생 형식에 따라 액세스할 수 있습니다.
internal 다른 프로젝트에 필요하지 않은 형식의 기본값입니다. 이 방법은 구현 세부 정보를 숨겨서 사용자를 중단하지 않고 리팩터링할 수 있는 자유를 제공합니다. 공유 라이브러리에 특히 중요합니다.
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);
}
권장 사례
- 네임스페이스를 일관되게 지정합니다. 명명 패턴으로는
CompanyName.ProductName.Feature를 사용하십시오. 예를 들면Contoso.Inventory.Shipping를 사용합니다. 일관된 이름을 지정하면 개발자가 검색하지 않고 형식을 찾을 수 있습니다. - 프로젝트에 집중합니다. 각 프로젝트에는 하나의 명확한 책임이 있어야 합니다. 프로젝트가 관련 없는 문제를 너무 많이 처리하면 분할합니다.
- 파일 범위 네임스페이스를 사용합니다. 이 구문은
namespace MyApp.Services;들여쓰기를 줄이고 권장되는 스타일입니다. 모든 새 코드에서 사용합니다. - 기본값은
internal입니다. 다른 어셈블리에 필요한 경우에만 형식public을 표시합니다. 언제든지 나중에 접근 권한을 확장할 수 있습니다. 범위를 좁히는 것은 호환성을 깨트리는 변경입니다.
관련 콘텐츠
.NET