티스토리 뷰

UWP & Windows App/Beginner

C# Style Guide

kaki104 2021. 3. 15. 10:00
반응형

이 스타일 가이드는 원문을 기본으로, 일부는 C# Coding Style와 Visual Studio의 기본 설정을 사용했습니다.

 

C# 코드 작성시 참고하는 용도로 사용하시면 좋을 것 같습니다. 수정이 필요한 사항이 있다면, 리플로 알려주시면 확인하도록 하겠습니다.
원문 C# at Google Style Guide | styleguide

Formatting guidelines

Naming rules

이름 지정 규칙은 Microsoft’s C# naming guidelines을 따릅니다. Microsoft의 명명 지침이 지정되지 않은 경우 (예 : 개인 및 로컬 변수) 규칙은 CoreFX C# coding guidelines에서 가져옵니다.

 

규칙 요약 :

Code

  • 클래스, 메서드, 열거형, 공용 필드(public fields), 공용 속성(public properties), 네임 스페이스의 이름, protected : PascalCase.
  • 지역 변수의 이름, 매개 변수 (parameters) : camelCase.
  • Private, internal 및 protected internal 필드 및 속성의 이름 : _camelCase.
  • 명명 규칙은 const, static, readonly 등과 같은 수정 자의 영향을 받지 않습니다.
    • private const string _createActionName = "CreateActionName";
    • public const string SiteAddress = "https://192.168.0.1";
  • 대소 문자의 경우 "단어"는 두문자어를 포함하여 내부 공백없이 작성된 모든 것입니다.
    • MyRPC 대신 MyRpc를 사용합니다. (2개 이상의 단어를 조합하는 경우)
    • MBC, MES 등 약어 단어의 단일 사용은 대문자를 그대로 사용합니다.
    • Id(Identification) 하나의 단어를 약어로 사용하는 경우 PascalCase를 사용합니다. (ID : X)
  • 인터페이스 이름은 I로 시작합니다. IInterface.
  • 약어가 앞에서 사용이 될 때는 앞단어는 PascalCase, 뒷 단어는 기본 명명규칙을 사용합니다.
    • MBC + MES = MbcMES

Files

  • 파일 이름과 디렉토리 이름은 PascalCase입니다. MyFile.cs.
  • 가능한 경우 파일 이름은 파일의 기본 클래스 이름과 동일해야합니다. MyClass.cs.
  • 일반적으로 파일 당 하나의 코어 클래스를 선호합니다.

Organization

  • Modifiers는 다음의 순서를 가집니다. [public protected internal private] async [new abstract virtual override sealed] static readonly extern unsafe volatile
    • public abstract string GetUserName() {}
    • protected async virtual Task GetUserIdAsync() {}
    • private readonly IContainerProvider _container;
  • Using을 사용하는 네임 스페이스는 네임 스페이스 앞의 맨 위에 있습니다. using 순서는 알파벳순입니다.
  • Class 맴버 순서:
    • 다음 순서로 그룹 클래스 구성:
      • Nested classes, enums, delegates and events.
      • Static, const and readonly fields.
      • Fields and properties.
      • Constructors and finalizers.
      • Methods.
    • 각 그룹 내에서는 다음 순서를 사용합니다.
      • Public.
      • Internal.
      • Protected internal.
      • Protected.
      • Private.
    • 가능하다면 인터페이스 구현도 함께 그룹화합니다.

Whitespace rules

Google Java 스타일에서 참고로 작성되었으며, 일부는 C# Coding Style와 Visual Studio의 기본 설정을 사용했습니다.

  • 한 줄에 최대 하나의 문.
  • 문당 최대 하나의 할당. (a = b = c = string.Empty; X)
  • Visual Studio의 기본 들여쓰기 값인 4를 사용합니다.
  • 열 제한 : 100.
  • Visual Stuido는 중괄호를 사용할 때 자동으로 줄바꿈을 해주기 때문에 기본값으로 사용합니다.
  • Visual Studio의 기본 스타일인 중괄호 else 중괄호를 사용합니다.
  • 1줄 이상의 빈줄을 사용하지 않습니다.
    • 한 화면에 하나의 메소드의 내용을 보기 위해서는 1개 이상 빈줄 사용을 자제합니다.
  • 선택 사항인 경우에도 브레이스 사용합니다.
  • if / for / while 등 뒤와 쉼표 뒤의 공백을 사용합니다.
    • if (a == b)
    • for (i = 0; i < 5; i++)
  • 여는 괄호 뒤 또는 닫는 괄호 앞에 공백이 없습니다.
  • 단항 연산자와 피연산자 사이에 공백이 없습니다. 연산자와 다른 모든 연산자의 각 피연산자 사이에 공백이 하나 있습니다.
    • ++count;
    • count = customer.Count() + room.Count();
