사실 코딩 스타일에 대한 기준을 명확히 제시하기는 어렵습니다. 하지만 잘 작성된 코드들을 보면 몇 가지 공통적인 특징들을 볼 수 있습니다.
문서화
코드작성 시 충분한 주석을 달아 문서화하는 것이 중요합니다. 예를 들어 함수가 어떤 역할을 하는 지, 입력 값과 출력값은 무엇인지, 함수를 어떻게 사용해야 하는지 등을 명확하게 기술하는 것이 좋습니다.
/**
* @brief 두 정수의 합을 계산한다.
*
* @param a : 첫번째 정수.
* @param b : 두번째 정수.
* @return 두 정수의 합.
*/
int sum(int a, int b) {
return a + b;
}
분할
코드를 기능 단위로 나누어 모듈화하는 것이 좋습니다. 이렇게 하면 코드의 유지보수성이 좋아지고, 코드 중복도 줄일 수 있습니다.
분할 전 코드
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int arr[n];
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
double avg = (double)sum / n;
cout << "Sum: " << sum << endl;
cout << "Average: " << avg << endl;
return 0;
}
분할 후 코드
#include <iostream>
#include <vector>
using namespace std;
vector<int> getInput(int n)
{
vector<int> arr(n);
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
return arr;
}
int getSum(vector<int> arr)
{
int sum = 0;
for (int i = 0; i < arr.size(); i++) {
sum += arr[i];
}
return sum;
}
double getAvg(int sum, int n)
{
return (double)sum / n;
}
int main() {
int n;
cin >> n;
vector<int> arr = getInput(n);
int sum = getSum(arr);
double avg = getAvg(sum, n);
cout << "Sum: " << sum << endl;
cout << "Average: " << avg << endl;
return 0;
}
명명 규칙
접두어
접두어는 변수나 함수의 이름 앞에 특정 문자열을 붙여서 그 변수나 함수의 역할을 명시하는 방법입니다. 다음은 흔히 사용하는 접두어의 예입니다.
int m_data; // m_ 멤버 변수를 나타낸다.
static int s_count; // s_ 정적 변수를 나타낸다.
int g_value; // g_ 전역 변수를 나타낸다.
int* p_memory // p_ 포인터를 나타낸다.
list<int>::iterator i_elem //i_ 반복자(iterator)를 나타낸다.
헝가리안 표기법
헝가리안 표기법은 변수의 타입을 변수의 이름 앞에 붙여서 정보를 더 담도록 표기하는 방법입니다.
int nCount; // n : 변수가 정수형임을 나타냄
float fValue; // f : 변수가 실수형임을 나타냄
char* pszName; // psz : 변수가 포인터 문자형임을 나타냄
게터와 세터
클래스에 mStatus라고 정의된 데이터 멤버에 접근할 때 주로 getStatus()와 같은 게터나 setStatus()와 같은 세터를 사용합니다. 부울 타입의 데이터 멤버에 접근할 때는 get 대신 is란 접두어를 붙일 때가 많습니다.(예 : isRunning()). C++ 언어에서는 이런 메서드 명명 규칙을 따로 정해두지 않았지만 팀 단위로 개발할 때는 이런 규칙을 도입할 필요가 있습니다.
대소문자 활용
대소문자는 코드의 가독성을 높이기 위해서 중요합니다. 대체로 변수명은 소문자, 클래스명은 대문자로 시작하는 것이 일반적인 규칙입니다. 또한 상수나 매크로 이름은 대문자와 언더바로 작성하는 것이 일반적입니다.
#include <iostream>
using namespace std;
// 변수명은 소문자로 시작하며, 명사나 형용사로 작성합니다.
int age;
double height;
string name;
// 클래스명은 대문자로 시작하며, 명사로 작성합니다.
class Person {
private:
string name;
int age;
public:
void setName(string n) {
name = n;
}
void setAge(int a) {
age = a;
}
};
// 상수와 매크로 이름은 대문자와 언더바로 작성합니다.
const int MAX_COUNT = 100;
#define PI 3.14
int main() {
// 변수명은 소문자로 시작합니다.
int count = 0;
double radius = 1.0;
// 클래스명은 대문자로 시작합니다.
Person person;
person.setName("John");
person.setAge(30);
// 상수와 매크로 이름은 대문자와 언더바로 작성합니다.
cout << "Max Count: " << MAX_COUNT << endl;
cout << "PI: " << PI << endl;
return 0;
}
네임스페이스를 적용한 상수
네임스페이스를 사용하여 상수를 정의할 때에는 상수 이름 앞에 해당하는 네임스페이스 이름을 명시해 줍니다. 이렇게 함으로써 상수 이름의 충돌을 방지하고, 상수의 역할을 더 명확하게 할 수 있습니다. 다음은 Constants라는 네임스페이스를 정의하고 그 안에 상수를 정의하는 예시입니다.
namespace Constants {
const double PI = 3.14159265358979323846;
const int MAX_VALUE = 1000;
}
int main() {
std::cout << "PI = " << Constants::PI << std::endl;
std::cout << "MAX_VALUE = " << Constants::MAX_VALUE << std::endl;
return 0;
}
언어 사용
상수 사용법
나쁜 코드는 대부분 매직 넘버(magic number)가 어지럽게 흩어져 있습니다. 어떤 함수에서는 대뜸 2.71828이란 값을 사용하기만 하고, 왜 그 값을 사용하고, 그 값이 무엇을 의미하는 지 아무런 설명이 없습니다. 수학을 잘 아는 사람이라면 이 값이 자연로그의 밑인 e의 근사값인 줄 알지만, 대부분의 사람은 모릅니다. C++에서 제공하는 상수 기능을 활용하면 2.71828과 같이 변하지 않는 값에 이름을 붙일 수 있습니다.
const double kApproximationForE = 2.71828182845904523536;
포인터 대신 레퍼런스 사용하기
C++에서 포인터는 메모리를 직접 조작할 수 있는 강력한 기능을 제공합니다. 하지만 사용하기가 어렵고 잘못 사용시 대부분 에러의 원인이 되기도 합니다. 반면 C++에서 제공하는 레퍼런스(Reference)를 사용하면 이러한 문제점들 대부분을 해결할 수 있습니다.
레퍼런스는 기존 변수의 별명을 만들어 사용하는 것으로, 포인터와 달리 메모리 주소 연산이 필요하지 않습니다. 따라서 더 안전하며, 코딩 스타일 측면에서도 레퍼런스를 사용하는 것이 낫습니다. *나 &같은 기호를 쓸 필요가 없이 일반 변수처럼 사용할 수 있기 때문입니다.
#include <iostream>
using namespace std;
// 레퍼런스를 사용한 swap 함수
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10;
int& ref = x; // x에 대한 레퍼런스 생성
cout << "x: " << x << endl; // 10
cout << "ref: " << ref << endl; // 값을 출력할 때 '*'을 사용하지 않는다.
int a = 10, b = 20;
cout << "Before swap - a: " << a << ", b: " << b << endl; // 10, 20
swap(a, b); // a, b의 주소값이 넘어가지만 '&'을 쓰지 않는다.
cout << "After swap - a: " << a << ", b: " << b << endl; // 20, 10
return 0;
}
사용자 정의 익셉션
C++에서는 익셉션(exception)을 무시하기 쉽습니다. 익셉션을 처리하라는 규칙이 없을 뿐만 아니라 nullptr을 리턴하거나 에러 플래그를 설정하는 기존 방식으로도 얼마든지 에러에 대처할 수 있기 때문입니다.
하지만 C++에서 제공하는 익셉션은 에러 처리에 관련된 기능을 풍부하게 제공하기 때문에, 용도에 맞게 정의해서 사용하는 것이 코드 스타일 측면에서 더 바람직합니다.
#include <iostream>
#include <exception>
#include <string>
using namespace std;
class DivideByZeroException : public std::exception
{
public:
DivideByZeroException(const std::string& message) : m_message(message) {}
const char* what() const noexcept override { return m_message.c_str(); }
private:
std::string m_message;
};
double divide(int a, int b)
{
if (b == 0) {
throw DivideByZeroException("Division by zero is not allowed");
}
return static_cast<double>(a) / b;
}
int main()
{
int a = 10, b = 0;
try {
double result = divide(a, b);
std::cout << a << " / " << b << " = " << result << std::endl;
}
catch (const DivideByZeroException& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
}
return 0;
}
포매팅
C++ 코드 작성 시 포매팅은 매우 중요한 요소 중 하나입니다. 이는 코드의 가독성을 높이고, 유지보수성을 높이기 위함입니다. 그러나 포매팅에 대한 세부적인 규칙은 개발자마다 다를 수 있으며 논쟁의 여지가 있습니다.
중괄호 정렬
중괄호 정렬에 관한 논쟁은 중괄호의 위치를 어디에 놓느냐에 따라 다릅니다. C++ 커뮤니티 내에서는 가장 일반적인 스타일은 중괄호를 다음 줄에 위치시키는 것입니다. 이러한 스타일은 가독성이 높고 코드 블록이 눈에 더 잘 들어오기 때문입니다. 하지만 몇몇 커뮤니티에서는 중괄호를 해당 줄의 끝에 위치시키는 것이 가독성이 더 높다고 주장하기도 합니다.
void printNumbers(int n) {
for (int i = 0; i < n; i++) {
if (i % 2 == 0) {
cout << i << " is even" << endl;
} else {
cout << i << " is odd" << endl;
}
}
}
...
void printNumbers(int n)
{
for (int i = 0; i < n; i++)
{
if (i % 2 == 0)
{
cout << i << " is even" << endl;
} else
{
cout << i << " is odd" << endl;
}
}
}
스페이스와 소괄호
또한, 스페이스와 소괄호에 대한 논쟁도 있습니다. 함수나 제어문을 작성할 때 소괄호 앞뒤에 스페이스를 둘지 말지에 대한 것입니다. 일부 개발자들은 스페이스를 넣으면 가독성이 좋아진다고 주장하지만, 다른 개발자들은 그렇지 않다고 주장하기도 합니다. C++ 커뮤니티에서는 스페이스를 넣지 않는 것이 더 일반적입니다.
if (i == 2) { //스페이스를 사용함
j = i + (k / m);
}
if(i == 2) { //스페이스를 사용하지 않음
j = i + (k / m);
}
포매팅은 매우 중요한 주제 중 하나이며, 커뮤니티 내에서 다양한 논쟁이 있습니다. 따라서, 정해진 규정이 없다면 반드시 정하는 것이 좋습니다. 개발자들은 자신이 속한 커뮤니티의 스타일 가이드를 따르는 것이 좋습니다. 이는 코드의 일관성을 유지하고 가독성을 높이기 위함입니다.
[참고]
1. 전문가를 위한 c++(c++(개정 4판) (저자 : 마크 그레고리 / 옮긴이 남기혁)
'Program Language > c++' 카테고리의 다른 글
C++ 중급(동적 메모리 사용하기) (20) | 2023.03.08 |
---|---|
C++ 기초(std::string_view 클래스) (10) | 2023.03.03 |
C++ 기초(std::string 클래스) (7) | 2023.03.02 |
C++ 기초(Raw String Literal) (13) | 2023.02.20 |
C++ 기초(직원 관리 시스템 - 사용자 인터페이스) (10) | 2023.02.13 |
댓글