Search
🖥️

코딩 규칙

이것은 Unity에 맞게 변환한 .NET / .NET Core의 네이밍 규칙입니다.
코딩에 대한 정답은 없지만, 오답은 있다고 생각합니다.
그러므로, 1단계로 지켜야 할 규칙들은 클린 코드 규칙 중 개인적인 것이나 프로그래머 간에 공유하는 것이라고 생각합니다.

클린코드 - 네이밍 규칙

네이밍에 의미 담기

string N
C#
복사
이런 식으로 코드를 작성하면 해당 변수가 어떤 값이 담기는지 쉽게 파악할 수 없습니다.
string userName;
C#
복사
자신만 알아볼 수 있는 규칙보다는, 누구든지 쉽게 파악할 수 있는 네이밍 규칙을 사용합시다.

오해할 수 있는 네이밍 피하기

아래와 같이 코딩하는 것은 변수 네이밍 이전에 Db라는 클래스 네이밍과 Get이라는 함수 네이밍이 모두 잘못된 표현입니다. 모든 클래스와 함수에는 의미가 담겨야 합니다.
var listFromDb = Db.Get().ToList();
C#
복사
다음과 같이 클래스부터 함수까지 의미를 담아서 표현해야 합니다.
var userInfoList = UserData.GetAllUserInfo().ToList();
C#
복사

헝가리언 표기법 x 하이브리드 표기법 사용하기

저는 주로 커멜 케이스, 파스칼 케이스, 스네이크 케이스 이 세 가지를 주로 사용하는 하이브리드 표기법을 사용합니다.
int iCount; string strName; long lUID;
C#
복사
위의 작성 방법은 헝가리안 표기법인데, 모든 변수 형식에 대해 앞에 유형을 나타내는 것은 불필요한 방법입니다.
private int _count; private string _name; public const long UID;
C#
복사
헝가리안 표기법을 제외하고, 규칙에 따라 작성해야 합니다.

일괄된 코딩 규칙 사용하기

코딩 규칙을 정했다면, 그 규칙에 맞게 코드를 작성해야 합니다.
private int _userCount; private int user_Power; int _userIndex; public bool Is_User_Bool; public bool isFrined; List USER_LIST; private List userInfo_List;
C#
복사
코딩 규칙은 다양한 방식으로 선언될 수 있지만, 어떤 규칙이든 하나의 규칙에 맞춰 작성되어야 합니다.
private int _userCount; private int _userPower; private int _userIndex; public bool isUser; public bool isFrined; private List userList; private List userInfoList;
C#
복사
변수의 이름이 private로 선언되었는지, public으로 선언되었는지, 또는 const인지 등등 규칙을 통해 알 수 있도록 이와 같은 방식으로 작성해야 합니다.

축약 단어 사용 금지

축약 단어를 사용하면 해당 단어의 의미가 정확하게 전달되지 않을 수 있습니다.
private int _eWorkTime private int _sWorkTime public string userN public string userC
C#
복사
축약 단어를 사용하면 정확한 뜻을 알 수 없습니다.
private int endWorkTime = 0; private int startWorkTime = 0; public string userName = string.Empty; public string userCount = string.Empty;
C#
복사
이번 정답 예시는 제가 사용하는 코딩 규칙에 맞게 초기화까지 해보았습니다.

상세한 코딩 규칙 적용하기