using System;                                           // 'using'은 네임스페이스 외부에 있는 맨 위에 있습니다.

namespace MyNamespace                                   // 네임스페이스는 PascalCase를 사용합니다.
{                                                       // 중괄호 시작은 한줄을 사용합니다.
    public interface IMyInterface                       // 인터페이스는 'I'로 시작합니다.
    {
        public int Calculate(float value, float exp);   // 메서드명은 PascalCase를 사용합니다.
    }                                                   // 중괄호 종료는 한줄을 사용합니다.
                                                        // 각 그룹 사이에 1개의 빈줄만 사용합니다.
    public enum MyEnum                                  // 열거형은 PascalCase를 사용합니다.
    {
        Yes,                                            // 열거자는 PascalCase를 사용합니다.
        No,
    }

    public class MyClass                                // 클래스는 PascalCase를 사용합니다.
    {
        public int Foo = 0;                             // Public 맴버 변수는 PascalCase를 사용합니다.
        public bool NoCounting = false;                 // 필드 초기화를 권장 합니다.

        private class Results
        {
            public int NumNegativeResults = 0;
            public int NumPositiveResults = 0;
        }
        private readonly Results _results;              // 비공개 벰버 변수는 _camelCase를 사용합니다.

        public static int NumTimesCalled = 0;
        private const int _bar = 100;                   // Const는 명명 규칙에 영향을 주지 않습니다.
        private readonly int[] _someTable =
        {
            2, 3, 4                                     // 컨테이너 이니셜라이저는 4개의 공간 들여쓰기를 사용합니다.
        };

        public MyClass()                                // 생성자의 위치
        {
            _results = new Results
            {
                NumNegativeResults = 1,                 // 오브젝트 이니셜라이저는 4개의 공백 들여쓰기를 사용합니다.
                NumPositiveResults = 1
            };
        }

        public int CalculateValue(int mulNumber)        // 파라메터는 camelCase를 사용합니다.
        {
            int resultValue = Foo * mulNumber;          // 로컬 변수는 camelCase를 사용합니다.
            NumTimesCalled++;
            Foo += _bar;

            if (!NoCounting)                            // 단항 연산자 뒤에는 공벽이 없고 'if' 뒤에 공백을 추가합니다.
            {
                if (resultValue < 0)                    // 비교 연산자 앞뒤로 공백을 사용하도록 합니다.
                {
                    _results.NumNegativeResults++;
                }
                else if (resultValue > 0)               // Visual Studio의 기본 스타일을 사용합니다.
                {
                    _results.NumPositiveResults++;
                }
            }
            // 메소드 내부에서도 1개 이상의 빈줄 사용을 하지 않습니다.
            return resultValue;
        }

