자습서: XML 설명서 만들기

이 자습서에서는 이전 자습서의 기존 개체 지향 샘플을 가져와서 XML 설명서 주석으로 향상시킵니다. XML 설명서 주석은 유용한 IntelliSense 도구 설명을 제공하며 생성된 API 참조 문서에 참여할 수 있습니다. 주석을 받을 자격이 있는 요소, , ,<summary>, ,<param><returns><value><remarks><example>, , 및 핵심 태그<seealso>를 사용하는 방법 및 <exception>일관성 있고 의도적인 주석 처리가 노이즈를 추가하지 않고 유지 관리 효율성, 검색 가능성 및 공동 작업을 향상시키는 방법을 알아봅니다. <inheritdoc> 결국 샘플의 공용 화면에 주석을 달고, XML 설명서 파일을 내보내는 프로젝트를 빌드했으며, 이러한 주석이 개발자 환경 및 다운스트림 설명서 도구로 직접 이동하는 방법을 살펴보았습니다.

이 자습서에서는 다음을 수행합니다.

  • C# 프로젝트에서 XML 설명서 출력을 사용하도록 설정합니다.
  • 형식 및 멤버에 XML 문서 주석을 추가하고 구조화합니다.
  • 프로젝트를 빌드하고 생성된 XML 설명서 파일을 검사합니다.

필수 조건

XML 설명서 사용

이전 개체 지향 자습서에서 빌드한 프로젝트를 로드합니다. 새로 시작하려면 dotnet/docs 리포지토리의 snippets/object-oriented-programming 폴더에서 샘플을 복제하십시오.

다음으로, 컴파일러가 어셈블리와 함께 .xml 파일을 내보낼 수 있도록 XML 문서 출력을 활성화합니다. 프로젝트 파일을 편집하고 요소 내에 다음 속성을 추가(또는 확인)합니다 <PropertyGroup> .

<GenerateDocumentationFile>True</GenerateDocumentationFile>

Visual Studio를 사용하는 경우 "빌드" 속성 페이지를 사용하여 사용하도록 설정할 수 있습니다.

프로젝트를 빌드합니다. 컴파일러는 공개적으로 표시되는 형식 및 멤버의 /// 모든 주석을 집계하는 XML 파일을 생성합니다. 이 파일은 IntelliSense 도구 설명서, 정적 분석 도구 및 다운스트림 설명서 생성 시스템을 공급합니다.

지금 프로젝트를 빌드합니다. 주석이 누락된 <summary> 모든 공용 멤버에 대한 경고가 표시됩니다. 이러한 경고를 완전하고 의도적인 설명서를 제공하는 데 도움이 되는 to-do 목록으로 처리합니다. 생성된 XML 파일(빌드 출력 옆에 있는 파일)을 열고 초기 구조를 검사합니다. 처음에는 아직 주석을 <members> 추가하지 않았기 때문에 섹션이 비어 있습니다.

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>oo-programming</name>
    </assembly>
    <members>
    </members>
</doc>

파일이 있는 상태에서 대상 XML 주석 추가를 시작하고 생성된 출력에 각 주석이 표시되는 방식을 즉시 확인합니다. 레코드 유형으로 Transaction 시작합니다.

namespace OOProgramming;

/// <summary>
/// Represents an immutable financial transaction with an amount, date, and descriptive notes.
/// </summary>
/// <param name="Amount">The transaction amount. Positive values represent credits/deposits, negative values represent debits/withdrawals.</param>
/// <param name="Date">The date and time when the transaction occurred.</param>
/// <param name="Notes">Descriptive notes or memo text associated with the transaction.</param>
public record Transaction(decimal Amount, DateTime Date, string Notes);

설명서 주석 추가

