Skip to content

Commit ab224d1

Browse files
chore: update style guide (#8415)
Co-authored-by: Gjermund Garaba <gjermund@garaba.net>
1 parent 7d579b9 commit ab224d1

File tree

1 file changed

+249
-1
lines changed

1 file changed

+249
-1
lines changed

docs/dev/go-style-guide.md

Lines changed: 249 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
In order to keep our code looking good with lots of programmers working on it, it helps to have a "style guide", so all the code generally looks quite similar. This doesn't mean there is only one "right way" to write code, or even that this standard is better than your style. But if we agree to a number of stylistic practices, it makes it much easier to read and modify new code. Please feel free to make suggestions if there's something you would like to add or modify.
55

6-
We expect all contributors to be familiar with [Effective Go](https://golang.org/doc/effective_go.html) (and it's recommended reading for all Go programmers anyways). Additionally, we generally agree with the suggestions in [Uber's style guide](https://github.com/uber-go/guide/blob/master/style.md) and use that as a starting point.
6+
We expect all contributors to be familiar with [Effective Go](https://golang.org/doc/effective_go.html) (and it's recommended reading for all Go programmers anyways). Additionally, we generally agree with the suggestions in [Google's style guide](https://google.github.io/styleguide/go/index) and use that as a starting point.
77

88
## Code Structure
99

@@ -65,6 +65,7 @@ type middleware struct {
6565

6666
- Acronyms are all capitalized, like "RPC", "gRPC", "API". "MyID", rather than "MyId".
6767
- Whenever it is safe to use Go's built-in `error` instantiation functions (as opposed to Cosmos SDK's error instantiation functions), prefer `errors.New()` instead of `fmt.Errorf()` unless you're actually using the format feature with arguments.
68+
- As a general guideline, prefer to make the methods for a type [either all pointer methods or all value methods.](https://google.github.io/styleguide/go/decisions#receiver-type)
6869

6970
## Importing libraries
7071

@@ -117,3 +118,250 @@ sdkerrors.Wrapf(
117118
<value 2>
118119
)
119120
```
121+
122+
## Common Mistakes
123+
124+
This is a compilation of some of the common mistakes we see in the repo that should be avoided.
125+
126+
---
127+
Keep receiver names short [Details here](https://google.github.io/styleguide/go/decisions#receiver-names)
128+
129+
```go
130+
// bad
131+
func (chain *TestChain) NextBlock() {
132+
res, err := chain.App.FinalizeBlock(&abci.RequestFinalizeBlock{
133+
Height: chain.ProposedHeader.Height,
134+
Time: chain.ProposedHeader.GetTime(),
135+
NextValidatorsHash: chain.NextVals.Hash(),
136+
})
137+
require.NoError(chain.TB, err)
138+
chain.commitBlock(res)
139+
}
140+
```
141+
142+
```go
143+
// good
144+
func (c *TestChain) NextBlock() {
145+
// Ommitted
146+
```
147+
148+
---
149+
**Naked returns**
150+
151+
We should always try to avoid naked returns. [Reference](https://google.github.io/styleguide/go/decisions#named-result-parameters)
152+
153+
---
154+
**Function and method calls should not be separated based solely on line length**
155+
156+
The signature of a function or method declaration [should remain on a single line](https://google.github.io/styleguide/go/decisions#function-formatting) to avoid indentation confusion.
157+
158+
```go
159+
// bad
160+
func (im IBCMiddleware) OnRecvPacket(
161+
ctx sdk.Context,
162+
channelVersion string,
163+
packet channeltypes.Packet,
164+
relayer sdk.AccAddress,
165+
) ibcexported.Acknowledgement {
166+
167+
// good
168+
func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement {
169+
```
170+
171+
---
172+
**Don't Use Get in function/Method names**
173+
[Reference](https://google.github.io/styleguide/go/decisions#getters)
174+
175+
```go
176+
// bad
177+
178+
// GetChainBID returns the chain-id for chain B.
179+
func (tc TestConfig) GetChainBID() string {
180+
if tc.ChainConfigs[1].ChainID != "" {
181+
return tc.ChainConfigs[1].ChainID
182+
}
183+
return "chainB-1"
184+
}
185+
186+
// good
187+
func (tc TestConfig) ChainID(i int) string {
188+
if tc.ChainConfigs[i].ChainID != "" {
189+
return tc.ChainConfigs[i].ChainID
190+
}
191+
return "chainB-1"
192+
}
193+
```
194+
195+
---
196+
**Do not make confusing indentation for saving vertical spaces**
197+
198+
```go
199+
// Bad
200+
cases := []struct {
201+
name string
202+
malleate func()
203+
expErr error
204+
}{
205+
{"verification success", func() {}, nil},
206+
{"verification success: delay period passed", func() {
207+
delayTimePeriod = uint64(1 * time.Second.Nanoseconds())
208+
}, nil},
209+
{"delay time period has not passed", func() {
210+
delayTimePeriod = uint64(1 * time.Hour.Nanoseconds())
211+
}, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet commitment verification for client (07-tendermint-0): cannot verify packet until time: 1577926940000000000, current time: 1577923345000000000")},
212+
{"client status is not active - client is expired", func() {
213+
clientState, ok := path.EndpointB.GetClientState().(*ibctm.ClientState)
214+
suite.Require().True(ok)
215+
clientState.FrozenHeight = clienttypes.NewHeight(0, 1)
216+
path.EndpointB.SetClientState(clientState)
217+
}, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Frozen")},
218+
}
219+
```
220+
221+
```go
222+
// Bad
223+
{
224+
"nil underlying app", func() {
225+
isNilApp = true
226+
}, nil,
227+
},
228+
```
229+
230+
```go
231+
// Good
232+
{
233+
"nil underlying app",
234+
func() {
235+
isNilApp = true
236+
},
237+
nil,
238+
},
239+
```
240+
241+
## Good Practices
242+
243+
**Testing context**
244+
245+
Go 1.24 added a (testing.TB).Context() method. In tests, prefer using (testing.TB).Context() over context.Background() to provide the initial context.Context used by the test. Helper functions, environment or test double setup, and other functions called from the test function body that require a context should have one explicitly passed. [Referance](https://google.github.io/styleguide/go/decisions#contexts)
246+
247+
---
248+
**Error Logging**
249+
250+
If you return an error, it’s usually better not to log it yourself but rather let the caller handle it.
251+
[Reference](https://google.github.io/styleguide/go/best-practices.html#error-logging)
252+
253+
---
254+
**Struct defined outside of the package**
255+
256+
Must have fields specified. [Referance](https://google.github.io/styleguide/go/decisions#field-names)
257+
258+
```go
259+
// Good:
260+
r := csv.Reader{
261+
Comma: ',',
262+
Comment: '#',
263+
FieldsPerRecord: 4,
264+
}
265+
```
266+
267+
```go
268+
// Bad:
269+
r := csv.Reader{',', '#', 4, false, false, false, false}
270+
```
271+
272+
---
273+
**Naming struct fileds in tabular tests**
274+
275+
If tabular test struct has more than two fields, consider explicitly naming them. If the test struct has one name and one error field, then we can allow upto three fields. If test struct has more fields, consider naming them when writing test cases.
276+
277+
```go
278+
// Good
279+
280+
tests := []struct {
281+
name string
282+
memo string
283+
expectedPass bool
284+
message string
285+
registerInterfaceFn func(registry codectypes.InterfaceRegistry)
286+
assertionFn func(t *testing.T, msgs []sdk.Msg)
287+
}{
288+
{
289+
name: "packet data generation succeeds (MsgDelegate & MsgSend)",
290+
memo: "",
291+
expectedPass: true,
292+
message: multiMsg,
293+
registerInterfaceFn: func(registry codectypes.InterfaceRegistry) {
294+
stakingtypes.RegisterInterfaces(registry)
295+
banktypes.RegisterInterfaces(registry)
296+
},
297+
assertionFn: func(t *testing.T, msgs []sdk.Msg) {
298+
t.Helper()
299+
assertMsgDelegate(t, msgs[0])
300+
assertMsgBankSend(t, msgs[1])
301+
},
302+
},
303+
}
304+
```
305+
306+
```go
307+
// Bad
308+
testCases := []struct {
309+
name string
310+
malleate func()
311+
callbackFn func(
312+
ctx sdk.Context,
313+
packetDataUnmarshaler porttypes.PacketDataUnmarshaler,
314+
packet channeltypes.Packet,
315+
maxGas uint64,
316+
) (types.CallbackData, bool, error)
317+
getSrc bool
318+
}{
319+
{
320+
"success: src_callback v1",
321+
func() {
322+
packetData = transfertypes.FungibleTokenPacketData{
323+
Denom: ibctesting.TestCoin.Denom,
324+
Amount: ibctesting.TestCoin.Amount.String(),
325+
Sender: sender,
326+
Receiver: receiver,
327+
Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, sender),
328+
}
329+
330+
expCallbackData = expSrcCallBack
331+
332+
s.path.EndpointA.ChannelConfig.Version = transfertypes.V1
333+
s.path.EndpointA.ChannelConfig.PortID = transfertypes.ModuleName
334+
s.path.EndpointB.ChannelConfig.Version = transfertypes.V1
335+
s.path.EndpointB.ChannelConfig.PortID = transfertypes.ModuleName
336+
},
337+
types.GetSourceCallbackData,
338+
true,
339+
},
340+
}
341+
```
342+
343+
## Known Anti Patterns
344+
345+
It's strongly recommended [not to create a custom context](https://google.github.io/styleguide/go/decisions#custom-contexts). The cosmos sdk has it's own context that is passed around, and we should not try to work against that pattern to avoid confusion.
346+
347+
---
348+
Test outputs should include the actual value that the function returned before printing the value that was expected. A standard format for printing test outputs is YourFunc(%v) = %v, want %v. Where you would write “actual” and “expected”, prefer using the words “got” and “want”, respectively. [Referance](https://google.github.io/styleguide/go/decisions#got-before-want)
349+
350+
But testify has it other way around.
351+
352+
`Require.Equal(Expected, Actual)`
353+
354+
This is a known anti pattern that we allow as the testify package is used heavily in tests.
355+
356+
---
357+
In tests suites we are embedding `testify/require` package for assertions. Since it's embedded and there are no name collision on `require` types methods, when calling, we should "promote" those methods to the suite. But throughout the repo we make explicit calls.
358+
359+
```go
360+
s.Require().NoError(err)
361+
```
362+
363+
A better and more cleaner approach could be,
364+
365+
```go
366+
s.NoError(err)
367+
```

0 commit comments

Comments
 (0)