        public void ExpressionBodies()
        {
            // 단순 람다의 경우 가능한 한 한 줄에 고정하십시오. 브래킷이나 브레이스가 필요하지 않습니다.
            Func<int, int> increment = x => x + 1;

            // 사용 가능
            Func<int, int, long> difference1 = (x, y) =>
            {
                long diff = (long)x - y;
                return diff >= 0 ? diff : -diff;
            };

            // 한줄을 띄워서 정의를 할 경우 전체 본문을 들여쓰기 합니다.
            Func<int, int, long> difference2 =
                (x, y) =>
                {
                    long diff = (long)x - y;
                    return diff >= 0 ? diff : -diff;
                };

            // 인라인 람다 인수도 동일한 룰을 사용합니다. 
            CallWithDelegate(
                (x, y) =>
                {
                    long diff = (long)x - (long)y;
                    return diff >= 0 ? diff : -diff;
                });

            void DoNoting() { }                         // 빈 블록은 간결하게 처리합니다.

            // 가능한 경우 첫 번째 인수와 정렬하여 인수를 줄바꿈 합니다.
            void AVeryLongFunctionNameThatCausesLineWrappingProblems(int longArgumentName,
                                                                     int p1, int p2)
            { }

            // 인수 선과 첫 번째 인수의 정렬이 맞지 않거나 읽기 어려운 경우, 4개의 공백 들여쓰기 한 줄에 표시
            void AnotherLongFunctionNameThatCausesLineWrappingProblems(
                int longArgumentName, int longArgumentName2, int longArgumentName3)
            { }

            void CallingLongFunctionName()
            {
                int veryLongArgumentName = 1234;
                int shortArg = 1;
                // 가능한 경우 첫 번째 인수와 정렬하여 인수를 줄바꿈합니다.
                AnotherLongFunctionNameThatCausesLineWrappingProblems(shortArg, shortArg,
                                                                      veryLongArgumentName);
                // 인수 선과 첫 번째 인수의 정렬이 맞지 않거나 읽기 어려운 경우, 4개의 공백 들여쓰기 한 줄에 표시
                AnotherLongFunctionNameThatCausesLineWrappingProblems(
                    veryLongArgumentName, veryLongArgumentName, veryLongArgumentName);
            }
        }

        private void CallWithDelegate(Func<object, object, long> p)
        { }
    }
}

C# coding guidelines

Constants

  • const로 만들 수 있는 변수와 필드는 항상 const로 만들어야합니다.
  • const를 사용할 수 없는 경우 읽기 전용이 적절한 대안이 될 수 있습니다.
  • 매직 넘버보다 명명 된 상수를 선호합니다.
    • var count = user.Count() + 10; (X)
    • var count = user.Count() + _absentUser; (O)

IEnumerable vs IList vs IReadOnlyList

  • 입력의 경우 가능한 가장 제한적인 컬렉션 유형을 사용합니다 (예 : IReadOnlyCollection / IReadOnlyList / IEnumerable을 입력이 변경 불가능해야하는 경우 메서드에 대한 입력으로 사용).
  • 출력의 경우 반환 된 컨테이너의 소유권을 소유자에게 전달하는 경우 IEnumerable보다 IList를 선호합니다. 소유권을 이전하지 않는 경우 가장 제한적인 옵션을 선호합니다.
    • private IList<Person> GetPeople(){};

Generators vs containers

다음을 염두에두고 최선의 판단을 내리십시오.

  • 생성기 코드는 종종 컨테이너를 채우는 것보다 읽기 어렵습니다.
  • 예를 들어, 모든 결과가 필요하지 않은 경우와 같이 결과가 느리게 처리될 경우 제너레이터 코드가 더 좋은 성능을 낼 수 있습니다.
  • ToList()를 통해 컨테이너로 직접 변환되는 생성기 코드는 컨테이너를 직접 채우는 것보다 성능이 떨어집니다.
  • 여러 번 호출되는 생성기 코드는 컨테이너를 여러 번 반복하는 것보다 상당히 느립니다.

Property styles

  • 한 줄 읽기 전용 속성의 경우 가능하면 식 본문 속성 (=>)을 선호합니다.
    • private string _name;
    • public string Name => _name;
  • 그 밖의 모든 경우에는 이전 { get; set; } 구문을 사용합니다.
    • public string Name { get; set; }
    • private string _id;
    • public string Id { get => _id; set => SetProperty(ref _id, value); }

Expression body syntax

For example:

int SomeProperty => _someProperty
  • 람다와 속성에서 식 본문 구문을 신중하게 사용합니다.
  • 메서드 정의에 사용하지 마십시오. 
  • 메서드 및 기타 범위 코드 블록과 마찬가지로 여는 중괄호를 포함하는 줄의 첫 번째 문자에 닫는 위치를 정렬합니다. 예제는 샘플 코드를 참조하십시오.