이제 빌드 경고를 순환하며 BankAccount 형식에 간결하고 유용한 문서를 추가합니다. 각 경고는 <summary> (또는 다른 필수 요소)가 없는 공용 멤버를 정확히 지적합니다. 경고 목록을 검사 목록으로 처리합니다. 노이즈를 추가하지 않도록 합니다. 의도, 고정 및 중요한 사용 제약 조건을 설명하는 데 집중합니다. 명백한 형식 이름 또는 매개 변수 형식을 다시 지정하는 것은 건너뜁니다.

  1. 프로젝트를 다시 빌드합니다. Visual Studio 또는 Visual Studio Code에서 오류 목록/문제 패널을 열고 설명서 경고(CS1591)를 필터링합니다. 명령줄에서 빌드를 실행하고 콘솔에 내보낸 경고를 검토합니다.
  2. 첫 번째 경고( BankAccount 클래스)로 이동합니다. 선언 위의 줄에 .를 입력합니다 ///. 편집기가 <summary> 요소를 지원합니다. 자리 표시자를 작업 중심의 단일 문장으로 바꿉다. 이 문장은 도메인에서 계정의 역할을 설명합니다. 예를 들어 트랜잭션을 추적하고 최소 잔액을 적용합니다.
  3. 동작을 설명해야 하는 경우에만 추가 <remarks> 합니다. 예를 들어 최소 잔액 적용의 작동 방식 또는 계정 번호가 생성되는 방법이 있습니다. 발언을 짧게 유지합니다.
  4. 각 속성(Number, Owner, Balance)에 대해 간단한 getter가 값을 반환하는 방식이 아니라, 값이 무엇을 의미하는지를 나타내도록 ///를 입력하고 <summary>를 작성하십시오. 속성이 값(예: Balance)을 계산하는 경우 계산을 <value> 명확히 하는 요소를 추가합니다.
  5. 각 생성자에 대해 매개 변수 이름을 다시 지정하는 것뿐만 아니라 각 인수의 의미를 설명하는 요소를 더합니다 <summary><param> . 한 오버로드가 다른 델리게이트에 위임하는 경우, 간결한 <remarks> 요소를 추가합니다.
  6. throw할 수 있는 메서드의 경우 각 의도적인 예외 유형마다 <exception> 태그를 추가합니다. 트리거하는 조건을 설명합니다. 인수 유효성 검사 함수가 공개 계약의 일부가 아닌 경우 예외를 문서화하지 마세요.
  7. 값을 반환하는 메서드의 경우 호출자가 수신하는 내용에 대한 간단한 설명을 추가 <returns> 합니다. 메서드 이름 또는 관리되는 형식을 반복하지 않습니다.
  8. 먼저 기본 클래스를 BankAccount 사용합니다.

당신의 버전은 다음 코드와 비슷하게 보일 것입니다.

namespace OOProgramming;

/// <summary>
/// Represents a bank account with basic banking operations including deposits, withdrawals, and transaction history.
/// Supports minimum balance constraints and provides extensible month-end processing capabilities.
/// </summary>
public class BankAccount
{
    /// <summary>
    /// Gets the unique account number for this bank account.
    /// </summary>
    /// <value>A string representation of the account number, generated sequentially.</value>
    public string Number { get; }
    
    /// <summary>
    /// Gets or sets the name of the account owner.
    /// </summary>
    /// <value>The full name of the person who owns this account.</value>
    public string Owner { get; set; }
    
    /// <summary>
    /// Gets the current balance of the account by calculating the sum of all transactions.
    /// </summary>
    /// <value>The current account balance as a decimal value.</value>
    public decimal Balance => _allTransactions.Sum(i => i.Amount);

    private static int s_accountNumberSeed = 1234567890;

    private readonly decimal _minimumBalance;

    /// <summary>
    /// Initializes a new instance of the BankAccount class with the specified owner name and initial balance.
    /// Uses a default minimum balance of 0.
    /// </summary>
    /// <param name="name">The name of the account owner.</param>
    /// <param name="initialBalance">The initial deposit amount for the account.</param>
    /// <remarks>
    /// This constructor is a convenience overload that calls the main constructor with a minimum balance of 0.
    /// If the initial balance is greater than 0, it will be recorded as the first transaction with the note "Initial balance".
    /// The account number is automatically generated using a static seed value that increments for each new account.
    /// </remarks>
    public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { }

    /// <summary>
    /// Initializes a new instance of the BankAccount class with the specified owner name, initial balance, and minimum balance constraint.
    /// </summary>
    /// <param name="name">The name of the account owner.</param>
    /// <param name="initialBalance">The initial deposit amount for the account.</param>
    /// <param name="minimumBalance">The minimum balance that must be maintained in the account.</param>
    /// <remarks>
    /// This is the primary constructor that sets up all account properties. The account number is generated automatically
    /// using a static seed value. If an initial balance is provided and is greater than 0, it will be added as the first
    /// transaction. The minimum balance constraint will be enforced on all future withdrawal operations through the
    /// <see cref="CheckWithdrawalLimit"/> method.
    /// </remarks>
    public BankAccount(string name, decimal initialBalance, decimal minimumBalance)
    {
        Number = s_accountNumberSeed.ToString();
        s_accountNumberSeed++;

        Owner = name;
        _minimumBalance = minimumBalance;
        if (initialBalance > 0)
            MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
    }

