{"id":24108227,"url":"https://github.com/solvicode/Go-Tiger-Style","last_synced_at":"2026-06-20T17:04:25.019Z","repository":{"id":271844506,"uuid":"914442435","full_name":"Predixus/Go-Tiger-Style","owner":"Predixus","description":"Adaptation of Tigerstyle, for Golang","archived":false,"fork":false,"pushed_at":"2025-02-07T08:31:40.000Z","size":156,"stargazers_count":13,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T16:01:41.009Z","etag":null,"topics":["golang","performance","powerof10","robustness","tigerbeetle"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Predixus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-09T15:53:31.000Z","updated_at":"2025-03-30T22:06:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"d1f7a3e0-3a40-493b-b3e7-337a09ca1f65","html_url":"https://github.com/Predixus/Go-Tiger-Style","commit_stats":null,"previous_names":["predixus/go-tiger-style"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Predixus/Go-Tiger-Style","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Predixus%2FGo-Tiger-Style","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Predixus%2FGo-Tiger-Style/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Predixus%2FGo-Tiger-Style/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Predixus%2FGo-Tiger-Style/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Predixus","download_url":"https://codeload.github.com/Predixus/Go-Tiger-Style/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Predixus%2FGo-Tiger-Style/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278561277,"owners_count":26006954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","performance","powerof10","robustness","tigerbeetle"],"created_at":"2025-01-10T23:26:31.073Z","updated_at":"2026-06-20T17:04:24.957Z","avatar_url":"https://github.com/Predixus.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go! Tiger Style\n\nA set of amendments to [Tigerstyle](https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md),\nfor Golang.\n\nFirst, I would read [TigerStyle](https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md) fully.\n\nThis will provide a solid foundation for the Go! specific amendments listed herein.\n\n## Why Style?\n\nAs taken directly from the TigerStyle docs:\n\n\u003e \"...in programming, style is not something to pursue directly. Style is necessary only where\n\u003e understanding is missing.\"\n\nFor our operations at Predixus, we are building data driven applications in Go. This, if done earnestly, results\nin us going into the unknown and recovering the proverbial gold to distribute. And so the goal of a style is to\nguide and support development through the unknown.\n\n## Technical Debt\n\nThere is nothing to add here. Tigerstyle nailed it. Refer to\n[their comments](https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md#technical-debt) on technical debt.\n\n## Safety \u0026 Performance\n\nThe code in many of these amendments has been implemented, tested, benchmarked and fuzzed. Look at [bench.txt](bench.txt)\nfor details on the benchmarks and the `main.go` \u0026 `main_test.go` files for details on the tests.\n\n[NASAs Power of 10](https://spinroot.com/gerard/pdf/P10.pdf) still applies. But there are some modifications\nthat we need to make that are specific to Go!:\n\n- Always be explicit about capacity, when allocating via `make`:\n\n  1. Preventing Hidden Allocations in Slices\n\n     Pre-allocate capacity when the final size is known to avoid expensive grow operations:\n\n     ```go\n     // Good: Single allocation with known final size\n     data := make([]int16, 0, 1000)\n     for i := 0; i \u003c 1000; i++ {\n         data = append(data, int16(i))  // No reallocation needed\n     }\n\n     // Bad: Multiple allocations as slice grows\n     data := make([]int16, 0)\n     for i := 0; i \u003c 1000; i++ {\n         data = append(data, int16(i))  // May trigger reallocation\n     }\n     ```\n\n2. Understanding Capacity Sharing Between Slices\n\n   There are two approaches to handling slice capacity sharing, each with different trade-offs:\n\n   - The first, is safe but costly (in allocations). This approach should be used when slice independence\n     needs to be garunteed.\n\n     ```go\n     original := make([]int16, 5, 5)\n     slice2 := make([]int16, 3)\n     copy(slice2, original[0:3]) // Copy just the elements we want\n     slice2 = append(slice2, 6)  // Now this truly won't affect original\n     ```\n\n   - The second approach is fast, but requires careful handling as capacity of a single slice is shared.\n     Use when performance is critical and the implications are well understood.\n\n     ```go\n     original := make([]int16, 5, 10)\n     slice2 := original[0:3]     // slice2 shares backing array\n     slice2 = append(slice2, 6)  // Modifies original's backing array!\n     ```\n\n   Choose between these patterns based on your needs:\n\n   - Use no-sharing when slice independence is crucial for correctness\n   - Use sharing when performance is critical and you can carefully manage the slice relationships\n   - The sharing approach is ~140x faster but requires more careful programming (inspect the benchmarks)\n\n3. Preventing Rehashing when Initialising Maps\n\n   Pre-size maps when the approximate size is known to avoid expensive rehashing operations:\n\n   ```go\n   // Good: Single hash table allocation\n   users := make(map[string]User, 1000)\n   for i := 0; i \u003c 1000; i++ {\n       users[fmt.Sprintf(\"user%d\", i)] = User{}  // No rehashing needed\n   }\n\n   // Bad: Multiple rehashing operations\n   users := make(map[string]User)  // Default small capacity\n   for i := 0; i \u003c 1000; i++ {\n       users[fmt.Sprintf(\"user%d\", i)] = User{}  // Forces periodic rehashing\n   }\n   ```\n\n4. Preventing Deadlocks in Channels\n\n   Be explicit about channel buffering intent, in the name of the variable to\n   prevent accidental deadlocks:\n\n   ```go\n   // Good: Clear buffering intent for synchronous communication\n   chSync := make(chan int)\n\n   // Good: Buffered for async communication, up to a capacity\n   chAsync := make(chan int, 5)\n\n   // Bad: Default to unbuffered without considering communication patterns\n   ch := make(chan int)  // Might deadlock if async communication is needed\n   ```\n\n5. Explicit size Buffer Pools to Prevent Growth\n\n   When implementing buffer pools, explicit capacity helps prevent buffer growth:\n\n   ```go\n   // Good: Fixed-size buffer pool\n   type Pool struct {\n       buffers sync.Pool\n   }\n\n   func NewPool() *Pool {\n       return \u0026Pool{\n           buffers: sync.Pool{\n               New: func() interface{} {\n                   return make([]byte, 0, 4096)  // Fixed capacity\n               },\n           },\n       }\n   }\n\n   // Bad: Growable buffers can escape size constraints\n   func NewPool() *Pool {\n       return \u0026Pool{\n           buffers: sync.Pool{\n               New: func() interface{} {\n                   return make([]byte, 0)  // Can grow unbounded\n               },\n           },\n       }\n   }\n   ```\n\n   Choose based on your requirements:\n\n   - Use fixed-size pools when memory constraints are critical\n   - Use growable pools when performance is the priority\n\n- Go does not have any natural notion of `assert`. The Go development team have stated their view on this:\n\n  \u003e [\"...programmers use them as a crutch to avoid thinking about proper error handling and reporting\"](https://go.dev/doc/faq#assertions)\n\n  Completely transparent error handling in Go is indeed one of its strongest features - there is no need to use\n  assertions to replace it. But there is value in using assertions to capture programmer errors that should be\n  be caught during the testing phase. As stated in Tigerstyle:\n\n  \u003e \"Assertions are a force multiplier for discovering bugs by fuzzing.\"\n\n  To achieve this, build tags should be used to build release and debug assertion functions:\n\n  ```go\n  //assert_debug.go\n  //go:build debug\n  package main\n\n  func assert(condition boolean, msg string) {\n    if !condition {\n        panic(msg)\n    }\n  }\n\n  //assert_release.go\n  //go:build !debug\n  package main\n\n  func assert(condition boolean, msg string) {}\n  ```\n\n  And used like so:\n\n  ```go\n    package main\n\n    type Counter struct {\n        count     int16\n    }\n\n    func (c *Counter) Increment() {\n        c.count += 1\n    }\n\n    func (c *Counter) Reset() {\n        c.count = 0\n    }\n\n    func (c *Counter) Update(u int16) {\n        c.count += u\n        assert(c.count\u003e=0, \"Count cannot be negative\")\n    }\n\n    func (c *Counter) Count() int16 {\n        return c.count\n    }\n  ```\n\n  If an assertion is raised, then the Counter has been incorrectly updated with a negative integer.\n  This highlights two programmer errors:\n\n  1. `count` should be of type `int16`\n  2. The calling code expected to be able to pass negative integers\n\n- Assert the _Property Space_ wherever possible, and use Golangs Fuzzer to test it\n\n  Property-based testing expands beyond traditional table-driven tests by verifying properties\n  that should hold true for entire classes of inputs, rather than just specific examples. While\n  table tests verify individual points in the input-output space, property tests verify relationships\n  that should hold across the entire space.\n\n  ![Property based test image](./assets/images/prop_based_test_dark.png#gh-dark-mode-only)\n  ![Property based test image](./assets/images/prop_based_test_light.pngpng#gh-light-mode-only)\n\n  For example, consider a function that reverses a string:\n\n  ```go\n  import (\n      \"testing\"\n  )\n\n  // Traditional table test - tests input output pairs\n  func TestReverse(t *testing.T) {\n      tests := []struct {\n          input    string\n          expected string\n      }{\n          {\"hello\", \"olleh\"},\n          {\"world\", \"dlrow\"},\n      }\n      for _, tt := range tests {\n          got := Reverse(tt.input)\n          assert.Equal(t, tt.expected, got, \"Reverse(%q)\", tt.input)\n      }\n  }\n\n  // Property-based test\n  func FuzzReverse(f *testing.F) {\n  \tseeds := []string{\"\", \"a\", \"hello\", \"12345\", \"!@#$%\"}\n  \tfor _, seed := range seeds {\n  \t\tf.Add(seed)\n  \t}\n\n  \tf.Fuzz(func(t *testing.T, input string) {\n  \t\t// Property 1: reversing twice should return the original string\n  \t\tif twice := ReverseString(ReverseString(input)); twice != input {\n  \t\t\tt.Errorf(\"Double reverse failed: got %q, want %q\", twice, input)\n  \t\t}\n\n  \t\t// Property 2: byte length should be preserved\n  \t\treversed := ReverseString(input)\n  \t\tif len(reversed) != len(input) {\n  \t\t\tt.Errorf(\"Length not preserved: got %d bytes, want %d bytes\",\n  \t\t\t\tlen(reversed), len(input))\n  \t\t}\n  \t})\n  }\n  ```\n\n  Key properties to consider testing when fuzzing:\n\n  1. **Invariants**: Properties that should always hold true\n  2. **Inverse operations**: Operations that should cancel each other out. E.g. encode/decode\n  3. **Idempotency**: Operations that yield the same result when applied multiple times\n  4. **Non-Idempotency**: Operations that do _not_ yield the same result when applied multiple\n     times. E.g. a hashing algorithm\n\n  When using runtime assertions on properties, focus on invariants that indicate\n  programmer errors:\n\n  ```go\n  func (v *Vector) Add(other *Vector) {\n      assert(len(v.elements) == len(other.elements), \"vectors must have same dimension\")\n      for i := range v.elements {\n          v.elements[i] += other.elements[i]\n      }\n  }\n  ```\n\n  And remember: Property-based testing complements, not replaces, traditional testing\n  approaches. Use both to achieve the required test coverage for your application.\n\n  - Use Go's static analysis tools (`go vet`, `staticcheck`, `golangci-lint`) at their\n    strictest settings\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolvicode%2FGo-Tiger-Style","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsolvicode%2FGo-Tiger-Style","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolvicode%2FGo-Tiger-Style/lists"}