Structs and classes:

  • 구조체는 클래스와 매우 다릅니다.
    • 구조체는 항상 값으로 전달되고 반환됩니다.
    • 반환 된 구조체의 멤버에 값을 할당해도 원본은 수정되지 않습니다. transform.position.x = 10 변형은 position.x를 10으로 설정하지 않습니다. 여기서 position은 값으로 Vector3를 반환하는 속성이므로 원본 복사본의 x 매개 변수만 설정합니다.
  • 거의 항상 클래스를 사용합니다.
  • 유형의 인스턴스가 작고 일반적으로 수명이 짧거나 일반적으로 다른 객체에 포함되는 경우와 같이 유형이 다른 값 유형처럼 처리 될 수있는 경우 struct를 고려하십시오. 좋은 예로는 Vector3, Quaternion 및 Bounds가 있습니다.
  • 이 지침은 성능 문제로 인해 구조체 사용을 강제해야하는 경우 팀마다 다르게 적용할 수 있습니다.

Lambdas vs named methods

람다의 코드가 크거나 (예 : 선언을 제외하고 두 개 이상의 문) 여러 위치에서 재사용되는 경우 이름이 지정된 메서드를 사용하는 것이 좋습니다.

Field initializers

필드 이니셜 라이저는 일반적으로 권장됩니다.

private int _count = 0;

Extension methods

  • 원본 클래스의 소스를 사용할 수 없거나 소스를 변경할 수없는 경우에만 확장 메서드를 사용하십시오.
  • 추가되는 기능이 원래 클래스의 소스에 추가하기에 적합한 핵심 기능인 경우에만 확장 방법을 사용하십시오.
    • 참고 - 확장되는 클래스의 소스가 있고 원래 클래스의 관리자가 함수를 추가하지 않는다고, 확장 메서드를 사용하는 것은 좋지 않습니다.
  • 모든 곳에서 사용할 수 있는 핵심 라이브러리에만 확장 메서드를 넣습니다. 일부 코드에서만 사용할 수 있는 확장은 가독성 문제가됩니다.
  • 확장 메서드를 사용하면 항상 코드가 난독화되므로 추가하지 않는 편이 좋습니다.

ref and out

  • 입력이 아닌 반환 매개 변수에는 out을 사용하십시오.
  • 메서드 정의에서 다른 모든 매개 변수 뒷쪽에 out을 배치합니다.
  • ref는 입력값을 변경해야 할 때는 사용하지 않아야합니다.
  • 구조체 전달을위한 최적화로 ref를 사용하지 마십시오.
  • ref를 사용하여 수정 가능한 컨테이너를 메서드에 전달하지 마십시오. ref는 제공된 컨테이너를 완전히 다른 컨테이너 인스턴스로 교체해야하는 경우에만 필요합니다.

LINQ

  • 일반적으로 LINQ의 긴 체인보다는 한 줄의 LINQ 호출과 명령형 코드를 선호합니다. 명령형 코드와 심하게 연결된 LINQ를 혼합하는 것은 읽기 어려운 경우가 많습니다.
  • SQL 스타일 LINQ 키워드보다 멤버 확장 메서드를 선호합니다. 예를 들어 myList where x 보다 myList.Where(x)를 선호합니다.

Array vs List

  • 일반적으로 공용 변수, 속성 및 반환 형식에 대해 배열보다 List<>를 선호합니다 (위의 IList / IEnumerable / IReadOnlyList에 대한 지침을 염두에 두십시오).
  • 컨테이너의 크기가 변경 될 수 있는 경우 List<>를 선호합니다.
  • 컨테이너의 크기가 고정되고 생성 시점에 알려진 경우 배열을 선호합니다.
  • 다차원 컨테이너는 배열을 선호합니다.
  • Note:
    • Array와 List<>는 모두 연속적인 선형 컨테이너입니다.
    • C++ 배열 vs std::vector와 유사하게 배열은 고정 용량이지만 List<>를 추가 할 수 있습니다.
    • 어떤 경우에는 배열이 더 성능이 좋지만 일반적으로 List<>가 더 유연합니다.

Folders and file locations

  • 프로젝트와 일관성을 유지하십시오.
  • 가능하면 평평한 구조를 사용합니다.
  • 솔루션 폴더와 프로젝트의 폴더는 맵핑이 않되기 때문에 사용에 유의합니다.

Use of tuple as a return type

