코딩 스타일 가이드

Vais 커뮤니티가 권장하는 코딩 스타일입니다. 이 가이드를 따르면 코드가 일관되고 읽기 쉬워집니다.

네이밍 컨벤션 (Naming Conventions)

함수 이름

함수명은 snake_case를 사용합니다. 동사로 시작하는 것이 좋습니다.

# 좋은 예
F calculate_sum(numbers: [i64; 10]) -> i64 { }
F is_prime(n: i64) -> bool { }
F get_user_by_id(id: i64) -> Option<User> { }
F parse_json(text: str) -> Result<Json> { }
F validate_email(email: str) -> bool { }

# 나쁜 예
F CalcSum(numbers: [i64; 10]) -> i64 { }      # PascalCase 사용 금지
F isprime(n: i64) -> bool { }                 # 약자 사용
F getUserById(id: i64) -> Option<User> { }    # camelCase 사용 금지
F PARSE_JSON(text: str) -> Result<Json> { }   # SCREAMING_SNAKE_CASE 함수명 사용 금지

구조체 이름

구조체명은 PascalCase를 사용합니다.

# 좋은 예
S User {
    id: i64
    name: str
}

S HttpResponse {
    status_code: i64
    body: str
}

S LinkedListNode<T> {
    value: T
    next: i64
}

# 나쁜 예
S user { }                # snake_case 사용 금지
S userProfile { }         # camelCase 사용 금지
S USER_DATA { }           # SCREAMING_SNAKE_CASE 사용 금지

변수 이름

변수명은 snake_case를 사용합니다.

# 좋은 예
user_count := 0
total_price := 99.99
is_valid := true
temp_buffer := "data"
~ mutable_counter := 0

# 나쁬 예
UserCount := 0              # PascalCase 사용 금지
totalPrice := 99.99         # camelCase 사용 금지
TEMP_BUFFER := "data"       # 변수에 SCREAMING_SNAKE_CASE 사용 금지
x := 0                      # 의미 없는 한 글자 (루프 제외)

상수 이름

상수명은 SCREAMING_SNAKE_CASE를 사용합니다.

# 좋은 예
C MAX_BUFFER_SIZE: i64 = 1024
C DEFAULT_TIMEOUT: i64 = 30
C PI: f64 = 3.14159
C APP_VERSION: str = "1.0.0"
C ERROR_FILE_NOT_FOUND: i64 = 1

# 나쁜 예
C MaxBufferSize: i64 = 1024   # PascalCase 사용 금지
C max_buffer_size: i64 = 1024 # snake_case 사용 금지
C maxBufferSize: i64 = 1024   # camelCase 사용 금지

Trait 이름

Trait명은 PascalCase를 사용합니다. 보통 형용사나 행동을 나타냅니다.

# 좋은 예
W Drawable {
    F draw(&self) -> i64
}

W Serializable {
    F serialize(&self) -> str
}

W Comparable {
    F compare(&self, other: i64) -> i64
}

W Reader {
    F read(&self) -> i64
}

# 나쁜 예
W drawable { }        # snake_case 사용 금지
W draw_able { }       # snake_case 사용 금지

Enum 이름

Enum명과 변수명 모두 PascalCase를 사용합니다.

# 좋은 예
E Color {
    Red,
    Green,
    Blue
}

E HttpMethod {
    Get,
    Post,
    Put,
    Delete
}

color := Color.Red
method := HttpMethod.Post

# 나쁜 예
E color { }           # snake_case 사용 금지
E COLOR { }           # SCREAMING_SNAKE_CASE 사용 금지
E http_method { }     # snake_case 사용 금지

파일 및 모듈 구조

파일 이름

파일명은 snake_case를 사용합니다.

# 좋은 구조
src/
├── main.vais              # 메인 엔트리 포인트
├── lib.vais               # 라이브러리 메인
├── user.vais              # 사용자 관련 모듈
├── config.vais            # 설정 관련 모듈
├── database.vais          # 데이터베이스 관련 모듈
└── utils.vais             # 유틸리티 함수

