구글 Go 스타일 가이드

1 개요[ | ]

Google Go Style Guide
구글 Go 스타일 가이드

2 스타일 원칙[ | ]

읽기 쉬운 Go 코드 작성에 대해 생각하는 방법을 요약하는 몇 가지 중요한 원칙이 있다. 다음은 읽기 쉬운 코드의 속성을 중요도 순으로 나열한 것이다.

  1. 명확함(clarity): 코드의 목적과 근거가 독자에게 명확하다.
  2. 단순함: 코드는 가능한 가장 간단한 방법으로 목표를 달성한다.
  3. 간결함: 코드의 신호 대 잡음비가 높다.
  4. 유지보수성: 코드는 쉽게 유지 관리할 수 있도록 작성한다.
  5. 일관성: 코드는 더 광범위한 Google 코드베이스와 일치한다.

2.1 명확함[ | ]

가독성의 핵심 목표는 독자에게 명확한 코드를 생성하는 것이다.

명확성은 주로 효과적인 네이밍, 유용한 주석, 효율적인 코드 구성을 통해 달성된다.

명확성은 코드 작성자가 아닌 독자의 렌즈를 통해 볼 수 있다. 코드는 쓰기 쉬운가보다 읽기 쉬운가이 더 중요하다. 코드의 명확성은 다음 두 가지 측면으로 볼 수 있다.

코드는 실제로 무엇을 하고 있는가? 코드가 그것을 하는 이유는 무엇인가?

2.1.1 코드는 실제로 무엇을 하고 있는가?[ | ]

Go is designed such that it should be relatively straightforward to see what the code is doing. In cases of uncertainty or where a reader may require prior knowledge in order to understand the code, it is worth investing time in order to make the code’s purpose clearer for future readers. For example, it may help to:

Use more descriptive variable names Add additional commentary Break up the code with whitespace and comments Refactor the code into separate functions/methods to make it more modular There is no one-size-fits-all approach here, but it is important to prioritize clarity when developing Go code.


2.1.2 코드가 그것을 하는 이유는 무엇인가?[ | ]

The code’s rationale is often sufficiently communicated by the names of variables, functions, methods, or packages. Where it is not, it is important to add commentary. The “Why?” is especially important when the code contains nuances that a reader may not be familiar with, such as:

A nuance in the language, e.g., a closure will be capturing a loop variable, but the closure is many lines away A nuance of the business logic, e.g., an access control check that needs to distinguish between the actual user and someone impersonating a user An API might require care to use correctly. For example, a piece of code may be intricate and difficult to follow for performance reasons, or a complex sequence of mathematical operations may use type conversions in an unexpected way. In these cases and many more, it is important that accompanying commentary and documentation explain these aspects so that future maintainers don’t make a mistake and so that readers can understand the code without needing to reverse-engineer it.

It is also important to be aware that some attempts to provide clarity (such as adding extra commentary) can actually obscure the code’s purpose by adding clutter, restating what the code already says, contradicting the code, or adding maintenance burden to keep the comments up-to-date. Allow the code to speak for itself (e.g., by making the symbol names themselves self-describing) rather than adding redundant comments. It is often better for comments to explain why something is done, not what the code is doing.

The Google codebase is largely uniform and consistent. It is often the case that code that stands out (e.g., by using an unfamiliar pattern) is doing so for a good reason, typically for performance. Maintaining this property is important to make it clear to readers where they should focus their attention when reading a new piece of code.

The standard library contains many examples of this principle in action. Among them:

Maintainer comments in package sort. Good runnable examples in the same package, which benefit both users (they show up in godoc) and maintainers (they run as part of tests). strings.Cut is only four lines of code, but they improve the clarity and correctness of callsites.

2.2 단순함[ | ]

Your Go code should be simple for those using, reading, and maintaining it.

Go code should be written in the simplest way that accomplishes its goals, both in terms of behavior and performance. Within the Google Go codebase, simple code:

Is easy to read from top to bottom Does not assume that you already know what it is doing Does not assume that you can memorize all of the preceding code Does not have unnecessary levels of abstraction Does not have names that call attention to something mundane Makes the propagation of values and decisions clear to the reader Has comments that explain why, not what, the code is doing to avoid future deviation Has documentation that stands on its own Has useful errors and useful test failures May often be mutually exclusive with “clever” code Tradeoffs can arise between code simplicity and API usage simplicity. For example, it may be worthwhile to have the code be more complex so that the end user of the API may more easily call the API correctly. In contrast, it may also be worthwhile to leave a bit of extra work to the end user of the API so that the code remains simple and easy to understand.