일반적으로 특히 복잡한 형식을 반환 할 때 Tuple<>보다 명명 된 클래스 형식을 선호합니다. 반환값 2개까지만 사용을 권장합니다.

private Tuple<string,string> GetUser() {}

String interpolation vs String.Format() vs String.Concat vs operator+

  • 일반적으로, 특히 메시지를 기록 및 assert하기 위해 가장 읽기 쉬운 것을 사용합니다.
  • 체인 operator+ 연결은 속도가 느려지고 상당한 메모리 변동을 유발함으로 권장되지 않습니다.
    • var address = state + " " + address1 + " " + address2; (X)
    • var address = $"{state} {address1} {address2}"; (O)
  • 성능이 중요한 경우 StringBuilder는 여러 문자열 연결에 대해 더 빠릅니다.

using

  • 일반적으로 using으로 긴 유형 이름을 별칭으로 지정하지 마십시오. 종종 이것은 Tuple<>이 클래스로 바뀌어야한다는 신호입니다.
    • 예 : using RecordList = List<Tuple<int, float>>는 클래스를 사용하는 것이 좋습니다.
  • using 문은 파일 범위 일 뿐이므로 제한적으로 사용됩니다. 유형 별칭은 외부 사용자가 사용할 수 없습니다.

Object Initializer syntax

For example: (Visual Studio의 기본 형태를 사용합니다.)

var x = new SomeClass 
{
    Property1 = value1,
    Property2 = value2,
};
  • 개체 이니셜 라이저 구문은 'plain old data' 유형에 적합합니다.
  • 생성자가있는 클래스 또는 구조체에는 이 구문을 사용하지 마십시오.
  • 여러 줄로 분할하는 경우 한 블록 수준을 들여 씁니다.

Namespace naming

  • 일반적으로 네임 스페이스는 2 레벨 이하이어야합니다.
  • 파일/폴더 레이아웃이 네임 스페이스와 일치하도록 강제하지 마십시오.
  • 공유 라이브러리/모듈 코드의 경우 네임 스페이스를 사용하십시오. unity_app과 같은 리프 '애플리케이션'코드의 경우 네임 스페이스가 필요하지 않습니다.
  • 새 최상위 네임 스페이스 이름은 전역 적으로 고유하고 인식 할 수 있어야합니다.

Default values/null returns for structs

  • 'success' 부울 값과 구조체 out 값을 반환하는 것을 선호합니다.
  • 성능이 문제가되지 않고 결과 코드가 훨씬 더 읽기 쉬운 경우 (예 : 연결된 null 조건 연산자와 깊게 중첩 된 if 문) nullable 구조체가 허용됩니다.

Removing from containers while iterating

다른 많은 언어와 마찬가지로 C#은 반복하는 동안 컨테이너에서 항목을 제거하기위한 명확한 메커니즘을 제공하지 않습니다. 몇 가지 옵션이 있습니다.

  • 어떤 조건을 만족하는 항목을 제거하기만 하면되는 경우 someList.RemoveAll(somePredicate)을 권장합니다.
  • 반복해서 다른 작업을 수행해야하는 경우 RemoveAll이 충분하지 않을 수 있습니다. 일반적인 대체 패턴은 루프 외부에 새 컨테이너를 만들고, 새 컨테이너에 보관할 항목을 삽입하고, 반복이 끝날 때 원래 컨테이너를 새 컨테이너로 교체하는 것입니다.

Calling delegates

대리자를 호출 할 때 Invoke()를 사용하고 null 조건부 연산자를 사용합니다. 예) SomeDelegate?.Invoke(). 이것은 호출 사이트의 호출이 '호출중인 대리인'으로 명확하게 표시됩니다. null 검사는 스레딩 경쟁 조건에 대해 간결하고 강력합니다.