# 나쁬 구조
src/
├── User.vais              # PascalCase 사용 금지
├── Config.vais            # PascalCase 사용 금지
├── get_user_by_id.vais   # 파일명이 너무 상세함

모듈 크기

파일이 너무 커지지 않도록 주의합니다.

권장 파일 크기:
- 300줄 이하: 이상적
- 300-500줄: 괜찮음
- 500줄 이상: 분할 검토

모듈 구성

관련 기능별로 파일을 구성합니다.

src/
├── http/
│   ├── request.vais       # HTTP 요청 타입
│   ├── response.vais      # HTTP 응답 타입
│   └── status.vais        # HTTP 상태 코드
├── database/
│   ├── connection.vais    # DB 연결
│   ├── query.vais         # 쿼리 빌더
│   └── migration.vais     # 마이그레이션
└── middleware/
    ├── auth.vais          # 인증
    └── logging.vais       # 로깅

주석 및 문서화

주석 스타일

# 한 줄 주석은 #를 사용합니다

# 여러 줄 주석은 여러 개의 #를 사용합니다
# 이것은 복잡한 로직을 설명합니다
# 각 줄마다 #를 붙입니다

F example() {
    # 코드 내 주석
    x := 42  # 인라인 주석은 간단하게
}

API 문서화

공개 함수는 주석으로 문서화합니다.

# Calculate the sum of two numbers
#
# Arguments:
#   a: first number
#   b: second number
#
# Returns:
#   sum of a and b
#
# Example:
#   result := add(2, 3)  # result = 5
F add(a: i64, b: i64) -> i64 = a + b

# Parse a JSON string
#
# Arguments:
#   text: JSON string to parse
#
# Returns:
#   Ok(JsonValue) if parsing succeeds
#   Err(ParseError) if parsing fails
#
# Note:
#   This function is strict about JSON format
F parse_json(text: str) -> Result<JsonValue> {
    # ...
}

설명이 필요한 주석

복잡한 로직에만 주석을 작성합니다.

# 좋은 예: 왜 이렇게 하는지 설명
F find_user_efficiently(id: i64) -> Option<User> {
    # Use binary search on sorted user list
    # Linear search would be O(n), but we have 100k users
    # so O(log n) is critical for performance
    # ...
}

# 나쁜 예: 명확한 코드는 주석 불필요
F add(a: i64, b: i64) -> i64 {
    # Add a to b
    a + b
}

포매팅 (Formatting)

들여쓰기

4개의 스페이스를 사용합니다. 탭은 사용하지 않습니다.

# 좋은 예
F outer() {
    I true {
        puts("inner")
    }
}

# 나쁜 예 (탭 사용)
F outer() {
→   I true {
→       puts("inner")
→   }
}

줄 길이

한 줄은 100자를 넘지 않도록 합니다.

# 좋은 예
result := calculate_complex_value(arg1, arg2, arg3)

# 나쁜 예 (한 줄이 너무 김)
result := calculate_complex_value(arg1, arg2, arg3) + another_function(x, y, z) * some_multiplier

# 개선
result := calculate_complex_value(arg1, arg2, arg3)
extra := another_function(x, y, z) * some_multiplier
result = result + extra

공백

함수 정의와 구현 사이에 빈 줄을 추가합니다.

# 좋은 예
F first_function() -> i64 {
    42
}

F second_function() -> str {
    "hello"
}

# 나쁜 예 (빈 줄 없음)
F first_function() -> i64 {
    42
}
F second_function() -> str {
    "hello"
}

연산자 주변 공백

연산자 주변에 공백을 추가합니다.

# 좋은 예
x := a + b
result := value * factor + offset
I count > 0 {
    # ...
}

# 나쁜 예
x := a+b
result := value*factor+offset
I count>0 {
    # ...
}

구조체와 함수 구조

구조체 레이아웃

필드는 논리적으로 그룹화합니다.

# 좋은 예
S User {
    # 식별 정보
    id: i64
    name: str
    email: str

    # 프로필 정보
    age: i64
    bio: str

    # 상태 정보
    is_active: bool
    created_at: i64
    updated_at: i64
}

