Skip to content

Commit dce0ba2

Browse files
Generated best practices file
1 parent 7ca0bd9 commit dce0ba2

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed

best_practices.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
2+
<b>Pattern 1: When introducing new behavior branches (e.g., feature flags, unsupported event types, or ambiguous inputs), return early with a clear outcome (nil event / no-op) instead of bubbling up errors that create noisy or misleading provider statuses.
3+
</b>
4+
5+
Example code before:
6+
```
7+
if isUnsupported(payload) {
8+
return nil, fmt.Errorf("unsupported event")
9+
}
10+
// later: provider sees error and posts "failed" status
11+
```
12+
13+
Example code after:
14+
```
15+
if isUnsupported(payload) {
16+
logger.Infof("skipping unsupported event: %s", reason)
17+
return nil, nil // explicit no-op; caller treats as "ignore"
18+
}
19+
```
20+
21+
<details><summary>Examples for relevant past discussions:</summary>
22+
23+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2321#discussion_r2526171554
24+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2338#discussion_r2570635760
25+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2044#discussion_r2048435591
26+
</details>
27+
28+
29+
___
30+
31+
<b>Pattern 2: Guard against nil/absent nested fields in webhook payloads and templating contexts by using nil-safe getters where available and adding explicit checks only where direct field access can still panic (e.g., slices/maps without Getters).
32+
</b>
33+
34+
Example code before:
35+
```
36+
// may panic if PullRequest is nil or Labels is nil
37+
for _, l := range evt.PullRequest.Labels {
38+
labels = append(labels, l.Name)
39+
}
40+
```
41+
42+
Example code after:
43+
```
44+
pr := evt.GetPullRequest()
45+
if pr != nil {
46+
for _, l := range pr.Labels { // still requires pr != nil
47+
labels = append(labels, l.GetName())
48+
}
49+
}
50+
```
51+
52+
<details><summary>Examples for relevant past discussions:</summary>
53+
54+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2348#discussion_r2610606932
55+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2348#discussion_r2610613693
56+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2264#discussion_r2394302093
57+
</details>
58+
59+
60+
___
61+
62+
<b>Pattern 3: Prefer structured/typed error handling for provider API failures; if only string matching is possible, constrain it to specific known non-actionable messages (e.g., status transition conflicts) and ensure behavior is applied consistently across similar code paths.
63+
</b>
64+
65+
Example code before:
66+
```
67+
if err != nil {
68+
postMRComment("failed to set status: " + err.Error())
69+
return err
70+
}
71+
```
72+
73+
Example code after:
74+
```
75+
if err != nil {
76+
if strings.Contains(err.Error(), "Cannot transition status") {
77+
logger.Debugf("status already set; skipping MR comment: %v", err)
78+
return nil
79+
}
80+
logger.Warnf("failed to set status: %v", err)
81+
postMRComment("failed to set status; check token permissions")
82+
return err
83+
}
84+
```
85+
86+
<details><summary>Examples for relevant past discussions:</summary>
87+
88+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2587664872
89+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2680946184
90+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2698324996
91+
</details>
92+
93+
94+
___
95+
96+
<b>Pattern 4: In tests, avoid relying on deprecated external surfaces (e.g., repository status fields) and prefer stable signals (annotations/logs/checkruns), and when behavior changes from erroring to logging, update tests to assert on emitted logs instead of returned errors.
97+
</b>
98+
99+
Example code before:
100+
```
101+
_, err := waitForRepoStatus("success")
102+
assert.NilError(t, err)
103+
```
104+
105+
Example code after:
106+
```
107+
logs := zapObserver.FilterMessageSnippet("cannot patch pipelinerun").TakeAll()
108+
assert.Assert(t, len(logs) > 0)
109+
```
110+
111+
<details><summary>Examples for relevant past discussions:</summary>
112+
113+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2363#discussion_r2639176544
114+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2338#discussion_r2570635760
115+
</details>
116+
117+
118+
___
119+
120+
<b>Pattern 5: Centralize commonly repeated logic (fallbacks, caching, helpers) behind a single accessor/helper method to avoid duplicated behavior across call sites (e.g., console URL fallback, changed-files caching, shared provider event parsing).
121+
</b>
122+
123+
Example code before:
124+
```
125+
func detailURL(host string, ns, name string) string {
126+
if host == "" { host = "fallback" }
127+
return fmt.Sprintf("https://%s/...", host)
128+
}
129+
func namespaceURL(host string, ns string) string {
130+
if host == "" { host = "fallback" }
131+
return fmt.Sprintf("https://%s/...", host)
132+
}
133+
```
134+
135+
Example code after:
136+
```
137+
func (o *Console) Host() string {
138+
if o.host == "" { return "fallback" }
139+
return o.host
140+
}
141+
func (o *Console) DetailURL(ns, name string) string {
142+
return fmt.Sprintf("https://%s/...", o.Host())
143+
}
144+
```
145+
146+
<details><summary>Examples for relevant past discussions:</summary>
147+
148+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2288#discussion_r2424435108
149+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2317#discussion_r2517263653
150+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2044#discussion_r2052211429
151+
</details>
152+
153+
154+
___
155+
156+
<b>Pattern 6: Keep PRs focused: when adding a feature, avoid bundling refactors or deprecation cleanups in the same change unless strictly necessary; move unrelated maintenance into a separate PR/commit for traceability.
157+
</b>
158+
159+
Example code before:
160+
```
161+
// Feature: accept new param
162+
// Also: large refactor + deprecation removal + formatting churn
163+
```
164+
165+
Example code after:
166+
```
167+
// PR 1: accept new param + tests
168+
// PR 2: refactor internals / remove deprecated paths
169+
```
170+
171+
<details><summary>Examples for relevant past discussions:</summary>
172+
173+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2290#discussion_r2432282457
174+
</details>
175+
176+
177+
___
178+
179+
<b>Pattern 7: Use consistent naming, types, and lint-aligned patterns: prefer idiomatic local variable casing, avoid redundant variables (reuse err), and align integer sizes with updated SDKs (int→int64) to prevent subtle mismatches in provider clients and tests.
180+
</b>
181+
182+
Example code before:
183+
```
184+
OrgAndRepo := fmt.Sprintf("%s/%s", org, repo)
185+
_, _, err2 := client.Do()
186+
if err2 != nil { return err2 }
187+
```
188+
189+
Example code after:
190+
```
191+
orgAndRepo := fmt.Sprintf("%s/%s", org, repo)
192+
_, _, err := client.Do()
193+
if err != nil { return err }
194+
```
195+
196+
<details><summary>Examples for relevant past discussions:</summary>
197+
198+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2317#discussion_r2547733683
199+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2609490729
200+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2353#discussion_r2610238087
201+
</details>
202+
203+
204+
___

0 commit comments

Comments
 (0)