The var keyword

  • 잡음이 심하거나 명백하거나 중요하지 않은 유형의 이름 명시를 피함으로써 가독성을 지원하는 경우 var를 사용하는 것이 권장됩니다.
  • C# 9.0에서 추가된 Target-typed new expressions을 사용해서 표현 할 수도 있습니다.
    • 예) Point p = new (3, 5);
  • Encouraged :
    • 유형이 분명한 경우-예 : var apple = new Apple(); 또는 var request = Factory.Create<HttpRequest>();
    • 다른 메소드로 직접 전달되는 일시적인 변수의 경우-예 : var item = GetItem(); ProcessItem(item);
  • Discouraged :
    • 기본 유형으로 작업 할 때-예 : var success = true;
    • 컴파일러에서 해결된 내장 숫자 유형으로 작업 할 때-예 : var number = 12 * ReturnsFloat();
    • 사용자가 유형을 아는 것이 분명하게 도움이 될 때-예 : var listOfItems = GetList();

Attributes

  • 속성은 연관된 필드, 속성 또는 메서드 위의 줄에 나타나야하며 멤버와 줄 바꿈으로 구분되어야합니다.
  • 여러 속성은 줄 바꿈으로 구분해야합니다. 이렇게하면 속성을 더 쉽게 추가하고 제거 할 수 있으며 각 속성을 쉽게 검색 할 수 있습니다.

Argument Naming

Google C++ 스타일 가이드에서 파생되었습니다.

 

함수 인수의 의미가 명확하지 않은 경우 다음 해결 방법 중 하나를 고려하십시오.

  • 인수가 리터럴 상수이고 동일한 상수가 여러 함수 호출에서 동일하다고 암묵적으로 가정하는 방식으로 사용되는 경우 명명된 상수를 사용하여 해당 제약 조건을 명시하고 유지되도록 보장합니다.
  • bool 인수를 enum 인수로 바꾸려면 함수 서명을 변경하는 것이 좋습니다. 이렇게하면 인수 값이 자체 설명됩니다.
  • 크고 복잡한 중첩 식을 명명 된 변수로 바꿉니다.
  • 호출 사이트에서 인수 의미를 명확히 하기 위해 Named Arguments를 사용하는 것이 좋습니다.
  • 여러 구성 옵션이 있는 함수의 경우 모든 옵션을 보유하고 해당 인스턴스를 전달하는 단일 클래스 또는 구조체를 정의하는 것이 좋습니다. 이 접근 방식에는 몇 가지 장점이 있습니다. 옵션은 그 의미를 명확히 하는 호출 사이트에서 이름으로 참조됩니다. 또한 함수 인수 갯수를 줄여 함수 호출을 읽고 쓰기가 더 쉬워집니다. 추가 혜택으로 다른 옵션이 추가 될 때 호출 사이트를 변경할 필요가 없습니다.

Consider the following example:

// Bad - 인수의 의미 파악이 어렵다
DecimalNumber product = CalculateProduct(values, 7, false, null);

versus:

// Good
ProductOptions options = new ProductOptions();
options.PrecisionDecimals = 7;
options.UseCache = CacheUsage.DontUseCache;
DecimalNumber product = CalculateProduct(values, options, completionDelegate: null);

Comment

주석을 추가할 때 권장되는 내용(참고 문서는 여기​​​​​​​)

  • 코드를 수정할 때는 주석도 항상 최신 상태로 유지하십시오.
  • 의견은 완전한 문장으로 구성되어야하며, 언어 명명 규칙을 따릅니다.
  • 모든 메소드의 시작 부분에서 메소드의 목적, 가정 및 제한 사항등을 입력합니다.
  • 코드 줄 끝에 주석을 추가하지 마십시오. 엔드 라인 의견은 코드를 더 읽기 어렵게 만듭니다. 그러나 가변 선언에 주석을 주는 경우 엔드 라인 주석이 적합합니다. 
  • 별표(*) 전체 줄(----, ====)과 같은 주석을 사용하지 마십시오. 대신 공백을 사용하여 코드의 주석을 구분하십시오. 
  • 블록 설명을 꾸미기 프레임으로 둘러싸지 않도록 합니다. 매력적으로 보일지 모르지만 유지하기가 어렵습니다.
  • 배포하기 전에 미래의 유지 보수 작업 중 혼동을 피하기 위해 임시 또는 외부 주석을 모두 제거하십시오.
  • 코드의 복잡한 부분에 대한 설명이 필요한 경우, 코드를 다시 작성해야 하는지 여부를 결정하기 위해 코드를 검토하십시오. 가능하면 잘못된 코드를 문서화하지 마십시오. 다시 작성하십시오.
  • 코딩할 때 코멘트를 함께 작성하세요. 왜냐하면 나중에 할 시간이 없을 가능성이 높으니까요.
  • 익살스러운 사이드바 발언과 같이 부적절하거나 불필요한 코멘트를 사용하지 않도록 합니다.
  • 코멘트를 사용하여 코드의 의도를 설명하십시오. 
  • 코드에서 쉽게 알 수 없는 어떤 것이든 코멘트를 추가 하세요. 코드가 명확하지 않은 적절한 수준의 의미를 판단하기 위해 최선을 다하십시오.
  • 루프 및 논리 분기로 구성된 코드에 주석을 사용하십시오. 이러한 영역은 소스 코드를 읽을 때 독자에게 도움이 되는 핵심 영역입니다.
  • 응용프로그램 전체에서 일관된 구두점 및 구조를 가진 통일된 스타일을 사용하여 주석을 구성합니다.
  • 주석에는 양식 피드 및 백스페이스와 같은 특수 문자가 포함되어서는 안 됩니다.