# 나쁜 예 (순서 무작위)
S User {
    name: str
    created_at: i64
    id: i64
    email: str
    is_active: bool
    age: i64
    updated_at: i64
    bio: str
}

impl 블록 구조

impl 블록의 메서드를 논리적으로 정렬합니다.

X User {
    # 생성자
    F new(id: i64, name: str) -> User {
        User { id: id, name: name, email: "", age: 0, bio: "", is_active: true, created_at: 0, updated_at: 0 }
    }

    # 기본 접근자
    F get_id(&self) -> i64 = self.id
    F get_name(&self) -> str = self.name

    # 수정자
    F set_email(&self, email: str) {
        # ...
    }

    # 비즈니스 로직
    F is_valid(&self) -> bool {
        self.id > 0 && self.name.len() > 0
    }

    # 직렬화
    F to_string(&self) -> str {
        # ...
    }
}

에러 처리

에러 처리는 일관되게 합니다.

# 좋은 예: 명확한 에러 타입
E DatabaseError {
    ConnectionFailed,
    QueryFailed,
    TimeoutError
}

F query_user(id: i64) -> Result<User> {
    I id <= 0 {
        R Result.Err(DatabaseError.QueryFailed)
    }
    # ...
}

# 나쁜 예: 문자열로 에러 표현
F query_user_bad(id: i64) -> Result<User> {
    I id <= 0 {
        R Result.Err("bad id")
    }
    # ...
}

타입 시스템

명시적 타입 주석을 사용합니다.

# 좋은 예: 명확한 타입
F process_data(items: [i64; 10]) -> i64 {
    ~ sum: i64 = 0
    # ...
}

# 나쁜 예: 타입 생략
F process_data(items) {
    ~ sum := 0  # 타입 불명확
    # ...
}

성능 관련 스타일

불필요한 복사 피하기

# 좋은 예: 참조 사용
F process_large_data(data: &[i64; 1000]) {
    # data 사용
}

# 나쁜 예: 복사 발생
F process_large_data_bad(data: [i64; 1000]) {
    # 전체 배열이 복사됨
}

사전 할당 (Pre-allocation)

# 좋은 예: 사전 할당
F build_results() {
    ~ results: [i64; 1000]  # 미리 할당

    ~ i := 0
    L i < 1000 {
        results[i] = process(i)
        i = i + 1
    }
}

# 나쁜 예: 반복 할당
F build_results_bad() {
    ~ results: [i64]  # 동적 배열 (매번 재할당)
    # ...
}

테스트 작성

테스트 함수는 명확한 이름을 사용합니다.

# 테스트 함수는 test_로 시작
F test_add_positive_numbers() {
    result := add(2, 3)
    I result != 5 {
        puts("FAIL: test_add_positive_numbers")
        R
    }
    puts("PASS: test_add_positive_numbers")
}

F test_add_negative_numbers() {
    result := add(-2, -3)
    I result != -5 {
        puts("FAIL: test_add_negative_numbers")
        R
    }
    puts("PASS: test_add_negative_numbers")
}

F test_add_zero() {
    result := add(0, 0)
    I result != 0 {
        puts("FAIL: test_add_zero")
        R
    }
    puts("PASS: test_add_zero")
}

스타일 체크리스트

명명:
□ 함수: snake_case
□ 구조체: PascalCase
□ 변수: snake_case
□ 상수: SCREAMING_SNAKE_CASE
□ 파일: snake_case

포매팅:
□ 들여쓰기: 4 스페이스
□ 줄 길이: 100자 이내
□ 함수 사이: 빈 줄 추가
□ 연산자 주변: 공백 추가

문서화:
□ 공개 함수: 문서화 주석
□ 복잡한 로직: 설명 주석
□ 의미 없는 주석: 제거

구조:
□ 파일 크기: 500줄 이하
□ 논리적 그룹화
□ impl 메서드: 순서대로 정렬

테스트:
□ 중요한 함수: 테스트 작성
□ 테스트 이름: test_* 형식
□ 엣지 케이스: 테스트 포함

다음 단계