규칙을 정할 때는 최대한 사소한 부분까지 코딩 규칙이 적용되어야 합니다.
int count; public void SetUserName(string name) { // source code }
C#
복사
private int _count; // 클래스 private 변수는 앞에 언다바 이후 커멜 표기법으로 나타낸다. public void SetUserName(string name) // 함수 내부 변수는 언다바 없이 커멜 표기법으로 나타낸다. { // source code }
C#
복사

올바른 의미로 네이밍 적용하기

규칙에 맞게 네이밍을 작성했어도 변수 이름과 변수에 담긴 내용이 의미와 다르면, 오히려 더 혼란스러울 수 있습니다.
private int _userTotalCount // 해당 정보에는 유저의 수가 총 몇 명인지 담아야합니다.
C#
복사
만약 위의 변수에 유저의 총 수가 아닌 적들의 총 수를 담아버리면, 코드를 이해하는 데 어려워질 겁니다.

변수 규칙

이곳에서는 '변수'에 관한 기본적인 규칙만 다루도록 하겠습니다.
private const int SOCRE = 100; public int num = 0; protected int _num = 0; private int _num = 0; public int Num { get => num; set => num = value; } public void Test(int total) { }
C#
복사
접근 제어자는 생략하지 않습니다.
Cost 변수의 경우 대문자로 표시하고 가장 위에 표기합니다.
변수를 처음 설정할 때는 값을 초기화합니다.
변수의 접근 제어자에 따라 기본 순서를 정합니다. Public → Protected → Private. Public 변수의 경우 기본적인 카멜 변수 법으로 표기합니다.
Protected 변수의 경우 앞에 '' 언더바를 사용하고 이후에 카멜 변수 법으로 표기합니다.
Private 변수의 경우 앞에 '' 언더바를 사용하고 이후에 카멜 변수 법으로 표기합니다.
프로퍼티의 경우 가장 앞 글자가 대문자부터 시작합니다.
프로퍼티의 경우 모든 변수를 작성 후, 가장 아래에 작성합니다.
함수의 매개변수의 경우 기본적인 카멜 변수 법을 사용합니다.

public 변수 지양하기

변수를 public 변수로 사용하는 것보다는 프로퍼티나 함수를 사용하여 변수를 컨트롤하는 것이 바람직합니다.
외부에서 사용하는 모든 변수를 public으로 사용하면, 어디에서 어떻게 변수가 변경되고 사용되는지 확인하기 어려울 수 있습니다.
public int num; public bool isCheck;
C#
복사
위와 같은 코드를 아래와 같은 코드로 변경할 수 있습니다.
prviate int _num; private bool _isCheck; public int GetNum() { return _num; } public void SetNum(int num) { _num = num; } public bool GetCheck() // 해당 변수는 외부에서 변경을 할 수 없을 경우, Get 함수만을 사용한다. { return _isCheck; }
C#
복사

Magic String 사용하지 않기

매직 스트링이란, 바뀌지 않을 변수를 직접 입력하는 방법입니다.
public void Complete() { CompleteString("Comeplete"); Do("100"); }
C#
복사
위와 같이 매개변수를 값에 직접 입력하는 방식은 Magic String을 사용한다고 표현합니다.
private const string COMPLETE_TEXT = "Complete"; private const int COMPLETE_SCORE = 100; public void Complete() { CompleteString(COMPLETE_TEXT); Do(COMPLETE_SCORE); }
C#
복사
Magic String 대신, 최소한 const 변수를 사용합시다.

일관성 있는 변수명 사용하기

변수명을 설정하는 규칙은 사람에 따라 혹은 팀의 코딩 규칙에 따라 다를 수 있지만, 일관성 있는 규칙을 사용해야 합니다.
private int _totalEnemy_int = 0; private int _totalArmyInt = 0; private int _totalPets = 0; private int _powerTotal = 0;
C#
복사
위의 방법들은 모두 제각각 장단점이 있는 네이밍 방식입니다. 그러나 일관성 있게 작성되지 않았습니다.
private int _totalEnemy = 0; private int _totalArmy = 0; private int _totalPet = 0; private int _totalPower = 0;
C#
복사
일관성 있는 네이밍을 사용해야 합니다. 제가 사용하는 변수 네이밍 방법은 한국식 문법을 사용하는 방법입니다. 동사 + 명사 형식으로 만드는 방법인데, 변수뿐만 아니라 클래스, 함수 등 모든 네이밍 규칙을 위와 같이 사용하고 있습니다.

축약하지 않기, 모든 의미를 담기

가장 많이 하는 실수 중 하나가 바로 변수 명을 축약하고, 모든 의미를 담지 않는 것입니다.
private int _tEnemy = 0; private int _tAr = 0; private DateTime _date = DateTime.Now;
C#
복사
이런 식으로 작성하면, 다른 사람들이 자신이 만든 코드를 이해하는 데 어려움을 겪을 수 있습니다. 또한 시간이 지나면서 자신의 코드를 다시 볼 때에도 이해하기 어려울 수 있습니다.
private int _totalEnemy = 0; private int _totalArmy = 0; private DateTime _currentDate = DateTime.Now;
C#
복사
변수명은 길더라도 의미를 충분히 담는 것이 중요합니다.
물론 축약이 가능한 str, Init 등과 같은 프로그래머에서 널리 쓰이는 축약 용어들은 사용해도 괜찮습니다. 그러나, 어떤 것을 축약시키는 것이 적절한지 모르겠다면 축약하지 않은 것이 가장 좋습니다.

같은 변수를 가져오는 부분 주의하기.

코드가 길어지면, 같은 변수를 가져오는 함수를 여러 개 만들거나 같은 변수를 변경하는 함수가 여러 개 존재할 수 있습니다. 이러한 이유로 변수의 네이밍이 중요합니다.
private Enemy _enemy; // 아래의 함수들은 모드 위 _enemy를 갖고오는 함수입니다. public Enemy GetEnemyInfo(); public Enemy GetEnemyData(); public Enemy GetEnemyUnit();
C#
복사
적절한 네이밍을 하지 않으면 적 캐릭터 정보를 가져오는 함수를 많이 만들어야 할 수 있습니다.
public Enemy GetEnemy();
C#
복사
전 보통, 어떤 변수를 갖고 온다면 그 변수의 네이밍을 마지막에 작성하는 방식을 사용합니다.

변수 줄 맞춤 통일하기

public int Num { get => num; set => num = value;} public int Num { get => num; set => num = value; }
C#
복사
같은 프로퍼티라도 줄을 어떻게 맞추느냐에 따라 여러 가지 경우가 생길 수 있습니다.
"줄까지 통일하는 건 너무 귀찮은 일 아닌가요?"라고 생각할 수 있지만, 이러한 경우가 많아지면 코드 네이밍 규칙이 없는 것과 같아 이해하기 어려워질 수 있습니다.
프로퍼티뿐만 아니라 모든 변수, 함수, Enum, 클래스 등에서 줄을 어떻게 사용할 지도 통일시켜야 합니다.

자신만 아는 값 사용하지 않기, Enum 사용하기

private void DayOfWeek(int day) { if(day == 0) SunDay(); if(day == 1) Monday(); ... }
C#
복사
위와 같이 day에 해당하는 매개 변수를 자신만 아는 값으로 설정하고 함수를 만든다면, 다른 사람이 코드를 이해하기 위해서는 모든 코드를 정독해야 할 수도 있습니다.
public enum DAY { Sunday = 0, Monday = 1, ... } private void DayOfWeek(DAY day) { if(day == Day.Sunday) SunDay(); if(day == Day.Monday) Monday(); ... }
C#
복사
함수의 전반적인 부분만 보더라도 단 번에 이해가 되게, Enum 값을 활용하여 작성해야 합니다.

함수 규칙

부정 조건 피하기

함수를 사용할 때, 부정적인 조건문을 사용하는 것은 좋은 방법이 아닙니다.
if(!isSame()) { ~~~ }
C#
복사
이런 식으로 부정문을 사용한다면 눈에 잘 띄지 않아 코드를 이해하는데, 어려울 수 있습니다.
if(isSame()) { ~~~ } if(isNotSame()) { ~~~ } if(isSame()==false) { ~~~ }
C#
복사
isSame() 함수를 그대로 사용하거나 isSame()의 부정 조건을 반환하는 isNotSame() 함수를 사용하는 것이 좋습니다.
마지막으로 위의 모든 방법이 어렵다면, isSame() == false로 눈에 잘 띄게 표시하는 것이 바람직합니다.

분기 함수 나눠 사용하기 - 1

함수를 작성하는 과정에서 분기가 발생한다면, 나누는 것이 좋습니다.
public void DoResult(bool isSame) { if(isSame) ~~ else ~~ }
C#
복사
위와 같이 코드를 작성하면 코드가 길어질 수 있습니다. 또한, isSame 일 때와 아닐 때를 구분하여 봐야 하는 어려움도 있습니다.
public void DoSameResult() { ~~~ } public void DoNotSameResult() { ~~~ }
C#
복사
이처럼, 함수를 나누는 것이 바람직합니다.

분기 함수 나눠 사용하기 - 2

만약 함수의 작동 방식이 타입에 따라 달라진다면, 인터페이스 혹은 상위 클래스를 사용하여 나누는 것이 좋습니다.
class Enemy { private EnemyType _type = 'Normal' // 'Special', 'Boss' public long GetEnemyHP() { switch (_type) { case 'Normal': return GetCurrentHP(); case 'Special': return GetCurrentHP() + GetSpeicalHP(); case 'Boss': return GetCurrentHP() + GetSpeicalHP() + GetBossHP(); } } }
C#
복사
위와 같이 함수를 사용할 경우, 자신이 어떤 타입인지에 따라 GetEnemyHP()가 다른 방식으로 작동합니다.
따라서 프로그래머는 잘못 작동되었을 경우 현재 type을 확인해야 합니다. 심지어 위와 같이 작성할 경우 사이드 이펙트가 발생할 확률이 큽니다.
interface Enemy { long GetEnemyHP(); } class NormalEnemy : Enemy { public int GetEnemyHP() { return GetCurrentHP(); } } class SpecialEnemy: Enemy { public int GetEnemyHP() { return GetCurrentHP() + GetSpeicalHP(); } } class BossEnemy : Enemy { public int GetEnemyHP() { return GetCurrentHP() + GetSpeicalHP() + GetBossHP(); } }
C#
복사
클래스를 이렇게 분리해서 사용하면, 사용하는 클래스에 따라 동작 방식을 명확히 확인할 수 있습니다.

매개 변수 활용하기

클래스 변수보다는 지역변수를 활용하는 것이 바람직합니다.
private string _resultText = string.Empty; public void PrintResult(string resultText) { _resultText = resultText; Debug.Log(_resultText) }
C#
복사
이런 식의 실수는 하지 않을 것 같지만, 생각보다 많이 하는 실수 중 하나입니다.
_resultText 변수를 클래스 변수로 만들어 버리면, 추후에도 다른 방면으로 사용할 것이라고 추측하고 실수할 수 있습니다.
public void PrintResult(string resultText) { Debug.Log(_resultText) }
C#
복사
차라리 우선, 매개변수로 만들어 놓고 나중에 resultText를 클래스 변수로 사용해야 한다면, 그때 가서 클래스 변수로 바꾸는 것이 바람직합니다.

이중 타입 체크 피하기

타입을 체크하면서 타입 변환을 하는 것은 피해야 합니다.
public void EnemyAttack(Object enemy) { if (enemy.GetType() == typeof(NormalEnemy)) { (enemy as NormalEnemy).Attack(); } else if (enemy.GetType() == typeof(SpecialEnemy)) { (enemy as SpecialEnemy).Attack(); } }
C#
복사
타입은 체크하면서 변환할 수 있는 방법이 여러 가지 존재합니다.
public void EnemyAttack(Enemy enemy) { enemy.Attack(); }
C#
복사
첫 번째 매개변수로부터, 원하는 타입을 받아 실행하는 것이 가장 좋은 방법입니다.
만약 이러한 방법을 사용할 수 없고 함수 내부에서 타입을 결정해야 한다면, is와 as 키워드를 사용해야 합니다.
public void EnemyAttack(object enemy) { if(enemy is NormalEnemy normalEnemy) { normalEnemy.Attack(); } if(enemy is SpecialEnemy specialEnemy) { speicalEnemy.Attack(); } }
C#
복사
is 키워드 사용 작성법.
public void EnemyAttack(object enemy) { var normalEnemy = enemy as NormalEnemy; if(normalEnemy != null) { noramlEnemy.Attack(); return;} var specialEnemy = enemy as SpecialEnemy; if(specialEnemy != null) {specialEnemy.Attack(); return;} }
C#
복사
as 키워드 사용 작성법.

타입 체크 피하기

타입 체크를 하면서 변환되지 않으면 실행 중에 예외를 발생시키는 것보다 컴파일러 오류를 즉시 출력하는 것이 더 좋은 방법입니다.
private void Sum(dynamic A, dynamic B) { if (!int.TryParse(A, out a) || !int.TryParse(B, out b)) { Debug.Error("Error"); return; } return a + b; }
C#
복사
위 코드의 장점은 컴파일러 에러 없이 실행할 수 있다는 것입니다. 그러나 그렇게 할 필요가 없는 경우에는 작성하지 않는 것이 좋습니다.
private void Sum(int A, int B) { return a + b; }
C#
복사
위처럼 코드를 작성하여 컴파일러 도중 에러를 발생하게 하는 것이 좋습니다.

매개변수 합치기

매개변수가 너무 길다 보면, 함수를 알아보기 힘들 수 있습니다.
public void CreateEnemy(int enemyID, string enemyName, int enemyDamage, int enemySpeed) { ... }
C#
복사
위와 같이 코드를 작성하면 매개변수를 관리하기 힘들 뿐만 아니라, 함수를 사용하는 것이 불편할 수 있습니다.
public class Enemy { public int enemyID = 0; public string enemyName = string.Empty; public int enemyDamage = 0; public int enemySpeed = 0; } public void CreateEnemy(Enemy enemy) { ... }
C#
복사
한 종류에 관한 매개변수가 많은 경우, 하나의 클래스를 만들어 관리하는 것이 현명한 방법입니다. 이와 같은 방식은 아래 코드처럼 확장성 있는 함수를 만드는 데 용이합니다.
public void CreateEnemy(Enemy enemy) { ... } public void DestroyEnemy(Enemy enemy) { ... } public void AttackEnemy(Enemy enemy) { ... }
C#
복사

하나의 함수는 하나의 동작

기본적으로 함수는 하나의 동작을 구성하는 것이 바람직합니다.
public void MoveCharacter() { Enemy shortestEnemy = null; float shortestDistance = 0f; foreach(enemy in enemyList) { var currentDistance = Vector3.Distance(this.position, enemy.position); if(shortestDistance > currentDistance) { shortestDistance = currentDistance; shortestEnemy = enemy; } } if(enemy != null) Goto(enemy); }
C#
복사
위와 같이 하나의 함수가 여러 개의 동작으로 구성되어 있다면, 가능한 세분화하는 것이 좋습니다.
public void MoveToShortestEnemy() { var enemy = GetShortestEnemy(); if(enemy != null) Goto(enemy); } public Enemy GetShortestEnemy() { Enemy shortestEnemy = null; float shortestDistance = 0f; foreach(enemy in enemyList) { var currentDistance = Vector3.Distance(this.position, enemy.position); if(shortestDistance > currentDistance) { shortestDistance = currentDistance; shortestEnemy = enemy; } } return shortestEnemy; }
C#
복사
코드는 직관성을 유지하는 것이 중요합니다. 그렇게 하면 유지 보수가 쉽고, 추후에 GetShortestEnemy 함수가 필요할 때 활용할 수 있습니다.

함수의 용도와 이름은 같아야 합니다.

당연한 얘기겠지만, 프로그래머가 많이 실수하는 부분입니다.
public class Enemy { ... public void MoveToEnemy() { Attack(); } public void Recovery(int value) { mp += value; } }
C#
복사
위와 같은 코드는 극단적인 예시이지만, 많은 사람들이 실수하는 경우입니다.
첫 번째는 함수의 용도와 이름이 다른 경우입니다.
두 번째는 함수의 이름을 자신만 알 수 있게 요약해서 나타낸 경우입니다.
public class Enemy { ... public void MoveToEnemy() { Move(enemy); } public void RecoveryMP(int value) { mp += value; } }
C#
복사
정확하게 함수를 작성해야 합니다.

함수의 분리. (포괄적인 네이밍)

함수의 이름을 포괄적으로 작성하면 함수가 길어져서 어디서부터 어디까지가 무엇을 하는지 파악하기 어려울 수 있습니다.
public class Enemy { public void Init() { transform.position = Vector3.Zero; ... transform.rotation = Quaternion.Identity; ... _damage = 0; _hp = MaxHP(); ... } }
C#
복사
Enemy를 초기화하는 함수의 코드를 나누지 않고 모두 한 곳에 몰아넣으면, 이 함수를 이해하기 힘들 뿐만 아니라, 나중에 수정이 필요한 부분을 찾기도 어려울 수 있습니다.
public class Enemy { public void Init() { TrnasformInit(); StatInit(); ... } private void TransfromInit() { transform.position = Vector3.Zero; ... transform.rotation = Quaternion.Identity; ... } private void StatInit() { _damage = 0; _hp = MaxHP(); ... } }
C#
복사
함수를 세분화해서 나누는 방법도 유지 보수에 필수적인 요소입니다.

이전 함수, 새로운 함수 관리 방법

public void OldMoveFunction() { ... } public void NewMoveFunction() { ... }
C#
복사
새로운 Move 알고리즘에 따라 움직이는 함수가 개발되어, 이전의 OldMoveFunction()은 더 이상 사용되지 않습니다.
따라서, 이전 함수를 지우는 것이 가장 좋은 방법입니다.
// public void OldMoveFunction() public void MoveFunction() { ... }
C#
복사
만약 이전 코드를 다시 확인하려면, 흔적 코드를 남겨두고 유니티에서 사용하는 VCS를 사용하여 이전 코드를 확인할 수 있습니다. 하지만, 이전 함수를 계속 사용하고 싶거나 가끔 사용해야 하는 경우가 있을 수 있습니다.
이럴 때는 Obsolete Attribute를 사용하면 됩니다.
[Obsolete("더 이상 사용하지 않는 함수입니다.")] public void OldMoveFunction() { ... } public void NewMoveFunction() { ... }
C#
복사
위와 같이 사용할 경우, 해당 함수를 호출하면 경고 메시지가 출력됩니다.
주의! 경고 메시지를 띄우더라도 사용은 가능합니다!
(컨파일 에러를 띄우고 싶은 경우, Obsolete를 true로 사용하는 방법이 있습니다.)