티스토리 뷰
공식 문서 : https://google.github.io/styleguide/go
Overview | Guide | Decisions | Best practices
참고: 이 문서는 Google의 Go Style 시리즈 문서의 일부입니다. 이 문서는 규범적이고 표준적입니다. 자세한 내용은 개요를 참조하세요.
스타일 원칙
읽기 쉬운 Go 코드를 작성하는 방법에 대한 몇 가지 기본 원칙이 있습니다. 다음은 코드 가독성의 핵심 속성들이며, 중요도 순으로 나열되었습니다:
- Clarity: 코드의 목적과 이유가 독자에게 명확하게 전달됩니다.
- Simplicity: 코드가 가능한 한 간단한 방식으로 목적을 달성합니다.
- Concision: 코드에서 핵심 내용이 불필요한 요소 없이 전달됩니다.
- Maintainability: 코드가 쉽게 유지보수될 수 있도록 작성됩니다.
- Consistency: 코드가 Google의 전체 코드베이스와 일관성을 유지합니다.
명확성 (Clarity)
가독성의 핵심 목표는 독자에게 명확한 코드를 작성하는 것입니다.
명확성은 효과적인 네이밍, 유용한 주석, 그리고 효율적인 코드 구조를 통해 달성됩니다. 명확성은 코드 작성자가 아닌 독자의 관점에서 바라봐야 합니다. 코드가 쉽게 작성되는 것보다 쉽게 읽히는 것이 더 중요합니다. 코드의 명확성은 두 가지 측면에서 고려할 수 있습니다:
코드가 실제로 무엇을 하고 있는가?
Go는 코드가 수행하는 동작을 쉽게 이해할 수 있도록 설계되었습니다. 만약 코드의 목적이 불분명하거나 이해하기 위해 사전 지식이 필요한 경우, 미래의 독자들을 위해 코드를 더 명확하게 만드는 데 시간을 투자하는 것이 좋습니다. 예를 들어, 다음과 같은 방법이 도움이 될 수 있습니다:
- 더 설명적인 변수명을 사용합니다.
- 추가적인 주석을 추가합니다.
- 코드에 공백과 주석을 넣어 가독성을 높입니다.
- 코드를 모듈화하기 위해 별도의 함수/메소드로 분리합니다.
여기에는 모든 상황에 적용되는 일관된 방법이 있는 것은 아니지만, Go 코드를 개발할 때 명확성을 우선시하는 것이 중요합니다.
왜 코드가 그러한 동작을 하는가?
코드의 이유는 변수, 함수, 메소드, 또는 패키지의 이름으로 충분히 전달되는 경우가 많습니다. 그렇지 않은 경우에는 주석을 추가하는 것이 중요합니다. "왜?"라는 질문은 특히 독자가 익숙하지 않을 수 있는 세부 사항이 있는 경우 중요합니다. 예를 들어:
- 언어의 특성, 예를 들어 클로저가 루프 변수에 대한 참조를 캡처하지만, 클로저가 코드의 여러 줄 뒤에 위치하는 경우
- 비즈니스 로직의 특성, 예를 들어 실제 사용자와 사용자 대리인을 구분해야 하는 접근 제어 확인
API는 올바르게 사용하기 위해 신중함이 필요할 수 있습니다. 예를 들어, 성능을 고려하여 코드가 복잡하게 구성되었거나 복잡한 수학적 연산이 예상치 못한 타입 변환을 사용하는 경우가 있습니다. 이러한 상황에서는 해당 내용이 명확히 설명되어 향후 유지보수자가 실수를 방지하고 독자가 코드를 이해하기 위해 역설계를 하지 않도록 하는 것이 중요합니다.
또한 추가 주석과 같은 명확성을 제공하려는 시도가 불필요한 내용을 추가하거나 코드와 모순되거나 유지보수의 부담을 초래하여 코드의 목적을 모호하게 만들 수 있음을 인지하는 것도 중요합니다. 중복된 주석을 추가하기보다는 기호 이름 자체를 설명적으로 만들어 코드 자체로 목적을 전달하도록 하는 것이 좋습니다. 주석은 코드가 무엇을 하는지를 설명하는 것보다는 왜 그러한 작업을 수행하는지 설명하는 것이 더 좋습니다.
Google의 코드베이스는 대체로 일관성과 통일성을 유지하고 있습니다. 따라서 눈에 띄는 코드(예: 낯선 패턴을 사용하는 코드)는 성능을 위한 중요한 이유가 있는 경우가 많습니다. 새로운 코드를 읽을 때 독자가 어느 부분에 주목해야 하는지를 명확히 하는 것이 중요합니다.
표준 라이브러리에는 이 원칙이 잘 적용된 예가 많이 포함되어 있습니다. 그중 몇 가지 예:
package sort
에 있는 유지보수 주석.- 같은 패키지에 포함된 좋은 실행 가능한 예제는 사용자(godoc에 표시됨)와 유지보수자(테스트로 실행됨) 모두에게 유용합니다.
strings.Cut
는 코드가 단 4줄이지만 호출 위치의 명확성과 정확성을 개선합니다.
단순성 (Simplicity)
Go 코드는 사용하는 사람, 읽는 사람, 유지보수하는 사람이 쉽게 이해할 수 있도록 단순해야 합니다.
Go 코드는 동작과 성능 측면에서 목표를 달성하는 가장 단순한 방식으로 작성되어야 합니다. Google의 Go 코드베이스에서 단순한 코드는 다음과 같은 특징을 가집니다:
- 위에서 아래로 읽기 쉽게 작성됩니다.
- 코드가 무엇을 하는지 미리 알고 있다고 가정하지 않습니다.
- 이전 코드의 모든 내용을 기억할 수 있다고 가정하지 않습니다.
- 불필요한 추상화 레벨이 없습니다.
- 특별할 것 없는 부분에 주의를 끄는 이름을 사용하지 않습니다.
- 값과 결정 사항의 흐름을 독자가 쉽게 이해할 수 있습니다.
- 코드가 무엇을 하는지가 아니라 왜 그렇게 하는지 설명하는 주석이 포함되어 있어 향후 변경을 방지합니다.
- 독립적으로 의미가 전달되는 문서화가 포함되어 있습니다.
- 유용한 오류 메시지와 테스트 실패 메시지를 제공합니다.
- “기발한” 코드와는 종종 배치됩니다.
코드의 단순성과 API 사용의 단순성 간에 균형이 필요할 수 있습니다. 예를 들어, API를 쉽게 호출할 수 있도록 코드가 다소 복잡해지는 것이 가치가 있을 수 있습니다. 반대로, 코드가 단순하고 이해하기 쉬운 상태를 유지하도록 하기 위해 사용자에게 약간의 추가 작업을 남기는 것이 좋을 수도 있습니다.
복잡성이 필요한 경우에는 의도적으로 복잡성을 추가해야 합니다. 이는 일반적으로 성능이 추가로 필요하거나 특정 라이브러리나 서비스를 다양한 사용자가 이용하는 경우 필요합니다. 복잡성이 정당화될 수 있지만, 그에 대한 문서가 동반되어야 하며, 사용자와 향후 유지보수자가 복잡성을 이해하고 탐색할 수 있도록 해야 합니다. 특히 코드에 “단순”한 사용법과 “복잡”한 사용법이 동시에 존재하는 경우에는 올바른 사용법을 시연하는 테스트와 예제도 함께 제공해야 합니다.
이 원칙은 복잡한 코드가 Go에서 작성될 수 없거나 작성되어서는 안 된다는 의미는 아닙니다. 우리는 불필요한 복잡성을 피하려고 노력하며, 복잡성이 나타나는 경우에는 해당 코드가 이해와 유지보수에 신중함이 필요함을 나타냅니다. 이상적으로는 복잡성을 설명하는 주석이 함께 제공되어, 왜 신중을 기울여야 하는지 알려주어야 합니다. 이는 주로 성능 최적화 작업에서 발생하며, 버퍼를 미리 할당하고 goroutine 생애 동안 재사용하는 것과 같은 더 복잡한 접근 방식이 요구됩니다. 유지보수자가 이러한 복잡성을 보면, 해당 코드가 성능에 민감하다는 신호로 받아들여야 하며, 이후 변경 시에 주의를 기울여야 합니다. 반대로, 복잡성이 불필요하게 사용된 경우, 향후 코드 변경 시 읽거나 수정하는 사람에게 부담을 줍니다.
코드의 목적이 단순해야 하는데 매우 복잡해진 경우, 종종 구현을 다시 살펴보아 더 단순한 방법이 있는지 검토하는 신호로 볼 수 있습니다.
최소 메커니즘 (Least mechanism)
동일한 아이디어를 표현할 수 있는 여러 방법이 있을 때, 가장 표준적인 도구를 사용하는 방법을 선호하세요. 정교한 도구가 존재할 수 있지만, 이유 없이 사용해서는 안 됩니다. 필요할 때 복잡성을 코드에 추가하는 것은 쉽지만, 불필요해진 후의 복잡성을 제거하는 것은 훨씬 어렵습니다.
- 사용 사례에 충분한 경우 핵심 언어 구성 요소(예: 채널, 슬라이스, 맵, 루프 또는 구조체)를 사용하도록 목표를 설정하세요.
- 적합한 언어 구성 요소가 없다면, 표준 라이브러리에서 도구를 찾으세요(예: HTTP 클라이언트나 템플릿 엔진).
- 마지막으로, 새 종속성을 추가하거나 직접 작성하기 전에 Google 코드베이스의 핵심 라이브러리가 충분한지 고려하세요.
예를 들어, 기본값이 설정된 변수를 플래그로 사용해야 하는 프로덕션 코드가 있으며, 테스트에서는 이 값을 재정의해야 하는 경우가 있다고 가정해봅니다. 프로그램의 명령줄 인터페이스를 테스트하려는 목적이 아닌 경우(예: os/exec
를 사용하는 경우), flag.Set
을 사용하기보다 바인딩된 값을 직접 재정의하는 것이 더 단순하고 선호됩니다.
마찬가지로, 특정 코드가 집합 멤버십 확인을 필요로 하는 경우, 부울 값 맵(e.g., map[string]bool
)이 종종 충분합니다. 집합 유형과 기능을 제공하는 라이브러리는 맵으로 처리할 수 없거나 너무 복잡한 작업이 필요한 경우에만 사용하세요.
간결성 (Concision)
간결한 Go 코드는 신호 대 잡음 비율이 높습니다. 즉, 관련된 세부 사항을 쉽게 파악할 수 있으며, 네이밍과 구조가 독자가 이러한 세부 사항을 따라갈 수 있도록 안내합니다.
다음 요소들이 중요한 세부 사항을 가리는 경우가 많습니다:
- 반복적인 코드
- 불필요한 구문
- 알아보기 어려운 이름
- 불필요한 추상화
- 공백
반복적인 코드는 거의 동일한 여러 코드 부분에서 차이점을 흐리게 만들어 독자가 시각적으로 유사한 코드 라인들을 비교하게 합니다. 테이블 기반 테스트는 반복되는 코드에서 중요한 세부 사항을 간결하게 분리해낼 수 있는 좋은 예입니다. 그러나 어떤 요소를 테이블에 포함할지에 따라 테이블을 이해하는 난이도가 달라질 수 있습니다.
코드 구조를 여러 가지로 고려할 때, 중요한 세부 사항을 가장 명확하게 드러내는 방식을 선택하는 것이 좋습니다.
일반적인 코드 구조와 관용구를 이해하고 사용하는 것도 높은 신호 대 잡음 비율을 유지하는 데 중요합니다. 예를 들어, 다음과 같은 코드 블록은 오류 처리에서 매우 흔히 사용되며, 독자는 이 코드 블록의 목적을 빠르게 파악할 수 있습니다.
// 좋은 예:
if err := doSomething(); err != nil {
// ...
}
이와 유사하지만 미묘하게 다른 코드는 독자가 변화된 점을 알아차리지 못할 수 있습니다. 이와 같은 경우, 오류 검사를 강조하기 위해 주석을 추가하는 등 "신호 강화"를 고려해 볼 만합니다.
// 좋은 예:
if err := doSomething(); err == nil { // 오류가 없는 경우
// ...
}
유지보수성 (Maintainability)
코드는 작성되는 것보다 수정되는 경우가 훨씬 많습니다. 읽기 쉬운 코드는 작동 방식을 이해하려는 독자뿐만 아니라 코드를 변경해야 하는 프로그래머에게도 의미가 있어야 합니다. 명확성이 핵심입니다.
유지보수 가능한 코드는 다음과 같은 특징을 가집니다:
- 미래의 프로그래머가 쉽게 올바르게 수정할 수 있습니다.
- API가 구조화되어 있어 원활하게 확장될 수 있습니다.
- 가정한 바를 명확히 하고, 문제의 구조에 맞는 추상화를 사용하여 코드 구조와 매핑되지 않습니다.
- 불필요한 결합을 피하고 사용하지 않는 기능을 포함하지 않습니다.
- 예상한 동작이 유지되고 중요한 로직이 올바른지 확인할 수 있는 종합적인 테스트 스위트를 갖추고 있으며, 실패 시 명확하고 실행 가능한 진단 정보를 제공합니다.
인터페이스와 타입과 같은 추상화를 사용할 때는, 이러한 추상화가 문맥에서 정보를 제거하기 때문에 충분한 이점을 제공하는지 확인하는 것이 중요합니다. IDE나 편집기는 구체적인 타입이 사용될 때 메서드 정의에 직접 연결하고 관련 문서를 보여줄 수 있지만, 인터페이스를 사용할 경우 인터페이스 정의로만 참조할 수 있습니다. 인터페이스는 강력한 도구이지만, 유지보수자가 인터페이스를 올바르게 사용하기 위해 기본 구현의 세부 사항을 이해해야 할 수 있으므로 인터페이스 문서나 호출 위치에서 설명이 필요합니다.
유지보수 가능한 코드는 또한 중요한 세부 사항을 쉽게 간과할 수 있는 위치에 숨기지 않습니다. 예를 들어, 다음과 같은 코드 줄에서는 단일 문자 하나가 매우 중요한 의미를 가집니다:
// 나쁜 예:
// := 대신 = 사용으로 인해 라인의 의미가 완전히 달라질 수 있습니다.
if user, err = db.UserByID(userID); err != nil {
// ...
}
// 나쁜 예:
// 중간의 ! 문자는 쉽게 놓칠 수 있습니다.
leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))
이들 코드가 잘못된 것은 아니지만, 보다 명시적인 방식으로 작성하거나 중요한 동작을 강조하는 주석을 추가할 수 있습니다:
// 좋은 예:
u, err := db.UserByID(userID)
if err != nil {
return fmt.Errorf("잘못된 사용자 ID: %s", err)
}
user = u
// 좋은 예:
// 그레고리력의 윤년은 단순히 year%4 == 0으로 판별되지 않습니다.
// 참조: https://en.wikipedia.org/wiki/Leap_year#Algorithm.
var (
leap4 = year%4 == 0
leap100 = year%100 == 0
leap400 = year%400 == 0
)
leap := leap4 && (!leap100 || leap400)
또한, 중요한 로직이나 예외 케이스를 숨기는 헬퍼 함수는 향후 변경 시 이를 고려하지 못하게 할 수 있습니다.
예측 가능한 네이밍도 유지보수 가능한 코드의 중요한 요소입니다. 패키지 사용자나 코드 유지보수자는 특정 컨텍스트에서 변수, 메서드 또는 함수의 이름을 예측할 수 있어야 합니다. 동일한 개념에 대해 함수 파라미터와 리시버 이름은 일반적으로 같은 이름을 공유해야 합니다. 이는 문서화를 이해하기 쉽게 하고 코드 리팩토링 시 최소한의 작업으로 수행할 수 있게 합니다.
유지보수 가능한 코드는 종속성을 최소화합니다(암시적이든 명시적이든). 적은 수의 패키지에 의존하면 영향을 미칠 수 있는 코드 줄이 줄어듭니다. 내부적이거나 문서화되지 않은 동작에 대한 종속성을 피하면, 미래에 해당 동작이 변경될 때 유지보수 부담을 줄일 수 있습니다.
코드를 구조화하거나 작성할 때는 시간이 지남에 따라 코드가 진화할 수 있는 방식을 고려할 가치가 있습니다. 더 쉽고 안전하게 변경할 수 있는 방법이 있다면, 약간의 복잡성을 더하더라도 좋은 선택일 가능성이 큽니다.
일관성 (Consistency)
일관성 있는 코드는 전체 코드베이스, 팀 또는 패키지 내에서, 그리고 단일 파일 내에서 유사한 코드처럼 보이고 느껴지며 작동합니다.
일관성은 앞서 언급한 원칙을 우선시하지는 않지만, 선택의 여지가 없는 경우에는 일관성을 유지하는 방향으로 결정하는 것이 유익할 수 있습니다.
패키지 내에서의 일관성은 특히 중요합니다. 동일한 문제가 패키지 전체에서 여러 가지 방식으로 접근되거나, 동일한 개념에 여러 이름이 부여된 경우 이는 혼란을 줄 수 있습니다. 그러나 이러한 일관성조차도 문서화된 스타일 원칙이나 전역적인 일관성을 무시해서는 안 됩니다.
핵심 지침
이 지침들은 모든 Go 코드가 준수해야 할 가장 중요한 Go 스타일의 측면을 모아놓은 것입니다. 이 원칙들은 가독성을 인정받을 때까지 학습되고 따라야 하며, 빈번하게 변경될 것으로 예상되지 않습니다. 새로운 추가 사항이 포함되려면 높은 기준을 통과해야 합니다.
아래의 지침들은 전체 Go 커뮤니티에서 공통 기준을 제공하는 Effective Go의 권장 사항을 확장한 것입니다.
포맷팅 (Formatting)
모든 Go 소스 파일은 gofmt
도구의 출력 형식을 따라야 합니다. 이 형식은 Google 코드베이스의 사전 제출 검사에 의해 강제됩니다. 생성된 코드 역시 가능한 경우 포맷팅되어야 합니다(예: format.Source
). 생성된 코드도 Code Search에서 쉽게 검색할 수 있기 때문입니다.
MixedCaps
Go 소스 코드에서는 여러 단어로 이루어진 이름을 작성할 때 언더스코어(snake_case) 대신 MixedCaps
또는 mixedCaps
(camel case)를 사용합니다.
이는 다른 언어의 관례를 따르지 않는 경우에도 적용됩니다. 예를 들어, 상수는 내보내는 경우 MaxLength
(예: MAX_LENGTH
가 아님), 내보내지 않는 경우 maxLength
(예: max_length
가 아님)로 표기됩니다.
로컬 변수는 대문자 사용 여부를 선택할 때 내보내지 않는 요소로 간주됩니다.
줄 길이 (Line length)
Go 소스 코드에는 고정된 줄 길이가 없습니다. 줄이 너무 길다고 느껴지면 분할하기보다 리팩토링하는 것이 좋습니다. 줄 길이가 이미 가능한 짧게 작성되었다면, 그대로 길게 유지해도 괜찮습니다.
다음과 같은 경우 줄을 나누지 마세요:
- 들여쓰기 변경(예: 함수 선언, 조건문) 전에
- 긴 문자열(예: URL)을 여러 줄에 나누기 위해
네이밍 (Naming)
네이밍은 과학이라기보다는 예술에 가깝습니다. Go에서는 이름이 다른 언어에 비해 다소 짧은 경향이 있지만, 동일한 일반 지침이 적용됩니다. 이름은 다음과 같아야 합니다:
- 사용 시 반복적으로 느껴지지 않아야 합니다.
- 컨텍스트를 고려해야 합니다.
- 이미 명확한 개념을 반복하지 않아야 합니다.
더 구체적인 네이밍 지침은 decisions에서 확인할 수 있습니다.
로컬 일관성 (Local consistency)
스타일 가이드가 특정 스타일 포인트에 대해 언급하지 않는 경우, 작성자는 선호하는 스타일을 자유롭게 선택할 수 있습니다. 단, 가까운 위치의 코드(일반적으로 동일한 파일이나 패키지 내, 때로는 팀 또는 프로젝트 디렉터리 내)가 일관된 방식을 취했다면 그 방식을 따르는 것이 좋습니다.
유효한 로컬 스타일 고려사항의 예:
- 오류 출력 포맷에서
%s
또는%v
사용 - 뮤텍스 대신 버퍼드 채널 사용
유효하지 않은 로컬 스타일 고려사항의 예:
- 코드에 대한 줄 길이 제한
- assertion 기반 테스트 라이브러리 사용
로컬 스타일이 스타일 가이드와 상충되지만 가독성에 대한 영향이 파일 하나로 제한된다면, 코드 리뷰에서 지적될 수 있으며, 이 경우 일관된 수정이 CL 범위를 벗어납니다. 이런 경우 수정 사항을 추적할 버그를 등록하는 것이 적절합니다.
변경이 기존 스타일의 일탈을 악화시키거나 더 많은 API 표면에 노출시키거나 일탈이 발생한 파일 수를 늘리거나 실제 버그를 도입하는 경우, 로컬 일관성은 새로운 코드에서 스타일 가이드를 위반하는 유효한 근거가 될 수 없습니다. 이러한 경우 작성자는 동일한 CL에서 기존 코드베이스를 정리하거나 현재 CL 이전에 리팩토링을 수행하거나 적어도 로컬 문제를 악화시키지 않는 대안을 찾아야 합니다.
'Golang > 기초' 카테고리의 다른 글
[한글] Google 공식 Go 스타일 문서 - 모범 사례 (2) | 2024.11.13 |
---|---|
[한글] Google 공식 Go 스타일 문서 - 결정 사항 (3) | 2024.11.13 |
[한글] Google 공식 Go 스타일 문서 - 개요 (0) | 2024.11.12 |
- Total
- Today
- Yesterday
- 외부모델연동
- python
- openai
- 에이전트 관리
- ai 오케스트레이션
- STYLE
- 컨텍스트 변수
- 스타일 가이드
- go
- Document
- Golang
- 에이전트 오케스트레이션
- structured outputs
- 다중 에이전트 시스템
- swarm
- gostyle
- golang 네이밍
- go style
- 도구 호출
- style guide
- 멀티에이전트
- golang 규칙
- golang 스타일
- golang style
- json schema
- ai 자동화
- golang 모범사례
- golang 네이밍 규칙
- function calling
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |