스타일 원칙
가독성이 좋은 Go 코드를 작성하는 방법을 요약한 몇 가지 원칙이 있습니다. 다음은 가독성이 좋은 코드의 특성입니다. 중요도순으로 나열합니다.
- 명확성: 독자의 입장에서코드의 목적과 이유가 명확합니다.
- 간결성: 코드는 가능한 간단한 방식으로 목표를 달성합니다.
- 간결성: 코드는 높은 신호 대 잡음 비율을 가집니다.
- 유지 보수성: 코드는 쉽게 유지 보수할 수 있도록 작성됩니다.
- 일관성: 코드는 Google 코드베이스 전체와 일관되게 작성됩니다.
명확성
가독성의 핵심 목표는 독자에게 명확한 코드를 제공하는 것입니다.
명확성은 주로 효과적인 이름 지정, 도움이 되는 주석 및 효율적인 코드 구성으로 달성됩니다.
명확성은 코드 작성자가 아닌 독자의 시각으로 볼 필요가 있습니다. 코드가 쉽게 읽히는 것이 쓰기 쉬운 것보다 더 중요합니다. 코드의 명확성에는 두 가지 다른 측면이 있습니다.
코드가 실제로 무엇을 하는가?
Go는 코드가 무엇을 하는지 비교적 쉽게 볼 수 있도록 설계되었습니다. 불확실성이나 독자가 코드를 이해하기 위해 사전 지식이 필요한 경우, 미래 독자를 위해 코드의 목적을 더욱 명확하게 만드는 데 시간을 투자하는 것이 가치가 있습니다. 예를 들어 다음과 같은 것이 도움이 될 수 있습니다:
- 더 구체적인 변수 이름 사용
- 추가 주석 추가
- 공백 및 주석으로 코드 분할
- 코드를 별도의 함수/메소드로 리팩터링하여 더 모듈화
여기에는 모든 상황에 적용되는 접근법이 없지만, Go 코드를 개발할 때 명료성을 우선시하는 것이 중요합니다.
코드가 왜 그렇게 하는가?
변수, 함수, 방법 또는 패키지의 이름으로 코드의 근거가 충분히 전달될 수 있습니다. 그렇지 않은 경우 주석을 추가하는 것이 중요합니다. “왜?”는 특히 코드에 독자가 익숙하지 않은 뉘앙스가 포함되어 있을 때 중요합니다.
- 언어의 뉘앙스, 예를 들어 클로저가 루프 변수를 캡처하지만 클로저는 멀리 떨어져 있습니다.
- 비즈니스 로직의 뉘앙스, 예를 들어 실제 사용자와 사용자를 가장하고 있는 사람을 구분해야 하는 액세스 제어 확인
API는 올바르게 사용하기 위해 주의가 필요할 수 있습니다. 예를 들어, 코드 일부가 성능 문제 때문에 복잡하고 이해하기 어려울 수 있으며, 복잡한 수학 연산의 순서가 예상치 못한 방식으로 타입 변환을 사용할 수 있습니다. 이런 경우와 다른 많은 경우에는 주석과 문서가 이러한 측면을 설명해주는 것이 중요합니다. 그래야 나중에 유지보수를 하는 사람들이 실수하지 않고, 독자들이 코드를 역공학하지 않고도 이해할 수 있습니다.
또한 명료성을 제공하려는 일부 시도(예: 추가적인 주석)가 실제로는 코드의 목적을 가리거나(예: 잡음을 추가하거나), 코드가 이미 말하는 것을 반복하거나, 코드와 모순되거나, 주석을 최신 상태로 유지하기 위한 유지보수 부담을 늘릴 수 있다는 것을 인식하는 것이 중요합니다. 불필요한 주석을 추가하기보다는 코드 자체가 말하게 하세요(예: 심볼 이름 자체가 스스로 설명되도록 만드세요). 주석은 코드가 무엇을 하는지가 아니라 왜 그렇게 하는지를 설명하는 것이 종종 더 좋습니다.
Google 코드베이스는 대부분 균일하고 일관성이 있습니다. 코드가 눈에 띄게 높은 패턴을 사용하는 경우(예: 성능을 위해), 일반적으로 유지하는 것이 중요합니다. 읽기 새로운 코드를 읽을 때 독자가 주의를 기울여야 할 위치를 명확하게 만들기 위해.
표준 라이브러리에는 이 원칙을 적용하는 많은 예가 있습니다. 그 중 일부는 다음과 같습니다.
- 패키지 sort의 Maintainer 주석.
- 동일한 패키지의 실행 가능한 예제, 사용자(they show up in godoc)와 유지 보수자(they run as part of tests) 모두에게 이점이 있습니다.
- strings.Cut는 단지 네 줄 코드이지만, 호출부의 명확성과 정확성을 향상시킵니다.
간결성
Go 코드는 사용자, 읽는 사람, 유지 보수하는 사람 모두에게 간단해야 합니다.
Go 코드는 동작 및 성능 측면에서 목표를 달성하는 가장 간단한 방법으로 작성되어야 합니다. Google Go 코드베이스 내에서 간단한 코드:
- 위에서부터 읽기 쉽습니다.
- 이미 무엇을 하는지 알고 있다는 가정을 하지 않습니다.
- 이전 코드를 모두 기억할 수 있다는 가정을 하지 않습니다.
- 불필요한 추상화 수준이 없습니다.
- 평범한 것에 주목하게 하는 이름이 없습니다
- 값 및 결정의 전파를 분명하게 읽는 사람에게 보여줍니다.
- 주석이 무엇을 하는지가 아니라 왜 그렇게 하는지를 설명하여 향후 편차를 방지합니다
- 스스로 설명할 수 있는 문서가 있습니다
- 유용한 오류 및 유용한 테스트 실패가 있습니다.
- 종종 “영리한” 코드와 상호 배타적일 수 있습니다.
코드 간결성과 API 사용 간편성 간에 트레이드오프가 발생할 수 있습니다. 예를 들어, API의 최종 사용자가 API를 올바르게 호출할 수 있도록 코드를 더 복잡하게 만드는 것이 가치가 있을 수 있습니다. 반대로, API의 최종 사용자에게 조금 더 추가적인 작업을 남겨두어 코드가 단순하고 이해하기 쉽도록 유지하는 것이 가치가 있을 수 있습니다.
코드에 복잡성이 필요한 경우, 복잡성은 의도적으로 추가되어야 합니다. 이는 일반적으로 추가적인 성능이 필요하거나 특정 라이브러리나 서비스의 고객이 여러 개인 경우에 필요합니다. 복잡성은 정당화될 수 있지만, 클라이언트와 향후 유지보수자들이 복잡성을 이해하고 탐색할 수 있도록 문서를 함께 제공해야 합니다. 이는 올바른 사용법을 보여주는 테스트와 예제로 보완되어야 합니다. 특히 코드를 사용하는 “단순한” 방법과 “복잡한” 방법이 모두 있는 경우에 그렇습니다
이 원칙은 복잡한 코드를 Go로 작성할 수 없거나 하지 말아야 한다는 것을 의미하지 않으며, Go 코드가 복잡해져서는 안된다는 것을 의미하지도 않습니다. 우리는 불필요한 복잡성을 피하는 코드베이스를 지향합니다. 그래서 복잡성이 나타날 때는 해당 코드가 이해하고 유지보수하기 위해 주의가 필요하다는 것을 나타내기 때문입니다. 이상적으로, 논리를 설명하고 주의해야 할 점을 알려주는 주석이 함께 있어야 합니다. 이것은 종종 성능을 최적화하기 위해 코드를 작성할 때 발생합니다. 그렇게 하려면 더 복잡한 접근법이 필요할 수 있습니다. 예를 들어, 버퍼를 미리 할당하고 고루틴의 수명 동안 재사용하는 것과 같습니다. 유지보수자가 이것을 볼 때, 이것은 해당 코드가 성능에 중요하다는 신호이며, 향후 변경을 할 때 주의를 기울여야 한다는 것을 의미합니다. 반면에, 불필요하게 사용된다면, 이 복잡성은 향후에 코드를 읽거나 변경해야 하는 사람들에게 부담이 됩니다.
코드가 목적이 단순할 때 매우 복잡하게 작성되었다면, 이는 동일한 작업을 수행하는 더 간단한 방법이 있는지 확인하기 위해 구현을 재검토해야 하는 신호입니다.
최소 메커니즘
같은 아이디어를 나타내는 여러 가지 방법이 있는 경우, 가장 표준적인 도구를 사용하는 것이 좋습니다. 복잡한 기계는 종종 존재하지만 이유없이 사용해서는 안 됩니다. 필요한 경우에 코드에 복잡성을 추가하기 쉽지만, 불필요한 복잡성을 제거하기는 훨씬 어렵습니다.
- 사용 사례에 충분한 핵심 언어 구조(예: 채널, 슬라이스, 맵, 루프 또는 구조체)를 사용하도록 노력하십시오.
- 그렇지 않은 경우 표준 라이브러리 내에서 도구를 찾으십시오(예: HTTP 클라이언트 또는 템플릿 엔진).
- 마지막으로, 새로운 종속성을 도입하거나 자체적으로 만들기 전에 Google 코드베이스의 핵심 라이브러리가 충분한지 검토하십시오.
예를 들어, 기본 값이 바인딩 된 플래그를 포함하는 프로덕션 코드가 있으며, 테스트에서 재정의해야하는 경우, 프로그램의 커맨드라인 인터페이스 자체를 테스트할 의도가 없다면(os/exec와 같은), flag.Set을 사용하여 오버라이드하기보다는 바인딩된 값을 직접 오버라이드하는 것이 더 간단하고 따라서 선호됩니다.
마찬가지로, 코드가 set membership check를 필요로 하는 경우 불린 값 맵(예: map[string]bool
)이 충분합니다. 집합과 같은 유형 및 기능을 제공하는 라이브러리는 맵으로 불가능하거나 너무 복잡한 작업이 필요한 경우에만 사용해야 합니다.