    private readonly List<Transaction> _allTransactions = [];

    /// <summary>
    /// Makes a deposit to the account by adding a positive transaction.
    /// </summary>
    /// <param name="amount">The amount to deposit. Must be positive.</param>
    /// <param name="date">The date when the deposit is made.</param>
    /// <param name="note">A descriptive note about the deposit transaction.</param>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the deposit amount is zero or negative.</exception>
    /// <remarks>
    /// This method creates a new <see cref="Transaction"/> object with the specified amount, date, and note,
    /// then adds it to the internal transaction list. The transaction amount must be positive - negative amounts
    /// are not allowed for deposits. The account balance is automatically updated through the Balance property
    /// which calculates the sum of all transactions. There are no limits or restrictions on deposit amounts.
    /// </remarks>
    public void MakeDeposit(decimal amount, DateTime date, string note)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
        }
        var deposit = new Transaction(amount, date, note);
        _allTransactions.Add(deposit);
    }

    /// <summary>
    /// Makes a withdrawal from the account by adding a negative transaction.
    /// Checks withdrawal limits and minimum balance constraints before processing.
    /// </summary>
    /// <param name="amount">The amount to withdraw. Must be positive.</param>
    /// <param name="date">The date when the withdrawal is made.</param>
    /// <param name="note">A descriptive note about the withdrawal transaction.</param>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the withdrawal amount is zero or negative.</exception>
    /// <exception cref="InvalidOperationException">Thrown when the withdrawal would cause the balance to fall below the minimum balance.</exception>
    /// <remarks>
    /// This method first validates that the withdrawal amount is positive, then checks if the withdrawal would
    /// violate the minimum balance constraint by calling <see cref="CheckWithdrawalLimit"/>. The withdrawal is
    /// recorded as a negative transaction amount. If the withdrawal limit check returns an overdraft transaction
    /// (such as a fee), that transaction is also added to the account. The method enforces business rules through
    /// the virtual CheckWithdrawalLimit method, allowing derived classes to implement different withdrawal policies.
    /// </remarks>
    public void MakeWithdrawal(decimal amount, DateTime date, string note)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
        }
        Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance);
        Transaction? withdrawal = new(-amount, date, note);
        _allTransactions.Add(withdrawal);
        if (overdraftTransaction != null)
            _allTransactions.Add(overdraftTransaction);
    }

    /// <summary>
    /// Checks whether a withdrawal would violate the account's minimum balance constraint.
    /// This method can be overridden in derived classes to implement different withdrawal limit policies.
    /// </summary>
    /// <param name="isOverdrawn">True if the withdrawal would cause the balance to fall below the minimum balance.</param>
    /// <returns>A Transaction object representing any overdraft fees or penalties, or null if no additional charges apply.</returns>
    /// <exception cref="InvalidOperationException">Thrown when the withdrawal would cause an overdraft and the account type doesn't allow it.</exception>
    protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)
    {
        if (isOverdrawn)
        {
            throw new InvalidOperationException("Not sufficient funds for this withdrawal");
        }
        else
        {
            return default;
        }
    }

    /// <summary>
    /// Generates a detailed account history report showing all transactions with running balance calculations.
    /// </summary>
    /// <returns>A formatted string containing the complete transaction history with dates, amounts, running balances, and notes.</returns>
    /// <remarks>
    /// This method creates a formatted report that includes a header row followed by all transactions in chronological order.
    /// Each row shows the transaction date (in short date format), the transaction amount, the running balance after that
    /// transaction, and any notes associated with the transaction. The running balance is calculated by iterating through
    /// all transactions and maintaining a cumulative total. The report uses tab characters for column separation and
    /// is suitable for display in console applications or simple text outputs.
    /// </remarks>
    public string GetAccountHistory()
    {
        var report = new System.Text.StringBuilder();

        decimal balance = 0;
        report.AppendLine("Date\t\tAmount\tBalance\tNote");
        foreach (var item in _allTransactions)
        {
            balance += item.Amount;
            report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
        }

        return report.ToString();
    }

    /// <summary>
    /// Performs month-end processing for the account. This virtual method can be overridden in derived classes
    /// to implement specific month-end behaviors such as interest calculations, fee assessments, or statement generation.
    /// </summary>
    /// <remarks>
    /// The base implementation of this method does nothing, providing a safe default for basic bank accounts.
    /// Derived classes such as savings accounts or checking accounts can override this method to implement
    /// account-specific month-end processing. Examples include calculating and applying interest payments,
    /// assessing monthly maintenance fees, generating account statements, or performing regulatory compliance checks.
    /// This method is typically called by banking systems at the end of each month as part of batch processing operations.
    /// </remarks>
    public virtual void PerformMonthEndTransactions() { }
}

완료되면 다시 생성된 XML 파일을 열고 각 멤버가 새 요소와 함께 표시되는지 확인합니다. 잘려진 부분은 다음과 같을 수 있습니다.

<member name="T:OOProgramming.BankAccount">
  <summary>Represents a bank account that records transactions and enforces an optional minimum balance.</summary>
  <remarks>Account numbers are generated sequentially when each instance is constructed.</remarks>
</member>
<member name="P:OOProgramming.BankAccount.Balance">
  <summary>Gets the current balance based on all recorded transactions.</summary>
  <value>The net sum of deposits and withdrawals.</value>
</member>

팁 (조언)

요약을 단일 문장으로 유지합니다. 하나 이상이 필요한 경우 보조 컨텍스트를 <remarks>에 이동합니다.

파생 클래스에서 사용 <inheritdoc/>

BankAccount에서 파생되는 경우(예: 이자를 적용하는 SavingsAccount), 설명서를 복사하는 대신 기본 설명서를 상속할 수 있습니다. 파생 멤버의 설명서 블록 내에 자체 닫힘 <inheritdoc/> 요소를 추가합니다. <inheritdoc/> 뒤에 추가 요소(예를 들어, 추가 <remarks> 세부 정보)를 추가하여 특수한 동작을 문서화할 수 있습니다.

/// <inheritdoc/>
/// <remarks>
/// An interest-earning account is a specialized savings account that rewards customers for maintaining higher balances.
/// Interest is only earned when the account balance exceeds $500, encouraging customers to maintain substantial deposits.
/// The annual interest rate of 2% is applied monthly to qualifying balances, providing a simple savings incentive.
/// This account type uses the standard minimum balance of $0 from the base <see cref="BankAccount"/> class.
/// </remarks>
public class InterestEarningAccount : BankAccount

비고

<inheritdoc/> 는 중복을 줄이고 나중에 기본 형식 설명서를 업데이트할 때 일관성을 유지하는 데 도움이 됩니다.

공용 화면 문서화를 완료한 후 마지막 한 번 빌드하여 남은 CS1591 경고가 없는지 확인합니다. 이제 프로젝트에서 유용한 IntelliSense와 게시 워크플로 준비가 완료된 구조적 XML 파일을 생성합니다.

GitHub에 있는 dotnet/docs 리포지토리의 원본 폴더에서 주석이 추가된 전체 샘플을 볼 수 있습니다.

주석을 기반으로 출력 생성

XML 주석에서 출력을 만들려면 다음 도구 중 원하는 것을 시도하여 자세히 살펴볼 수 있습니다.

  • DocFX: DocFX 는 현재 C#, Visual Basic 및 F#을 지원하는 .NET용 API 설명서 생성기입니다. 또한 생성된 참조 설명서를 사용자 지정할 수 있습니다. DocFX는 소스 코드 및 Markdown 파일에서 정적 HTML 웹 사이트를 빌드합니다. 또한 DocFX는 템플릿을 통해 웹 사이트의 레이아웃과 스타일을 사용자 지정할 수 있는 유연성을 제공합니다. 사용자 지정 템플릿을 만들 수도 있습니다.
  • 샌드캐슬: 샌드캐슬 도구 는 개념 및 API 참조 페이지를 모두 포함하는 관리되는 클래스 라이브러리에 대한 도움말 파일을 만듭니다. 샌드캐슬 도구는 명령줄 기반이며 GUI 프런트 엔드, 프로젝트 관리 기능 또는 자동화된 빌드 프로세스가 없습니다. 샌드캐슬 도움말 파일 작성기는 자동화된 방식으로 도움말 파일을 빌드하는 독립 실행형 GUI 및 명령줄 기반 도구를 제공합니다. Visual Studio 통합 패키지도 사용할 수 있으므로 Visual Studio 내에서 프로젝트를 완전히 만들고 관리할 수 있습니다.
  • Doxygen: Doxygen 은 문서화된 원본 파일 집합에서 온라인 설명서 브라우저(HTML) 또는 오프라인 참조 설명서(LaTeX)를 생성합니다. RTF(MS Word), PostScript, 하이퍼링크 PDF, 압축된 HTML, DocBook 및 Unix 수동 페이지에서 출력을 생성할 수도 있습니다. 문서화되지 않은 소스 파일에서 코드 구조를 추출하도록 Doxygen을 구성할 수 있습니다.