참조
https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/coding-style/coding-conventions
언어 지침
1) 제네릭 예외 처리를 지양한다.
- 범용 예외 타입인 'Exception'을 포괄적으로 catch하지 말 것
→ 구체적인 예외 타입을 catch하여 예상 가능한 오류만 처리하기
2) 사용자가 예외 상황을 이해할 수 있도록 의미 있는 오류 메시지를 제공한다.
3) 컬렉션 조작에 LINQ 쿼리와 메서드를 사용하여 코드 가독성을 높인다.
- LINQ (Language Integrated Query)
: C#에서 컬렉션, 배열, 리스트 등 데이터를 쿼리하는 도구
→ DB에서 SQL로 데이터를 조회/조작하듯, C#에서는 LINQ로 데이터를 조회/조작할 수 있음
4) I/O 바인딩된 작업에 async 및 await를 사용한 비동기 프로그래밍을 사용한다.
- 입출력 (I/O) 바운드 작업
: CPU가 아닌 외부 시스템과의 통신에 의존하는 작업
ex) 파일 시스템 접근, 네트워크 통신, 데이터베이스 쿼리 등
→ 전통적인 방식으로 처리하면 해당 작업이 완료될 때까지 애플리케이션이 멈춰있게 됨
5) 교착 상태(Deadlock)에 주의하고, 적절한 경우 Task.ConfigureAwait를 사용한다.
- Task.ConfigureAwait(false)
: 비동기 작업이 완료된 후, 작업의 후속 처리(continuation)가 현재의 컨텍스트로 돌아가지 않도록 제어함
- 주로 UI 스레드에서 비동기 작업을 할 때, 불필요한 교착 상태를 방지하거나 성능을 향상시키기 위해 사용함
· 교착 상태
: 비동기 작업을 수행할 때, await 이후 해당 스레드로 돌아오기를 기다리다가 교착 상태에 빠질 수 있음
ex) UI 스레드에서 어떤 작업이 끝나길 기다리지만, 그 작업도 UI 스레드로 돌아가 후속 작업을 하려고 할 때
양쪽이 서로 대기하는 상황이 발생할 수 있음
- Task.ConfigureAwait(false)는 비동기 작업이 완료된 후 UI 스레드나 원래의 컨텍스트로 돌아가지 않고
다른 스레드에서 후속 작업을 처리하도록 함
· 이점 ① 교착 상태 방지
· 이점 ② 성능 최적화
: 후속 작업이 반드시 UI 스레드에서 실행될 필요가 없다면,
Task.ConfigureAwait(false)을 통해 불필요하게 UI 스레드로 돌아가는 것을 방지함
→ UI 스레드 부하 Down / 애플리케이션 응답성 Up
· 이점 ③ 컨텍스트 전환 비용 절약
6) 데이터 형식에 런타임 형식 대신 언어 키워드를 사용한다.
ex) System.String 대신 string / System.Int32 대신 int
7) 부호 없는 정수형 대신 int를 사용한다.
- unit 타입 (unsigned int)는 음수 값을 허용하지 않고 0과 양수만을 표현할 수 있음
8) 개발자가 변수의 자료형을 명확히 유추할 수 있는 경우에만 var를 사용한다. (암시적 형식 지역 변수 선언)
- C#은 기본적으로 변수를 선언할 때 앞에 변수의 자료형을 명시해주어야 함
→ 명시하지 않아도 개발자가 해당 변수의 자료형을 유추할 수 있는 경우에는 var 로 대체할 수 있음
- 모든 상황에서 var를 쓰더라도 컴파일러는 변수의 자료형을 추론할 수 있기 때문에 컴파일 에러가 발생하지 않음
→ 그러나, 가독성을 위해 변수의 타입이 명확하지 않을 때에는 var 대신 자료형을 명시하는 것이 좋음
▶ 문자열 데이터에 대한 지침
1) 문자열 보간법을 사용하여 짧은 문자열을 연결한다.
string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
2) 문자열을 다룰 때 많은 양의 텍스트를 반복적으로 추가할 때에는 System.Text.StringBuilder를 사용한다.
- C#에서 string은 불변함 (Java와 동일)
· 상수 풀 영역에 한 번 생성된 문자열은 변경되지 않음
· 동일한 문자열을 사용할 경우, 메모리 공간을 새로 생성하지 않고 기존에 저장되어 있는 문자열을 재사용함
· 문자열을 변경하려고 하면 새로운 문자열 객체가 생성됨
string str = "hello";
str = "world"; // 새로운 문자열 "world"를 가리킴, 이전 "hello"는 메모리에서 유지됨
→ 따라서, 문자열을 자주 변경해야 하는 경우 StringBuilder를 사용하는 것이 성능 면에서 효율적임
· StringBuilder : 가변 객체
- 문자열을 수정할 때 새로운 객체를 생성하지 않고, 내부 버퍼를 사용해 문자열을 변경함
- 스레드 안전을 보장하지 않음
: 여러 스레드가 동시에 같은 StringBuilder 객체를 수정할 경우 동기화를 보장하지 않음
→ 다중 스레드 환경에서는 별도의 동기화 처리 필요 (lock 활용)
- 병렬 처리나 다중 스레드를 사용하지 않는 상황에서는 빠른 성능을 제공하며, 효율적임
var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
※ Java와의 차이점
· Java는 스레드 안전을 보장하는 대신 처리 속도가 느린 StringBuffer와,
스레드 안전을 보장하지 않지만 처리 속도가 빠른 StringBuilder가 존재함
▶ 배열에 대한 지침
1) 아래와 같은 간결한 구문으로 배열을 초기화할 때에는 var를 사용할 수 없다.
string[] vowels1 = { "a", "e", "i", "o", "u" }; // var를 사용할 수 없음
2) 아래와 같이 명시적 인스턴스화를 사용할 때에는 var를 사용할 수 있다.
var vowels2 = new string[] { "a", "e", "i", "o", "u" }; // var를 사용할 수 있음
▶ 대리자(Delegate)에 대한 지침
- TODO : Delegate 형식에 대해 공부한 후 추가
▶ 예외 처리에 대한 지침
1) 대부분의 예외 처리에서는 try-catch 문을 사용한다.
2) using 문을 사용하여 코드를 간소화한다.
- 일반적으로 리소스 (파일, DB 연결, 네트워크 소켓 등)는 사용 후에 반드시 해제해야 함
→ using 문을 사용하면 이러한 리소스들을 자동으로 정리할 수 있음
- using
: IDisposable 인터페이스를 구현하는 객체에 대해 자동으로 Dispose 메서드를 호출하여 리소스를 해제함
# using 문 없이 리소스를 관리하는 방식 (try-finally)
FileStream fileStream = null;
try
{
fileStream = new FileStream("example.txt", FileMode.Open);
// 파일 작업 수행
}
finally
{
if (fileStream != null)
{
fileStream.Dispose(); // 리소스 해제
}
}
# using 문을 사용하여 리소스를 관리하는 방식
using (FileStream fileStream = new FileStream("example.txt", FileMode.Open))
{
// 파일 작업 수행
} // 여기서 fileStream이 자동으로 Dispose
▶ new 연산자에 대한 지침
1) 간결한 형식의 인스턴스화를 사용한다.
// 방식 1)
var firstExample = new ExampleClass();
// 방식 2)
ExampleClass instance2 = new();
2) 개체 이니셜라이저(Object Initializer)를 활용하여 인스턴스화한다.
var thirdExample = new ExampleClass
{
Name = "Desktop",
ID = 37414,
Location = "Redmond",
Age = 2.3
};
▶ 이벤트 처리기에 대한 지침
1) 나중에 제거할 필요가 없는 이벤트 처리기를 정의하려는 경우 람다 식을 사용하여 축약한다.
# 축약하지 않은 기존 정의
public Form1()
{
this.Click += new EventHandler(Form1_Click);
}
void Form1_Click(object? sender, EventArgs e)
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}
# 람다 식을 활용하여 축약한 정의
public Form2()
{
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}
▶ LINQ 쿼리에 대한 지침
1) 쿼리 변수에 의미 있는 이름을 사용한다.
2) 별칭을 사용하여 '익명 형식'의 속성 이름 대/소문자를 올바르게 표시한다.
var localDistributors =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { Customer = customer, Distributor = distributor }; // 익명 형식을 이용하여 객체로 반환
- 익명 형식 : https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/types/anonymous-types
3) 결과의 속성 이름이 모호하면 속성 이름을 바꾼다.
var localDistributors2 =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { CustomerName = customer.Name, DistributorID = distributor.ID };
4) where 절을 다른 쿼리 절 앞에 사용하여, 뒤에 있는 쿼리 절이 필터링된 데이터 집합에 대해 작동하게 한다.
var seattleCustomers2 = from customer in customers
where customer.City == "Seattle" // orderby보다 where을 앞에
orderby customer.Name
select customer;
5) 하나의 join 절 대신 여러 개의 from 절을 사용함으로써 내부 컬렉션에 더 간단하게 접근할 수 있다.
var scoreQuery = from student in students
from score in student.Scores
where score > 90
select new { Last = student.LastName, score };
▶ 암시적 형식 지역 변수에 대한 지침
1) 변수의 형식이 명확하면 지역 변수에 대해 암시적 형식(var)를 사용한다.
a. new 연산자
Person person = new Person(); // 형식이 명확하므로 var 사용 가능
var person = new Person();
b. 명시적 캐스트
var number = (int)3.14; // int로 명확하게 변환되므로 var 사용 가능
c. 리터럴 값
var number = 10; // int 형식으로 명확함
var text = "Hello"; // string 형식으로 명확함
2) 메서드/함수 호출의 반환 형식이 명확하지 않으면 var를 사용하지 않는다.
- 메서드 이름을 통해 형식을 명확히 알 수 있다고 가정하지 말 것
var result = GetSomeData(); // GetSomeData()의 반환 형식이 불분명하면 var 사용하지 않는 것이 좋음
List<int> result = GetSomeData(); // 반환 형식이 명확함
3) dynamic 대신 var를 사용하지 않는다.
- dynamic
· 런타임에 형식이 결정됨
: 컴파일 시에는 형식 검사가 이뤄지지 않으며, 실제 형식은 프로그램이 실행될 때 결정됨
· 컴파일 타임에 형식 검사 없이 다양한 형식의 값을 가질 수 있음
- var
· 컴파일 타임에 형식이 결정됨
: 컴파일러는 할당된 값의 형식을 바탕으로 변수의 타입을 추론함
· 한 번 결정된 형식은 변경할 수 없으며, 컴파일러는 강한 형식 검사를 수행함
→ 서로 다른 목적으로 사용되는 키워드이므로, 형식의 결정 시점에 따라 선택해서 사용해야 함
4) for 루프의 루프 변수에 암시적 형식을 사용한다.
for (var i = 0; i < 10000; i++)
{
// ...
}
5) foreach 루프의 루프 변수에는 명시적 형식을 사용한다.
foreach (char ch in laugh)
{
// ...
}
6) LINQ 쿼리의 결과를 변수에 할당할 때는 암시적 형식을 사용한다.
var groupedStudents = from student in students
group student by student.Grade into gradeGroup
select gradeGroup;
▶ using 지시문에 대한 지침
1) using 지시문은 네임스페이스 선언 외부에 배치한다.
using Azure;
namespace CoolStuff.AwesomeFeature
{
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
'C#' 카테고리의 다른 글
| [C#/dotnet] 페이지 구조 - 마스터 페이지 & 콘텐츠 페이지 (1) | 2024.10.24 |
|---|---|
| [C#/dotnet] 페이지 수명 주기 (3) | 2024.10.24 |
| [C#/dotnet] 제네릭 (Generics) (0) | 2024.10.23 |
| [C#/dotnet] LINQ (Language-Integrated Query) (0) | 2024.10.23 |
| [C#/dotnet] 식별자 명명 규칙 (0) | 2024.10.18 |
