{"id":13567243,"url":"https://github.com/amirrezaask/Goify","last_synced_at":"2025-04-04T01:31:29.131Z","repository":{"id":118782448,"uuid":"205446571","full_name":"amirrezaask/Goify","owner":"amirrezaask","description":"Goify is My Vision of clean code in golang","archived":false,"fork":false,"pushed_at":"2019-12-04T19:43:02.000Z","size":9,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-04T22:36:31.457Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"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/amirrezaask.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}},"created_at":"2019-08-30T19:40:52.000Z","updated_at":"2020-06-04T05:47:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"f2e371e7-f864-40cf-9b77-f33608218ac5","html_url":"https://github.com/amirrezaask/Goify","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirrezaask%2FGoify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirrezaask%2FGoify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirrezaask%2FGoify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirrezaask%2FGoify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amirrezaask","download_url":"https://codeload.github.com/amirrezaask/Goify/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247107816,"owners_count":20884793,"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","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":[],"created_at":"2024-08-01T13:02:26.644Z","updated_at":"2025-04-04T01:31:24.979Z","avatar_url":"https://github.com/amirrezaask.png","language":null,"funding_links":[],"categories":["Misc"],"sub_categories":[],"readme":"# Goify\n\n\n## App struct\nAlways avoid using global state for any thing. use a type like server or app to hold your global state, connections and every thing you are going too need in requests, and always try to use abstract implementation(interface) instead of concrete types, If you need something in only for example 2 of your handlers, don't put them in App struct, and initialize them in controllers or create a new struct for those 2 handlers, don't make a mess in your main app struct.\n```go\ntype App struct {\n    db someDbInterface\n    logger someLogger\n    emailer someEmailer\n    notificationService someNotificationService\n}\n```\nRemember some times your app struct becomes too fat, you can break it into simpler and smaller structs\n```go\ntype PeopleServer struct {\n    db peopleDbInterface\n    logger somelogger\n}\ntype OrderServer struct {\n    db orderDbInterface\n    logger somelogger\n    bankService bankInterface\n}\n```\n## Handlers are better when they are closures\nin Go http handlers (in stdlib, but third party libs are not that different) should be of a specific type, forexample: func(w http.ResponseWriter, req \\*http.Request), so with this function signature you can't pass any external dependencies to it like database connections or logger or any thing else, on way to do such thing is to wrap this handler in a parent function which accepts what ever you want.\n\n```go\nfunc requestHandler(someDB *sql.DB) func(w http.ResponseWriter, r *http.Request) {\n    return func(w http.ResponseWriter, r *http.Request) {\n        // do handler logic\n    }\n}\n\n```\nbenefit of this strategy in comparison with above solution (App struct) is that app struct of course handles all kind of dependency but has the overhead of having all deps in a handler whether it's going to need it or not, and probably in some edge cases like stateful application it may cause data race, but this pattern only copies the deps we actually going too need in handler because we said so, it's more explicit, and it's more concurrency friendly.\n\n## Middlewares\nThere are libraries that implement middlewares, or even routers like [gorrila/mux](http://github.com/gorrila/mux) that support middlewares outside the box, but I like to look at middlewares even simpler, middlewares are just functions that run some logic before/after request being handled by router so let's do exactly this in our code.\n```go\nfunc (b *BookServer) IsAdmin(next http.HandlerFunc) http.HandlerFunc {\n    return func(w http.ResponseWriter, r *http.Request) {\n        //query for user and check permission\n        if !userIsAdmin {\n            w.WriteHeader(401)\n            fmt.Fprintln(w, \"Access Denied\")\n        }\n        next(w,r)\n    }\n}\n```\n\n## Context is a great data container\ncontexts are a great a way of having propagation of goroutines but they have another great ability, they can hold data in them as well.\nimagin you have a middleware that checks for user identity, so it will query database for user, and you are going too need that data later, you can simply change request and put it in request context as below:\n```go\nfunc (b *BookServer) CheckUserIdentity(next http.HandlerFunc) http.HandlerFunc {\n    return func(w http.ResponseWriter, r *http.Request) {\n        //get user identity from db\n        // changing request context to a new context with user identity\n        r = r.WithContext(context.WithValue(r.Context(), \"ident\", /*user identity*/))\n        next(w, r)\n    }\n}\n```\n\n## DRY is good but not always\ncreating abstraction over a repititive code that you write most of the times is good but in my experience some times it's easier to have a code that is copied few times, to have a abstraction that is either complex and unreadable or has overhead.\n\n## Always stick to left\nWhen you are reading code, it's nice to see all logic sequentialy following each other, program flow should not be indented into conditions, unless you are handling an edge case or error scenario.\n```go\n// hard to follow\nfunc someErrorProneFunction(x, y int) (int,error) {\n    r, err := redis()\n    if err == nil {\n        err = r.Set(x, y)\n        if err == nil {\n            z, err := r.Get(x)\n            if err == nil {\n                return z, nil\n            }\n            return 0, err\n        }\n        return err\n    }\n    return err\n}\n// more simple version\nfunc someErrorProneFunction(x, y int) (int, error) {\n    r, err := redis()\n    if err != nil {\n      return 0, err  \n    }\n    err = r.Set(x, y)\n    if err != nil {\n        return 0, err\n    }\n    z, err := r.Get(x)\n    if err != nil {\n        return 0, err\n    }\n    return z, nil\n\n}\n\n\n```\n\n## Be liberal in what you accept, and be conservative in what you return\nwhen writing a function always accept parameters that are generic and abstract like interfaces, because they are so much easier to mock\nand always return concrete types again because they are much simpler to assert in tests.\n```go\nfunc WriteToFile(f *os.File) {} // Wrong: when testing this function creating a os.File is too expensive\n\nfunc WriteToFile(w io.Writer) {} // Good: when testing this function it's really easy to create io.Pipe and pass pipeWriter to this function and assert on reader\n\nfunc WriteToFile(rwc io.ReadWriteCloser) {} // Not Good: we don't know exactly what this function is going todo, is it going to close writer? is it going to read ? we don't know.\n\n```\n\nanother thing about passing an interface is that an interface some how shows us what this functions is going to do, forexample in above when we are passing a file we really don't know what this function is going to do with this file, maybe it's going to read content, maybe write something, maybe append something we don't know, but in second function we exactly know that this function is going to write on the interface we are passing to it.\n\n```go\nfunc NewRecorder() io.Writer // not good because we don't know what is the exact type behind io.Writer, this could cause problem\nfunc NewRecorder() *os.File // good we know exactly that this function is opening/creating a file\n\n```\n\n## Initialize only when you need to\ngolang has multiple ways of defining and initializing variables, like `:=` and `var`, don't always use them interchangably, if you want to only define a variable but you want to intialize it later, use `var` and if you want to define and initialize use `:=`, don't use `:=` all the time.\n```go\n//not good\nmodel := Model{}\n\nerr := json.Unmarshall(data, \u0026model)\nif err != nil {\n    return fmt.Errorf(\"error in unmarshalling json: %v\", err)\n}\n\n//good\nvar model Model\n\nerr := json.Unmarshall(data, \u0026model)\nif err != nil {\n    return fmt.Errorf(\"error in unmarshalling json: %v\", err)\n}\n\n``` \nlike above example when you want to forexample pass pointer of a variable to be the destination of a data, just define it and not initialize it\n\n\n## const when possible, var when necessary\nconstatns are one of the few ways we can approach immutability in golang, they are super fast so use them in every place you can. When you define a constant you are helping go-compiler a lot, with a constant compiler knows that this value is not going to change through the entier program so it can do so many optimizations around it, another thing is when you are working in concurrent programs it's so good to avoid sharing some data that can be manipulated by multiple goroutines. \n\n## expected errors should be expected\nif you are writing a package, let's say a bank api interface for golang, always for expected error types like connection problems or domain specific errors create error types so that user of that \npackage be able to assert different error types. another thing is to always define your errors as consts and not vars because they are exported and it's really easy to mess up with them so make them const and immutable.\n\n## table driven tests\nit's good practice to find all parameters that can change a function behaviour and create a type holding all those parameters and then test the function with different values for those params.\n```go\nfunc TestSum(t *testing.T) {\n    tt:=map[string]struct{\n        x,y,exp int\n    }{\n        \"simple math 2+3=5\": {\n            2,3,5\n        },\n        \"simple math 2+0=2\": {\n            2,2,2\n        }\n    }\n    for sc,tc:=range tt{\n        res:=Sum(tc.x,tc.y)\n        if res != tc.exp{\n            t.Errorf(\"Scenario %s faild, expected:%v got:%v\", sc,tc.exp, res)\n        }\n    }\n\n}\n\n```\nanother good practice is to name your test scenarios, it'll help you a lot when debugging.\n\n## don't name vars after what they are, name them after what they do\nDon't have a variable named forexample usersMap, don't put type of variables in their name, good name for a variable shows what that variable is used for. forexample:\n```go\npackage user\nvar usersMap map[int]*User // not good, if we look at the variable name we cannot understand what it is doing\n\nvar usersRepository map[int]*User // better but we are defining this variable in user package so we already know that this is users repository\n\nvar Repository map[int]*User // probably best soloution\n\n```\n\n## Smaller the interface, better\nmake your interfaces as small as possible, interfaces meant to be a way to focus on similarities of types in functionality, when you create a fat interface, first of all it's hard to implement it, and then it complicates the code that is using that interface because from outside it's not clear that forexample a function uses which one of methods in that interface\n\n```go\n\n//not good\ntype Animal interface {\n    Breath()\n    Eat()\n    Die()\n    Sleep()\n    Go()\n    Stay()\n} // too fat interface\nfunc AnimalManager(a Animal) {} // we don't know exactly what this functional is going to do with the animal\n\n//good\ntype Eater interface {\n    Eat()\n}\nfunc AnimalManager(e Eater) {} // now we exactly know that this function has only one option of calling the Eat() method, we can mock/Patch method with out even looking at function body\n\n```\n\n\n## Don't tell exactly what you want, describe it\n\nwhen writing a pkg or an exposed function instead of asking for a concrete type, ask for an abstraction that describes what you are going to do with that value.\n```go\ntype CacheConn struct {\n    // some unnecessary detail\n}\nfunc UpdateCache(c *CacheConn) {} // in this function outside world needs to create a cache connection and then pass it to us, and in case of unit tests we need some cache connection only for test and that is not good\n\n\ntype Cache interface {\n    Update(key string, data interface{}) error\n}\n\nfunc UpdateCache() {} // better, now for unit test we can easily create a stub/mock of this interface. \n\n```\nremember when writing functions that accepts only interfaces, you should make your interfaces as small as possible\n\n## Reflect is Evil, but a necessary one\nfor go developers, reflect package is like sun to vampires. but at least in my experience some times reflect package is the best way to implement a generic solution. reflect package has overhead, maybe some times it increases the complexity but as I said it's the necassary evil, an evil package that golang programs need to survive. stdlib uses reflect too, forexample json marshal/unmarshal uses reflect package to read struct tags, so it's not some thing that you never should touch, but it's some thing you need to be carefull when using, because of overhead and some times complexity.\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famirrezaask%2FGoify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famirrezaask%2FGoify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famirrezaask%2FGoify/lists"}