When code needs complexity, the complexity should be added deliberately. This is typically necessary if additional performance is required or where there are multiple disparate customers of a particular library or service. Complexity may be justified, but it should come with accompanying documentation so that clients and future maintainers are able to understand and navigate the complexity. This should be supplemented with tests and examples that demonstrate its correct usage, especially if there is both a “simple” and a “complex” way to use the code.

This principle does not imply that complex code cannot or should not be written in Go or that Go code is not allowed to be complex. We strive for a codebase that avoids unnecessary complexity so that when complexity does appear, it indicates that the code in question requires care to understand and maintain. Ideally, there should be accompanying commentary that explains the rationale and identifies the care that should be taken. This often arises when optimizing code for performance; doing so often requires a more complex approach, like preallocating a buffer and reusing it throughout a goroutine lifetime. When a maintainer sees this, it should be a clue that the code in question is performance-critical, and that should influence the care that is taken when making future changes. If employed unnecessarily, on the other hand, this complexity is a burden on those who need to read or change the code in the future.

If code turns out to be very complex when its purpose should be simple, this is often a signal to revisit the implementation to see if there is a simpler way to accomplish the same thing.


2.2.1 Least mechanism[ | ]

Where there are several ways to express the same idea, prefer the one that uses the most standard tools. Sophisticated machinery often exists, but should not be employed without reason. It is easy to add complexity to code as needed, whereas it is much harder to remove existing complexity after it has been found to be unnecessary.

Aim to use a core language construct (for example a channel, slice, map, loop, or struct) when sufficient for your use case. If there isn’t one, look for a tool within the standard library (like an HTTP client or a template engine). Finally, consider whether there is a core library in the Google codebase that is sufficient before introducing a new dependency or creating your own. As an example, consider production code that contains a flag bound to a variable with a default value which must be overridden in tests. Unless intending to test the program’s command-line interface itself (say, with os/exec), it is simpler and therefore preferable to override the bound value directly rather than by using flag.Set.

Similarly, if a piece of code requires a set membership check, a boolean-valued map (e.g., map[string]bool) often suffices. Libraries that provide set-like types and functionality should only be used if more complicated operations are required that are impossible or overly complicated with a map.

2.3 Concision[ | ]

Concise Go code has a high signal-to-noise ratio. It is easy to discern the relevant details, and the naming and structure guide the reader through these details.

There are many things that can get in the way of surfacing the most salient details at any given time:

Repetitive code Extraneous syntax Opaque names Unnecessary abstraction Whitespace Repetitive code especially obscures the differences between each nearly-identical section, and requires a reader to visually compare similar lines of code to find the changes. Table-driven testing is a good example of a mechanism that can concisely factor out the common code from the important details of each repetition, but the choice of which pieces to include in the table will have an impact on how easy the table is to understand.

When considering multiple ways to structure code, it is worth considering which way makes important details the most apparent.

Understanding and using common code constructions and idioms are also important for maintaining a high signal-to-noise ratio. For example, the following code block is very common in error handling, and the reader can quickly understand the purpose of this block.

// Good: if err := doSomething(); err != nil {

   // ...

} If code looks very similar to this but is subtly different, a reader may not notice the change. In cases like this, it is worth intentionally “boosting” the signal of the error check by adding a comment to call attention to it.

// Good: if err := doSomething(); err == nil { // if NO error

   // ...

}

2.4 Maintainability[ | ]

Code is edited many more times than it is written. Readable code not only makes sense to a reader who is trying to understand how it works, but also to the programmer who needs to change it. Clarity is key.

Maintainable code:

Is easy for a future programmer to modify correctly Has APIs that are structured so that they can grow gracefully Is clear about the assumptions that it makes and chooses abstractions that map to the structure of the problem, not to the structure of the code Avoids unnecessary coupling and doesn’t include features that are not used Has a comprehensive test suite to ensure promised behaviors are maintained and important logic is correct, and the tests provide clear, actionable diagnostics in case of failure When using abstractions like interfaces and types which by definition remove information from the context in which they are used, it is important to ensure that they provide sufficient benefit. Editors and IDEs can connect directly to a method definition and show the corresponding documentation when a concrete type is used, but can only refer to an interface definition otherwise. Interfaces are a powerful tool, but come with a cost, since the maintainer may need to understand the specifics of the underlying implementation in order to correctly use the interface, which must be explained within the interface documentation or at the call-site.

Maintainable code also avoids hiding important details in places that are easy to overlook. For example, in each of the following lines of code, the presence or lack of a single character is critical to understand the line:

// Bad: // The use of = instead of := can change this line completely. if user, err = db.UserByID(userID); err != nil {

   // ...

} // Bad: // The ! in the middle of this line is very easy to miss. leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0)) Neither of these are incorrect, but both could be written in a more explicit fashion, or could have an accompanying comment that calls attention to the important behavior:

// Good: u, err := db.UserByID(userID) if err != nil {

   return fmt.Errorf("invalid origin user: %s", err)

} user = u // Good: // Gregorian leap years aren't just year%4 == 0. // See 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) In the same way, a helper function that hides critical logic or an important edge-case could make it easy for a future change to fail to account for it properly.

Predictable names are another feature of maintainable code. A user of a package or a maintainer of a piece of code should be able to predict the name of a variable, method, or function in a given context. Function parameters and receiver names for identical concepts should typically share the same name, both to keep documentation understandable and to facilitate refactoring code with minimal overhead.

Maintainable code minimizes its dependencies (both implicit and explicit). Depending on fewer packages means fewer lines of code that can affect behavior. Avoiding dependencies on internal or undocumented behavior makes code less likely to impose a maintenance burden when those behaviors change in the future.

When considering how to structure or write code, it is worth taking the time to think through ways in which the code may evolve over time. If a given approach is more conducive to easier and safer future changes, that is often a good trade-off, even if it means a slightly more complicated design.

2.5 Consistency[ | ]

Consistent code is code that looks, feels, and behaves like similar code throughout the broader codebase, within the context of a team or package, and even within a single file.

Consistency concerns do not override any of the principles above, but if a tie must be broken, it is often beneficial to break it in favor of consistency.

Consistency within a package is often the most immediately important level of consistency. It can be very jarring if the same problem is approached in multiple ways throughout a package, or if the same concept has many names within a file. However, even this should not override documented style principles or global consistency.

3 코어 가이드라인[ | ]

These guidelines collect the most important aspects of Go style that all Go code is expected to follow. We expect that these principles be learned and followed by the time readability is granted. These are not expected to change frequently, and new additions will have to clear a high bar.

The guidelines below expand on the recommendations in Effective Go, which provide a common baseline for Go code across the entire community.

3.1 포맷팅[ | ]

Formatting All Go source files must conform to the format outputted by the gofmt tool. This format is enforced by a presubmit check in the Google codebase. Generated code should generally also be formatted (e.g., by using format.Source), as it is also browsable in Code Search.


3.2 MixedCaps[ | ]

Go source code uses MixedCaps or mixedCaps (camel case) rather than underscores (snake case) when writing multi-word names.

This applies even when it breaks conventions in other languages. For example, a constant is MaxLength (not MAX_LENGTH) if exported and maxLength (not max_length) if unexported.

Local variables are considered unexported for the purpose of choosing the initial capitalization.

3.3 행 길이[ | ]

There is no fixed line length for Go source code. If a line feels too long, it should be refactored instead of broken. If it is already as short as it is practical for it to be, the line should be allowed to remain long.

Do not split a line:

Before an indentation change (e.g., function declaration, conditional) To make a long string (e.g., a URL) fit into multiple shorter lines

3.4 네이밍[ | ]

Naming is more art than science. In Go, names tend to be somewhat shorter than in many other languages, but the same general guidelines apply. Names should:

Not feel repetitive when they are used Take the context into consideration Not repeat concepts that are already clear You can find more specific guidance on naming in decisions.

3.5 로컬 일관성[ | ]

스타일 가이드가 스타일의 특정한 부분에 대해 언급이 없는 경우, 인접한 코드(일반적으로 동일한 파일 또는 패키지 내에 있지만 때로는 팀 또는 프로젝트 디렉토리 내의 코드)가 문제에 대해 일관된 입장을 취하지 않는 한, 작성자는 선호하는 스타일을 자유롭게 선택할 수 있다.

유효한 로컬 스타일 고려사항 예시:

  • 오류 출력 포맷팅에 %s 또는 %v 사용
  • 뮤텍스 대신 버퍼드 채널 사용

유효하지 않은 로컬 스타일 고려사항 예시:

  • 코드에 대한 행 길이 제한
  • 어설션 기반 테스트 라이브러리 사용

로컬 스타일이 스타일 가이드와 일치하지 않지만 가독성 영향도가 하나의 파일로 제한되는 경우, 일반적으로 문제의 CL 범위를 벗어나는 일관된 수정사항에 대한 코드 검토에서 표면화된다. 그 시점에서 수정사항을 추적하기 위해 버그를 신고하는 것이 적절하다.

변경사항이 기존 스타일 편차를 악화시키거나 더 많은 API 표면에 노출하거나 편차가 있는 파일 수를 확장하거나 실제 버그를 만드는 경우 로컬 일관성은 더 이상 스타일 가이드 위반에 대한 유효한 정당화가 아니다. 새 코드. 이러한 경우 작성자는 동일한 CL에서 기존 코드베이스를 정리하거나 현재 CL보다 먼저 리팩터링을 수행하거나 적어도 로컬 문제를 악화시키지 않는 대안을 찾는 것이 적절하다.