Implementation Comment Formats

Block Comments

// Here is a block comment 
// that breaks across multiple 
// lines.

Single-Line Comments

짧은 설명은 이어지는 코드 수준에 들여쓰여진 한 줄에 나타날 수 있습니다. 주석을 한 줄로 작성할 수 없는 경우, 주석 블록 형식을 따라야 합니다. 한 줄 설명 앞에는 빈 줄이어야 합니다. 다음은 코드에서 한 줄 코멘트의 예입니다.

if (condition) 
{
    // Handle the condition.
    ...
}

Trailing Comments

매우 짧은 코멘트는 설명하는 코드와 동일한 줄에 나타날 수 있지만, 코멘트와 문장이 분리될 수 있을 만큼 충분히 멀리 이동해야 합니다. 코드에 둘 이상의 짧은 주석이 나타나는 경우 모두 동일한 탭 설정으로 들여써야 합니다.

if (a == 2) 
{
    return true; // special case
}
else
{
    return isPrism(a); // Works only for odd a
}

Documentation Comments

C#는 개발자들이 XML을 사용하여 코드를 문서화할 수 있는 메커니즘을 제공합니다. 소스 코드 파일에서 ///로 시작하고 클래스, 위임자 또는 인터페이스와 같은 사용자 정의 유형 앞에 있는 줄, 필드, 이벤트, 속성 또는 메서드와 같은 멤버 또는 네임스페이스 선언을 주석으로 처리하여 파일에 저장할 수 있습니다.

 

XML 문서는 classes, delegates, interfaces, events, methods 및 properties에 필요합니다. 

using System;
namespace CodeStyle
{
    /// <summary>
    /// 클래스 레벨 요약 설명서
    /// </summary>
    /// <remarks>
    /// remarks 태그를 통해 유형 또는 구성원과 긴 주석을 연결 할 수 있습니다.
    /// </remarks>
    public class SomeClass
    {
        private readonly string _name; 
        /// <summary>
        /// 프로퍼티명
        /// </summary>
        /// <value>
        /// 값 태그는 속성 값을 설명하는데 사용됩니다.
        /// </value>
        public string Name
        {
            get
            {
                if (_name == null)
                {
                    throw new Exception("Name is null");
                }
                return _name;
            }
        }

        /// <summary>
        /// 클래스 생성자
        /// </summary>
        public SomeClass()
        {
            // TODO: 생성자 로직을 추가합니다.
        }

        /// <summary>
        /// SomeMothod에 대한 설명
        /// </summary>
        /// <param name="s">s에 대한 매개 변수 설명은 여기에 입력합니다.</param>
        /// <seealso cref="string">
        /// 태그의 cref 속성을 사용하여 유형 또는 멤버를 참조할 수 있으며 컴파일러가 참조가 
        /// 있는지 확인합니다.
        /// </seealso>
        public void SomeMethod(string s)
        {
        }

        /// <summary>
        /// SomeOtherMethod에 대한 설명
        /// </summary>
        /// <returns>
        /// 리턴 결과는 returns 태그에 설명합니다.
        /// </returns>
        /// <seealso cref="SomeMethod(string)">
        /// 특정 method를 참조하기 위해 cref 속성을 사용하는 것에 주목하십시오.
        /// </seealso>
        public int SomeOtherMethod()
        {
            return 0;
        }

        /// <summary>
        /// 응용 프로그램의 진입 점입니다.
        /// </summary>
        /// <param name="args">명령줄 인수 목록입니다.</param>
        /// <returns></returns>
        public static int Main(string[] args)
        {
            // TODO: 여기에서 응용 프로그램을 시작하는 코드를 추가하십시오
            return 0;
        }
    }
}

​​​​​​​Comment Tokens - TODO, HACK, UNDONE

​​​​​​​코드에 주석 토큰이 있는 주석을 추가하면 작업 목록 창에 바로 가기를 자동으로 추가합니다. 작업 목록에 표시된 설명을 두 번 클릭하여 삽입 지점을 설명이 시작되는 코드 행으로 직접 이동합니다.

 

작업 목록은 Ctrl+W,T를 누르면 보입니다.

 

작업 목록 창에 주석 하이퍼링크를 추가하려면 TODO, HACK 또는 UNDONE을 입력합니다. 입력 방법은 전체 대문자 혹은 소문자와 :을 추가하며 예제는 아래와 같습니다.

// todo: 할일에 대해 설명합니다. 
// HACK: 코드 리팩토링이 필요합니다. 
// undone: 아래 로직은 미완료 상태입니다.

Test Code 정책

소스에 포함되어 있는 테스트 코드들의 경우 테스트 내용과 설명을 주석으로 추가합니다.

주석으로 처리된 테스트 코드들은 todo, hack, undone 등의 토큰을 추가해서 언제 삭제할지를 명시합니다. 

Debug Mode 정책

Debug 모드에서 실행을 한 애플리케이션이라도 기본 실행화면은 Release 모드와 동일하게 보여야 합니다.

#region #endregion(블로그 참고)

C# region은 2003년 비주얼 스튜디오 .NET를 통해 #region 리즌 열기, #endregion 리즌 닫기이라는 키워드를 사용하여 코드 블록을 이름 짓고 접는 방법으로 도입되었습니다.

 

접을 수 있는 region에 대한 개념은, 접을 수 있는 네임스페이스, 클래스, 메서드, if-statement 이전에 나온 기능입니다.

 

Regions의 단점은 다음과 같습니다.

  • 많은 regions을 사용하면 Code를 읽어야하는 다른 개발자들에게 고통을 줍니다.
  • 일부 개발자들은 800또는 8000줄의 클래스를 region을 이용해서 덜 끔찍하고 냄새가 나지 않게 만드는데 사용하는데, 이는 대형 코드 블록을 리팩토링하지 않기 위해 region을 사용해서 숨기는 나쁜 습관을 만들게 됩니다.
  • 일반적으로 regions을 사용해서 코드 블럭을 만들어야 할 정도의 클래스라면 작은 단위의 클래스로 분리하는 것을 선호 합니다.

Region 사용 규칙

  • Region을 사용해서 소스코드 전체에서 파트별로 구분하는 것은 하지 않습니다.
  • Region은 최소한의 범위에서만 사용합니다.
    • 예) DependencyProperty와 같이 2개의 프로퍼티로 나누어 생성되는 것을 하나로 묶을 때
    • 예) Public 속성의 private 필드가 함께 구성되어서 PropertyChanged 이벤트를 발생시키는 경우
  • 하나의 클래스에 Static과 Instance가 동시에 존재할 때 구분을 하기위해 사용하는 경우

CodeMaid

C# 스타일 가이드를 기준이 쉽게 적용될 수 있도록 Extension을 사용하면 좋습니다.

 

Extensions -> Online 목록에 상단에 위치해 있으니 선택 후 Download 선택 -> 비주얼 스튜디오 종료 후 자동 설치

비주얼 스튜디오 재시작 -> Extensions -> CodeMaid -> Options -> Import -> 설정 파일 선택

 

설정

저장시 자동으로 cleanup 동작 실행

 

코드 복잡도 확인

Extensions -> CodeMaid -> Spade 선택

반응형
댓글