Golang

142 flashcards · Programming Languages · by Mina Huang

  1. How to backward to traverse the array in Golang? for i := len(arr) - 1; i >= 0; i-- { // loop }

  2. what's rune in golang?

    A rune is a built-in type in Golang that represents a Unicode code point. It is used to represent a single Unicode character and is defined as an alias to the int32 type. In Go, strings are made up of runes, and many string manipulation functions operate on runes rather than bytes. Sure, here are a few examples: ### Converting a string to a slice of runes str := "hello" runes := []rune(str) ### Printing the Unicode code point of a rune r := 'A' fmt.Println("Unicode code point:", r) ### Iteratin…

  3. What is the purpose of json.Marshal and json.Unmarshal in Go?

    json.Unmarshal takes a byte slice containing JSON-encoded data and a pointer to a Go data structure, and populates the Go data structure with the data from the JSON-encoded byte slice. json.Marshal takes a Go data structure and returns a JSON-encoded byte slice.

  4. What is the error message "cannot use resp.Body (variable of type []byte) as io.Reader value in argument to io.ReadAll: []byte does not implement io.Reader (missing method Read)"? respBody, err := io.ReadAll(resp.Body)

    This error message occurs when you try to use a []byte variable as an io.Reader argument in the io.ReadAll function. []byte does not implement the io.Reader interface because it does not have the Read method. body, err := ioutil.ReadAll(bytes.NewBuffer(resp.Body)) if err != nil { // handle error } bytes.NewBuffer(resp.Body) creates a new io.Reader from the resp.Body byte slice, which can then be passed as an argument to io.ReadAll().

  5. The way to check the Time.Time{} is empty in Golang

    if time.IsZero() { // do something }

  6. What does HTTP 502 status code indicate?

    HTTP 502 status code, also known as Bad Gateway, indicates that CloudFront (or a proxy server) was unable to serve the requested object because it could not connect to the origin server. You can use it when third-party server goes wrong

  7. What is the difference between &(ampersand) and * (asterisk) in Golang?

    In Go, & is used to find the memory address of a variable, while * is used to access the value stored at a particular memory address. Example: var x int = 10 var p *int = &x // p stores the memory address of x fmt.Println(*p) // prints 10 (the value stored at the memory address stored in p)

  8. How to use fmt.Println to print out a struct's key?

    To use fmt.Println to print out a struct's key in Go, you can use the "%+v" verb. For example, if you have a struct named "Person" with keys "Name" and "Age", you can use the following code to print out the values of these keys: type Person struct { Name string Age int } p := Person{"John Doe", 30} fmt.Printf("%+v", p) The output will be: {Name:John Doe Age:30}

  9. What is variable shadowing in Golang?

    Variable shadowing is a variable name declared in a block that can be redeclared in an inner block Example: package main import "fmt" func main() { x := 10 fmt.Println(x) { x := 20 fmt.Println(x) } fmt.Println(x) } Output: 10 20 10

  10. How to solve the variable shadowing issue in Go? var client *http.Client if tracing { client, err := createClientWithTracing() if err != nil { return err } log.Println(client) } else { client, err := createDefaultClient() if err != nil { return err } log.Println(client) } // Use client

    Solution 1: Use temporary variables in the inner blocks var client *http.Client if tracing { c, err := createClientWithTracing() if err != nil { return err } client = c } else { // Same logic } Solution 2: Use the assignment operator = in the inner blocks Perform one assignment, easier to read than a temporary variable The error can be handled outside the `if/else` statement var client *http.Client var err error if tracing { client, err = createClientWithTracing() } else { client, err = createD…

  11. What are some best practices for writing clean code with a happy path to the left in Go?

    Happy path to the left Avoid else returns, keep the else body small Flip the if statement to existing early and indent only to deal with errors and edge cases Promote big conditional blocks to their own functions, break each body into its own function in switch cases Examples: Avoiding else returns: if !something.OK() { // flipped return errors.New("something not ok") } something.Lock() defer something.Unlock() err := something.Do() if err != nil { // flipped return err } stop := StartTimer() d…

  12. What is an init function in Go?

    An init function is used to initialize an application's state and takes no arguments and returns no result The execution order of multiple init functions from multiple packages is based on the source files' alphabetical order It is not recommended to rely on the ordering of init functions within a package as source files can be renamed, impacting the execution order Init functions cannot be called directly Example package main import "fmt" // first var a = func() int { fmt.Println("var") return…

  13. What is a bad practice to use the init function in Go and why?

    Holding a database connection pool var db *sql.DB func init() { dataSourceName := os.Getenv("MYSQL_DATA_SOURCE_NAME") d, err := sql.Open("mysql", dataSourceName) if err != nil { log.Panic(err) } err = d.Ping() if err != nil { log.Panic(err) } db = d } Error management in an init function is limited (only panic) The init function will be executed before running the test cases Require assigning to a global variable: favor encapsulating a variable rather than keeping it global

  14. What is a good practice to use the init function in Go and why?

    Set up the static HTTP configuration func init() { redirect := func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } http.HandleFunc("/blog", redirect) http.HandleFunc("/blog/", redirect) static := http.FileServer(http.Dir("static")) http.Handle("/favicon.ico", static) http.Handle("/fonts.css", static) http.Handle("/fonts/", static) http.Handle("/lib/godoc/", http.StripPrefix("/lib/godoc/", http.HandlerFunc(staticHandler))) } no need to create any global va…

  15. What's Encapsulation in programming

    hiding the values or state of an object

  16. What're getters and setters?

    Getters and setters are means to enable encapsulation by providing exported methods on top of unexported object fields. Example // Getter currentBalance := customer.Balance() if currentBalance < 0 { // Setter customer.SetBalance(0) }

  17. What are the advantages of using getters and setters?

    Enable new functionality to be added later Hide the internal representation Provide a debugging interception point for when the property changes at run time

  18. What is the naming convention for getters and setters?

    The getter method should be named Balance (not GetBalance). The setter method should be named SetBalance.

  19. What is an interface in Go? Give me 3 points of definition and example

    Definition An interface provides a way to specify the behavior of an object. We use interfaces to create common abstractions that multiple objects can implement. There is no explicit keyword like implements to mark that an object X implements interface Y in Golang. Example type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) }

  20. What are the 3 benefits of using an interface in Go?

    flexible and reusable code: Using an interface in Go allows for more flexible and reusable code. write functions that work with any object: By creating a common interface that multiple objects can implement, we can write functions that work with any object that implements that interface. easily swap out different objects: This allows us to easily swap out different objects that have the same behavior, without having to rewrite our functions. Example func copySourceToDest(source io.Reader, dest…

  21. What is interface pollution?

    Interface pollution is about overwhelming our code with unnecessary abstractions, making it harder to understand.

  22. What is an example of using interfaces to create a higher-level abstraction?

    An example of using interfaces to create a higher-level abstraction is the io.ReadWriter interface. This interface combines the io.Reader and io.Writer interfaces to create a new interface that can be implemented by objects that can both read and write data. Example type ReadWriter interface { Reader Writer }

  23. What is the benefit of using interfaces in unit testing?

    Easier testing: Using interfaces in unit testing allows for easier testing and better code coverage. Create mock objects: By creating mock objects that implement the same interface as the real objects, we can test our code without having to set up complex dependencies. Example func TestCopySourceToDest(t *testing.T) { const input = "foo" source := strings.NewReader(input) dest := bytes.NewBuffer(make([]byte, 0)) err := copySourceToDest(source, dest) if err != nil { t.FailNow() } got := dest.Str…

  24. When should you use an interface in Go? List 3 cases

    Common behavior Decoupling Restricting behavior

  25. Give me an example of common behavior in using an interface

    Define a common behavior that can be shared among different types or structs. One example is when you want to sort a collection based on common behavior. Example type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) } Highly reusable Add `IsSorted` utility function func IsSorted(data Interface) bool { n := data.Len() for i := n - 1; i > 0; i-- { if data.Less(i, i-1) { return false } } return true }

  26. What is the Liskov substitution principle (LSP in SOLID) and how does using an interface help with it?

    It helps us rely on an abstraction instead of a concrete implementation, the implementation itself can be replaced with another without even having to change our code.

  27. What is the benefit of using an interface to decouple the code? (1 answer)

    unit testing: mock data

  28. Refactoring the service code below that relies on a concrete implementation type CustomerService struct { store mysql.Store } func (cs CustomerService) CreateNewCustomer(id string) error { customer := Customer{id: id} return cs.store.StoreCustomer(customer) } // Creates a storage abstraction type customerStorer interface { StoreCustomer(Customer) error } // Decouples CustomerService from the actual implementation type CustomerService struct { storer customerStorer } func (cs CustomerService) Cr…

    we can: Use the concrete implementation via integration tests Use a mock (or any kind of [[test double]]) via unit tests Or both

  29. What're all types of test double and it's definitions?

    A test double is a generic term to refer to any object that stands in for a production one during testing Dummy Fake Stub Spy Mock

  30. Finish DummySearcher in test double type DummySearcher struct{} func (ds DummySearcher) Search(people []*Person, firstName, lastName string) *Person { return &Person{} } func TestFindReturnsError(t *testing.T) { phonebook := &Phonebook{} want := ErrMissingArgs _, got := phonebook.Find(DummySearcher{}, "", "") if got != want { t.Errorf("Want '%s', got '%s'", want, got) } }

    Dummy objects are passed around but never actually used. Usually, they are just used to fill parameter lists. package main import ( "errors" ) var ( ErrMissingArgs = errors.New("FirstName and LastName are mandatory arguments") ErrNoPersonFound = errors.New("No person found") ) type Searcher interface { Search(people []*Person, firstName string, lastName string) *Person } type Person struct { FirstName string LastName string Phone string } type Phonebook struct { People []*Person } func (p *Phon…

  31. Finish FakeSearcher in test double type FakeSearcher struct{} func (fs FakeSearcher) Search(people []*Person, firstName string, lastName string) *Person { if len(people) == 0 { return nil } return people[0] } func TestFindCallsSearchAndReturnsEmptyStringForNoPerson(t *testing.T) { phonebook := &Phonebook{} fake := &FakeSearcher{} phone, _ := phonebook.Find(fake, "Jane", "Doe") if phone != "" { t.Errorf("Wanted '', got '%s'", phone) } }

    Fake objects actually have working implementations, usually take some shortcut that makes them unsuitable for production (an InMemoryTestDatabase is a good example). package main import ( "errors" ) var ( ErrMissingArgs = errors.New("FirstName and LastName are mandatory arguments") ErrNoPersonFound = errors.New("No person found") ) type Searcher interface { Search(people []*Person, firstName string, lastName string) *Person } type Person struct { FirstName string LastName string Phone string }…

  32. Finish StubSearcher in test double type StubSearcher struct { phone string } func (ss StubSearcher) Search(people []*Person, firstName, lastName string) *Person { return &Person{ FirstName: firstName, LastName: lastName, Phone: ss.phone, } } func TestFindReturnsPerson(t *testing.T) { fakePhone := "+31 65 222 333" phonebook := &Phonebook{} phone, _ := phonebook.Find(StubSearcher{fakePhone}, "Jane", "Doe") if phone != fakePhone { t.Errorf("Want '%s', got '%s'", fakePhone, phone) }

    Stubs provide canned answers to calls made during the test usually not responding at all to anything outside what's programmed in for the test. How would we test that the Find method when supplied the firstName and lastName arguments will work as expected? When we want to make the Searcher implementation return an actual value that we can assert against, we need a stub. package main import ( "errors" ) var ( ErrMissingArgs = errors.New("FirstName and LastName are mandatory arguments") ErrNoPers…

  33. Finish SpySearcher in test double type SpySearcher struct { phone string searchWasCalled bool } func (ss *SpySearcher) Search(people []*Person, firstName, lastName string) *Person { ss.searchWasCalled = true return &Person{ FirstName: firstName, LastName: lastName, Phone: ss.phone, } } func TestFindCallsSearchAndReturnsPerson(t *testing.T) { fakePhone := "+31 65 222 333" phonebook := &Phonebook{} spy := &SpySearcher{phone: fakePhone} phone, _ := phonebook.Find(spy, "Jane", "Doe") if !spy.search…

    Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent. package main import ( "errors" ) var ( ErrMissingArgs = errors.New("FirstName and LastName are mandatory arguments") ErrNoPersonFound = errors.New("No person found") ) type Searcher interface { Search(people []*Person, firstName string, lastName string) *Person } type Person struct { FirstName string LastName string Phone string…

  34. Finish MockSearcher in test double type MockSearcher struct { phone string methodsToCall map[string]bool } func (ms *MockSearcher) Search(people []*Person, firstName, lastName string) *Person { ms.methodsToCall["Search"] = true return &Person{ FirstName: firstName, LastName: lastName, Phone: ms.phone, } } func (ms *MockSearcher) ExpectToCall(methodName string) { if ms.methodsToCall == nil { ms.methodsToCall = make(map[string]bool) } ms.methodsToCall[methodName] = false } func (ms *MockSearcher)…

    Mocks are pre-programmed with expectations that form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting. package main import ( "errors" ) var ( ErrMissingArgs = errors.New("FirstName and LastName are mandatory arguments") ErrNoPersonFound = errors.New("No person found") ) type Searcher interface { Search(people []*Person, firstName…

  35. How to rely on an interface (restricting behavior)? Finished the code below type Foo struct { threshold intConfigGetter } // Injects the configuration getter func NewFoo(threshold intConfigGetter) Foo { return Foo{threshold: threshold} } // Reads the configuration func (f Foo) Bar() { threshold := f.threshold.Get() // ... }

    type IntConfig struct { // ... } func (c *IntConfig) Get() int { // Retrieve configuration } func (c *IntConfig) Set(value int) { // Update configuration } type intConfigGetter interface { Get() int Set(value int) }

  36. What's restricting behavior in the interface?

    We use interfaces to restrict a type to a specific behavior for various reasons, such as semantics enforcement.

  37. > Abstractions should be discovered, not created. What does it mean?

    It means we shouldn’t start creating abstractions in our code if there is no immediate reason to do so.

  38. What's an interface on the producer side?

    An interface defined in the same package as the concrete implementation Sometimes used in the standard library (encoding create interfaces implemented by other subpackages such as encoding/json or encoding/binary)

  39. What's an interface on the consumer side?

    An interface defined in an external package where it’s used

  40. Which is better in Golang and why? A. Interface on the consumer side B. Interface on the producer side

    A. Interface on the consumer side is better in Golang The client creates an interface it needs with the method it wants `customersGetter` interface can remain unexported No circular dependencies: no dependency from store to client Follows the Interface-Segregation Principle in SOLID package client // unexport type customersGetter interface { // referencing the Customer struct from the external package GetAllCustomers() ([]store.Customer, error) } B. Interface on the producer side has short come…

  41. What is the Interface Segregation Principle (ISP) in SOLID principles?

    The Interface Segregation Principle (ISP) is one of the SOLID principles of object-oriented design. It states that clients should not be forced to depend on interfaces they do not use, and it is better to have several smaller, more specialized interfaces than one large, general-purpose interface.

  42. What're the issues if a function returns an interface instead of a struct?

    Restrict flexibility: force all the clients to use on a particular type of abstraction cyclic dependency There’s a dependency from the implementation package to the client package

  43. What're the rules for not returning interfaces? > *Be conservative in what you do, be liberal in what you accept from others.* > —Transmission Control Protocol

    Returning structs instead of interfaces Accepting interfaces if possible

  44. What is the empty interface in Go?

    In Go, an interface type that specifies zero methods is known as the empty interface, interface{}.

  45. What is the equivalent of interface{} in Go 1.18?

    With Go 1.18, interface{} equals to any.

  46. What is the any type?

    The any type in Go can hold any value type. For example: func main() { var i any i = 42 i = "foo" i = struct { s string }{ s: "bar", } i = f _ = i } func f() {}

  47. When to use any type in Golang?

    Developers should use any when there's a genuine need for accepting or returning any possible type, such as in marshaling or formatting The encoding/json package provides the Marshal function, which can marshal any type: func Marshal(v any) ([]byte, error) { // ... } The database/sql package also uses the any type for parameterizing queries: func (c *Conn) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) { // ... }

  48. Why not use `any`?

    Overgeneralization: It doesn't cover anything No safeguard: No safeguard at compile time Need time to understand the code before using the function

  49. What is the validation tag used in Go for conditional validation?

    The required_with and required_without validation tags are used for conditional validation in Go. required_with specifies that a field is required when another field is present required_without specifies that a field is required when another field is absent. For example, to make the Value field required when Interpretation is empty and vice versa, you could use the following validation tag: type BloodSugarRequest struct { ObservationBaseRequest Value *float64 `json:"value" validate:"omitempty,n…

  50. What is the purpose of adding generic to Go 1.18?

    To simplify code and increase type safety. With the addition of generic, developers no longer need to resort to reflection or code generation to handle generic types. This leads to less boilerplate code and better performance.

  51. What is the difference between using int and ~int as a constraint in a type parameter in Go?

    Using int as a constraint in a type parameter in Go restricts it to that type. ~int restricts all the types whose underlying type is an int. For example, a constraint where we would like to restrict a type to any int type implementing the String() string the method can be defined as: type customConstraint interface { ~int String() string } This allows any type whose underlying type is an int to be used as an argument, as long as it implements the String() string method.

  52. Give me a generic example of a function foo

    generic example func foo[T any](t T){ // ... } Supplying a type argument is called instantiation, and the work is done at compile time keeps type safety as part of the core language features and avoids run-time overhead.

  53. What're the issues in this function? // Accepts and returns any arguments func getKeys(m any) ([]any, error) { switch t := m.(type) { // Handles run-time errors if a type isn’t implemented yet default: return nil, fmt.Errorf("unknown type: %T", t) case map[string]int: var keys []any for k := range t { keys = append(keys, k) } return keys, nil case map[int]string: // Copy the extraction logic } }

    add a case requires duplicating the range loop function accept any which means we lose some of the benefits of Go as a typed language return a slice of any which increases the effort on the caller's side type check of the keys extra conversion

  54. In Go, can map keys be of any type?

    No, the map keys can’t be of any type. var m map[[]byte]int causes error This code leads to a compilation error: invalid map key type []byte. func main() { // Slices and arrays var m1 map[[]int]string // compilation error: invalid map key type []int m1 = make(map[[]int]string) var m2 map[[3]int]string // compilation error: invalid map key type [3]int m2 = make(map[[3]int]string) // Structs with unsupported fields type Person struct { Name string Age int IDs []int } var m3 map[Person]string // c…

  55. Can you refactor the function more generic? // Accepts and returns any arguments func getKeys(m any) ([]any, error) { switch t := m.(type) { // Handles run-time errors if a type isn’t implemented yet default: return nil, fmt.Errorf("unknown type: %T", t) case map[string]int: var keys []any for k := range t { keys = append(keys, k) } return keys, nil case map[int]string: // Copy the extraction logic } } // TODO: finish refactoring type customConstraint interface { ~int|~string } func getKeys[K c…

    define a customConstraint interface to restrict the types to be either int or string using the union operator | . K is now a customConstraint instead of a comparable as before.

  56. What is an example of a generic data structure in Go?

    An example of a generic data structure in Go is a linked list that can contain values of any type. Here's an example implementation: type Node[T any] struct { Val T next *Node[T] } func (n *Node[T]) Add(next *Node[T]) { n.next = next }

  57. Can type parameters be used with method arguments in Go?

    No, type parameters cannot be used with method arguments in Go. Here's an example that demonstrates this: type Foo struct {} func (Foo) bar[T any](t T) {} // Compilation error: methods cannot have type parameters In Go, type parameters can only be used with function arguments function receivers not with method arguments This means that you cannot define a method with a generic argument or receiver, only a function. If we want to use generics with methods, it’s the receiver that needs to be a ty…

  58. What are some common uses of generics in Go? package sort type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) }

    Data structures like binary trees linked lists heaps Functions that work with slices, maps, and channels of any type, like a function to merge two channels using the following syntax: func merge[T any](ch1, ch2 <-chan T) <-chan T { // ... } Factoring out behaviors instead of types, like the sort.Interface interface that contains three methods and can be implemented using a type parameter: type SliceFn[T any] struct { S []T Compare func(T, T) bool } func (s SliceFn[T]) Len() int { return len(s.S…

  59. What are some misuses of generics in Go?

    When calling a method of the type argument like in a function that receives an io.Writer and calls the Write method. In such cases, it is better to make the argument an io.Writer directly. func foo[T io.Writer](w T) { b := getBytes() _, _ = w.Write(b) } When using generics makes the code more complex

  60. How to print the type of value with fmt.Printf fmt.Printf("%T\n",3) // "int"

    stand for reflect.TypeOf

  61. var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty var w io.Writer // what's this expression? w = r.(io.Writer)

    type assertion

  62. var x float64 = 3.4 v := reflect.ValueOf(x) // get type fmt.Println("type:", v.Type()) // type: float64 // type constant fmt.Println("kind is float64:", v.Kind() == reflect.Float64) // kind is float64: true // grab value fmt.Println("value:", v.Float()) // value: 3.4

    Reflect types of Value

  63. package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) // type: float64 }

    Reflect types of Type

  64. What's a reflection in Golang?

    reflection is just a mechanism to examine the type and value pair stored inside an interface variable

  65. What method of the reflect.Value type can be used to recover an interface value, and what does it return? v := reflect.ValueOf(x) // TODO: recover the value into type float64 y := v.Interface().(float64) fmt.Println(y)

    The Interface() method of the reflect.Value type can be used to recover an interface value, which packs the type and value information back into an interface representation and returns the result as an interface{} .

  66. Will fmt the package automatically unpacks a reflect.Value? var x float64 = 3.4 v := reflect.ValueOf(x)

    Yes // unpack automatically fmt.Println(v) // 3.4 // floating-point format fmt.Printf("value is %7.1e\n", v.Interface()) // 3.4e+00

  67. Does the code below work? If not, what is the error that it throws? var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1)

    No, the code above does not work. It will throw a panic error: panic: reflect.Value.SetFloat using unaddressable value. This is because v is not settable, which means that we cannot modify its underlying value.

  68. What is settability in reflection?

    Settability is a property of a reflection object that determines whether it can modify the actual storage that was used to create the reflection object. A reflection object is settable if it holds the original item, and it is not settable if it holds a copy of the original item.

  69. How can we check the settability of a Value using reflection? var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet()) // Prints: settability of v: false

  70. How can we modify a value using reflection in Go? var x float64 = 3.4 // Note: take the address of x. p := reflect.ValueOf(&x) // indirects through the pointer v := p.Elem() // Check settability fmt.Println("settability of v:", v.CanSet()) // Set value in reflection v.SetFloat(7.1) fmt.Println(v.Interface()) // Prints: 7.1 fmt.Println(x) // Prints: 7.1

    passing a pointer to the original item

  71. What are the laws of reflection? Reflection goes from interface value to reflection object. Reflection goes from reflection object to interface value. To modify a reflection object, the value must be settable.

  72. Modify Structs fields with reflection type T struct { // exported fields of a struct are settable. A int B string } t := T{23, "skidoo"} // create the reflection object with the address of the struct s := reflect.ValueOf(&t).Elem() // set typeOfT to its type typeOfT := s.Type() // Method1: iterate over the fields using straightforward method calls for i := 0; i < s.NumField(); i++ { f := s.Field(i) // 0: A int = 23 fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()…

  73. What is wrong with embedding a mutex in a struct?

    type InMem struct { sync.Mutex m map[string]int } func New() *InMem { return &InMem{m: make(map[string]int)} } When a mutex is embedded in a struct, its Lock and Unlock methods will be promoted and become visible to external clients using the struct, which is bad for encapsulation. m := inmem.New() m.Lock() // ?? A mutex is something that we want to encapsulate within a struct and make invisible to external clients. A better way to do this is to create a sync.Mutex field in the struct and use i…

  74. What is the right way to use type embedding for convenience?

    Type embedding is mainly used for convenience to promote behaviors. A good example of this is a custom logger that contains an io.WriteCloser field. By embedding io.WriteCloser in the Logger struct, we can avoid implementing additional methods to forward a call and make the Logger struct satisfy the io.WriteCloser interface. type Logger struct { // Makes io.Writer embedded io.WriteCloser } func main() { l := Logger{WriteCloser: os.Stdout} // call .Write() directly _, _ = l.Write([]byte("foo"))…

  75. What is the code snippet for implementing a struct that holds in-memory data with a mutex? type InMem struct { mu sync.Mutex m map[string]int } func New() *InMem { return &InMem{m: make(map[string]int)} } func (i *InMem) Get(key string) (int, bool) { i.mu.Lock() v, contains := i.m[key] i.mu.Unlock() return v, contains }

  76. What is the code snippet for implementing a custom logger with an embedded io.WriteCloser? type Logger struct { io.WriteCloser } func main() { l := Logger{WriteCloser: os.Stdout} _, _ = l.Write([]byte("foo")) _ = l.Close() }

  77. Front: What is the difference between embedding and OOP subclassing?

    Embedding is about composition, not inheritance. With embedding, the receiver of the method remains the embedded struct, while with OOP subclassing, the receiver of the method becomes the subclass.

  78. What are the two main constraints to use type embedding?

    The two main constraints to use type embedding are It shouldn’t be used solely as some syntactic sugar to simplify accessing a field It shouldn’t promote data (fields) or a behavior (methods) we want to hide from the outside.

  79. What is the downside of using a config struct to add options to a struct? type Config struct { Port *int } func NewServer(addr string, cfg Config) { }

    Clients have to create a variable and then pass a pointer port := 0 config := httplib.Config{ Port: &port, } client using our library with the default configuration will need to pass an empty struct this way httplib.NewServer("localhost", httplib.Config{})

  80. What is the Builder pattern?

    the builder pattern provides a flexible solution to various object-creation problems // Config struct type Config struct { Port int } // Config builder struct, containing an optional port type ConfigBuilder struct { port *int } func (b *ConfigBuilder) Port( // Public method to set up the port port int) *ConfigBuilder { b.port = &port // return builder tfor use method chaining(builder.Foo("foo").Bar("bar")) return b } // Build method to create the config struct func (b *ConfigBuilder) Build() (C…

  81. What is the downside of using the Builder pattern to add functional options

    error handling becomes more complex. If we want to keep the ability to chain the calls, the function can't return an error.

  82. What is the main idea behind the functional options pattern? An unexported struct holds the configuration: options Each option is a function that returns the same type: type Option func(options *options) error.

  83. Finished the functional options pattern below // Configuration struct type options struct { port *int } // Represents a function type that updates the configuration struct type Option func(options *options) error // A configuration function that updates the port func WithPort(port int) Option { // return a closure return func(options *options) error { if port < 0 { return errors.New("port should be positive") } options.port = &port return nil } }

    WithPort returns a closure Each config field requires creating a public function (that starts with the With prefix by convention) containing similar logic: validating inputs if needed and updating the config struct.

  84. provider side: `NewServer` implementation for functional options pattern // Accepts variadic Option arguments func NewServer(addr string, opts ...Option) ( *http.Server, error) { // Creates an empty options struct var options options // Iterates over all the input options for _, opt := range opts { // Calls each option, which results in modifying the common options struct err := opt(&options) if err != nil { return nil, err } } // At this stage, the options struct is built and contains the conf…

    this pattern (functional options pattern) is used in different Go libraries such as gRPC.

  85. What's closure? A closure is an anonymous function that references variables from outside its body

  86. What is a best practice in package organization? Avoiding premature packaging and overcomplicating a project Avoiding having dozens of nano packages or huge packages that dilute the meaning of a package name Considering package naming with care, using short, concise, and expressive names that conventionally have a single lowercase word Minimizing what should be exported to reduce coupling between packages and keeping unnecessary elements hidden

  87. What is package collision in Go and how to solve it?

    Package collision occurs when a variable name collides with an existing package name. To solve it, we can: Use a different variable name. redisClient := redis.NewClient() v, err := redisClient.Get("foo") Use an alias to change the qualifier to reference the redis package. This approach should be avoided. import redisapi "mylib/redis" // ... redis := redisapi.NewClient() v, err := redis.Get("foo")

  88. What is the bad practice of creating shared packages such as utils, common, and base?

    The naming is meaningless

  89. What is the solution for the util package problem?

    The solution for the util package problem is to create an expressive package name that reflects the purpose and responsibilities of the package. a specific type can be created and methods can be exposed to avoid having multiple references to the package.

  90. How to improve util package? package package stringset type Set map[string]struct{} func New(...string) Set { ... } func (s Set) Sort() []string { ... } client use set := stringset.New("c", "a", "b") fmt.Println(set.Sort())

    package package util func NewStringSet(...string) map[string]struct{} { // ... } func SortStringSet(map[string]struct{}) []string { // ... } client use set := util.NewStringSet("c", "a", "b") fmt.Println(util.SortStringSet(set))

  91. What's the main idea of documentation?

    every exported element must be documented

  92. What is the convention for adding comments in Go code?

    Starting with the name of the exported element. Each comment should be a complete sentence that ends with punctuation. Document a function or a method by highlighting what it intends to do, not how it does it. To deprecate an exported element, use the // Deprecated: comment followed by a description of why it is deprecated. // Customer is a customer representation. type Customer struct{} // ID returns the customer identifier. func (c Customer) ID() string { return "" } // ComputePath returns th…

  93. What is the convention for documenting an exported constant in Go?

    The convention is to add a comment alongside the constant that describes its purpose and actual content. Here's an example: // DefaultPermission is the default permission used by the store engine. const DefaultPermission = 0o644 // Need read and write accesses.

  94. What is the convention for documenting a package in Go?

    The convention is to start the comment with // Package followed by the package name. The comment should convey basic information about the package. Here's an example: // Package math provides basic constants and mathematical functions. // // This package does not guarantee bit-identical results // across architectures. package math

  95. Should comments that are not adjacent to a declaration be included in the documentation?

    No, comments that are not adjacent to a declaration are omitted from the documentation. Copyright will not show in produced documentation. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package math provides basic constants and mathematical functions. // // This package does not guarantee bit-identical results // across architectures. package math

  96. What is the output of the following code in Go? sum := 100 + 010 fmt.Println(sum)

    The code will print 108 instead of the expected 110. In Go, an integer literal starting with a 0 is considered an octal integer (base 8). 010 is interpreted as the octal number 10, which is equivalent to the decimal number 8. 100 + 010 equals 100 + 8, resulting in 108.

  97. When using os.OpenFile in Go, what is the recommended practice for specifying Linux permissions?

    Using 0o644 instead of just 0644 helps improve code readability. The recommended practice is to use octal literals for Linux permissions. Instead of using a base 10 number, it is considered good practice to pass an octal number for readability. For example: file, err := os.OpenFile("foo", os.O_RDONLY, 0644) Passing 0644 as an octal number denotes the same permissions as its base 10 equivalent. However, to make the code clearer, you can also use the prefix 0o followed by the octal digits: file,…

  98. What are the different representations of integer literals in Go?

    In Go, integer literals can be represented in different bases: Binary: Uses a 0b or 0B prefix (e.g., 0b100 is equal to 4 in base 10). Hexadecimal: Uses an 0x or 0X prefix (e.g., 0xF is equal to 15 in base 10). Imaginary: Uses an i suffix (e.g., 3i). These representations allow you to express integer values in binary, hexadecimal, or imaginary form.

  99. How can you use the underscore character to improve readability in numeric literals?

    In Go, the underscore character _ can be used as a separator in numeric literals to enhance readability. For example: 1 billion can be represented as 1_000_000_000. Other representations, such as binary (0b00_00_01), can also use the underscore character as a separator for better readability.

  100. What are the signed and unsigned integer types in Go and their respective sizes?

    In Go, the signed integer types are: int8 (8 bits) int16 (16 bits) int32 (32 bits) int64 (64 bits) The unsigned integer types are: uint8 (8 bits) uint16 (16 bits) uint32 (32 bits) uint64 (64 bits) The size of these types depends on the system architecture, with 32 bits on 32-bit systems and 64 bits on 64-bit systems.