{"record":{"id":"2dd7e60dbd62eeb2","repo":"unknwon/the-way-to-go_ZH_CN","slug":"key-already-exists","errorCode":null,"errorMessage":"key already exists","messagePattern":"key already exists","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"eBook/19.8.md","lineNumber":68,"sourceCode":"\t\t*key = genKey(s.Count())\n\t\t\tif err := s.Set(key, url); err == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\tif s.save != nil {\n\t\ts.save <- record{*key, *url}\n\t}\n\treturn nil\n}\n```\n\n`Put()` 调用 `Set()`，由于后者也要做调整，`key` 和 `url` 参数现在是指针类型，还必须返回 `error` 取代 `boolean`：\n```go\nfunc (s *URLStore) Set(key, url *string) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif _, present := s.urls[*key]; present {\n\t\treturn errors.New(\"key already exists\")\n\t}\n\ts.urls[*key] = *url\n\treturn nil\n}\n```\n\n同样，当从 `load()` 调用 `Set()` 时，也必须做调整：\n```go\ns.Set(&r.Key, &r.URL)\n```\n\n还必须修改 HTTP 处理函数以适应 `URLStore` 上的更改。`Redirect()` 处理函数现在返回 `URLStore` 给出错误的字符串形式：\n```go\nfunc Redirect(w http.ResponseWriter, r *http.Request) {\n\tkey := r.URL.Path[1:]\n\tvar url string\n\tif err := store.Get(&key, &url); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/unknwon/the-way-to-go_ZH_CN/blob/7a54d34d3657084b6a59e5618bd069b912d571aa/eBook/19.8.md#L50-L86","documentation":"Returned by URLStore.Set when the proposed short key is already present: Set takes the write lock, checks if _, present := s.urls[*key]; present, and refuses to overwrite, returning errors.New(\"key already exists\"). By design, Put loops forever doing *key = genKey(s.Count()) until Set succeeds — so seeing this error escape means the retry loop was bypassed or Count() no longer reflects the stored keys.","triggerScenarios":"load() replays a persisted data file into Set while genKey still derives keys from s.Count(), so regenerated keys collide with already-loaded ones; or a direct call to Set from handler code skips Put's regeneration loop; duplicated lines in the store file also make Count() drift from len(s.urls).","commonSituations":"Restarting the service with a populated data_store.txt; counter/len divergence after partial loads or failed saves; deleting entries without adjusting the counter; shortening genKey's alphabet so its cycle is exhausted and collisions become routine.","solutions":["Never call Set directly from request handlers — always go through Put's for-loop, which re-derives *key = genKey(s.Count()) after every Set error and self-heals collisions","After load(), sync the counter so genKey starts past existing keys: derive Count() from len(s.urls) or persist the counter alongside the records","If your edit removed the loop around Set, restore it: for { *key = genKey(s.Count()); if err := s.Set(key, url); err == nil { break } }","For long-lived stores, replace count-derived keys with a monotonic or random scheme so keys are never revisited"],"exampleFix":"// before: single attempt, collision escapes to the caller\nif err := s.Set(&key, &url); err != nil {\n\treturn err // 'key already exists' reaches the HTTP layer\n}\n\n// after: book's design — regenerate until a free key is found\nfor {\n\t*key = genKey(s.Count())\n\tif err := s.Set(key, url); err == nil {\n\t\tbreak\n\t}\n}","handlingStrategy":"retry","validationCode":"if _, present := urls[key]; present {\n\t// pick the next candidate before calling Set\n\tkey = genKey(count + 1)\n}","typeGuard":null,"tryCatchPattern":"for {\n\t*key = genKey(s.Count())\n\tif err := s.Set(key, url); err == nil {\n\t\tbreak // key accepted\n\t}\n\t// 'key already exists': loop regenerates a fresh candidate\n}","preventionTips":["Never call Set directly from request handlers; always go through Put's retry loop","After load(), keep genKey's input (Count()) consistent with the stored records — persist or recompute the counter","Make genKey injectable in tests and assert it produces distinct keys when Set reports collisions","Prefer monotonic or random key schemes over count-derived keys for long-lived stores"],"tags":["go","url-shortener","key-collision","map","concurrency"],"backgroundTag":null,"analyzedSha":"7a54d34d3657084b6a59e5618bd069b912d571aa","analyzedAt":"2026-08-15T15:13:06.026Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}