Hii ni makala ya tatu katika mfululizo wa "Clean Code in Go". Kodi safi: kazi na usindikaji wa makosa katika Go: Kutoka Chaos kwa Ufafanuzi [Sehemu ya 1] Kodi safi katika hatua (Sehemu ya 2): Miundo, mbinu, na muundo juu ya urithi Maelezo ya mchezo Interfaces - Silaha ya siri ya Go Nimeona timu kujenga interfaces ya mbinu ya 20 ambayo inakuwa haiwezekani kujaribu, kuchanganya, au kudumisha. Kisha wanajiuliza kwa nini Go inaonekana mbaya. "Kubali interfaces, kurudi miundo" - ikiwa umesikia tu idiom moja ya Go, pengine ni hii. Lakini kwa nini ni muhimu? Makosa ya kawaida ya interface niliyopata: - Interfaces na mbinu za 10 +: ~45% ya Enterprise Go code - Kufafanua interfaces kwenye tovuti ya utekelezaji: ~70% ya mipango - Kurejesha interfaces badala ya aina za beton: ~55% ya kazi - Kutumia interface tupu kila mahali: ~30% ya API - nil interface vs nil pointer confusion: ~25% ya bugs subtle Baada ya miaka 8 ya kufanya kazi na Go na kurekebisha matatizo mengi yanayohusiana na interface, ninaweza kusema: matumizi sahihi ya interfaces ni tofauti kati ya msimbo ambao unapigana na lugha na msimbo unaoishi kama maji. Ufanisi wa interface: Duck kuandika kwa watu wazima Katika Go, aina inashughulikia interface moja kwa moja, bila taarifa ya wazi: // Interface defines a contract type Writer interface { Write([]byte) (int, error) } // File satisfies Writer automatically type File struct { path string } func (f *File) Write(data []byte) (int, error) { // implementation return len(data), nil } // Buffer also satisfies Writer type Buffer struct { data []byte } func (b *Buffer) Write(data []byte) (int, error) { b.data = append(b.data, data...) return len(data), nil } // Function accepts interface func SaveLog(w Writer, message string) error { _, err := w.Write([]byte(message)) return err } // Usage - works with any Writer file := &File{path: "/var/log/app.log"} buffer := &Buffer{} SaveLog(file, "Writing to file") // OK SaveLog(buffer, "Writing to buffer") // OK Miundombinu ndogo: nguvu ya urahisi Kanuni ya Njia Moja Angalia orodha ya vitabu vya Go: type Reader interface { Read([]byte) (int, error) } type Writer interface { Write([]byte) (int, error) } type Closer interface { Close() error } type Stringer interface { String() string } Mfumo mmoja - interface moja. kwa nini? // BAD: large interface type Storage interface { Save(key string, data []byte) error Load(key string) ([]byte, error) Delete(key string) error List(prefix string) ([]string, error) Exists(key string) bool Size(key string) (int64, error) LastModified(key string) (time.Time, error) } // Problem: what if you only need Save/Load? // You'll have to implement ALL methods! // GOOD: small interfaces type Reader interface { Read(key string) ([]byte, error) } type Writer interface { Write(key string, data []byte) error } type Deleter interface { Delete(key string) error } // Interface composition type ReadWriter interface { Reader Writer } type Storage interface { ReadWriter Deleter } // Now functions can require only what they need func BackupData(r Reader, keys []string) error { for _, key := range keys { data, err := r.Read(key) if err != nil { return fmt.Errorf("read %s: %w", key, err) } // backup process } return nil } // Function requires minimum - only Reader, not entire Storage Kuanzisha mlinzi juu ya msalaba wa asili katika hatua // Instead of one monstrous interface type HTTPClient interface { Get(url string) (*Response, error) Post(url string, body []byte) (*Response, error) Put(url string, body []byte) (*Response, error) Delete(url string) (*Response, error) Head(url string) (*Response, error) Options(url string) (*Response, error) Patch(url string, body []byte) (*Response, error) } // Create focused interfaces type Getter interface { Get(url string) (*Response, error) } type Poster interface { Post(url string, body []byte) (*Response, error) } // Function requires only what it uses func FetchUser(g Getter, userID string) (*User, error) { resp, err := g.Get("/users/" + userID) if err != nil { return nil, fmt.Errorf("fetch user %s: %w", userID, err) } // parse response return parseUser(resp) } // Testing becomes easier type mockGetter struct { response *Response err error } func (m mockGetter) Get(url string) (*Response, error) { return m.response, m.err } // Only need to mock one method, not entire HTTPClient! Kuanzisha mlinzi juu ya msalaba, kurudi nyuma Kwa nini hii ni muhimu // BAD: returning interface func NewLogger() Logger { // Logger is interface return &FileLogger{ file: os.Stdout, } } // Problems: // 1. Hides actual type // 2. Loses access to type-specific methods // 3. Complicates debugging // GOOD: return concrete type func NewLogger() *FileLogger { // concrete type return &FileLogger{ file: os.Stdout, } } // But ACCEPT interface func ProcessData(logger Logger, data []byte) error { logger.Log("Processing started") // processing logger.Log("Processing completed") return nil } Mfano wa vitendo // Repository returns concrete types type UserRepository struct { db *sql.DB } func NewUserRepository(db *sql.DB) *UserRepository { return &UserRepository{db: db} } func (r *UserRepository) FindByID(id string) (*User, error) { // SQL query return &User{}, nil } func (r *UserRepository) Save(user *User) error { // SQL query return nil } // Service accepts interfaces type UserFinder interface { FindByID(id string) (*User, error) } type UserSaver interface { Save(user *User) error } type UserService struct { finder UserFinder saver UserSaver } func NewUserService(finder UserFinder, saver UserSaver) *UserService { return &UserService{ finder: finder, saver: saver, } } // Easy to test - can substitute mocks type mockFinder struct { user *User err error } func (m mockFinder) FindByID(id string) (*User, error) { return m.user, m.err } func TestUserService(t *testing.T) { mock := mockFinder{ user: &User{Name: "Test"}, } service := NewUserService(mock, nil) // test with mock } Muundo wa interface Upatikanaji wa Interface // Base interfaces type Reader interface { Read([]byte) (int, error) } type Writer interface { Write([]byte) (int, error) } type Closer interface { Close() error } // Composition through embedding type ReadWriter interface { Reader Writer } type ReadWriteCloser interface { Reader Writer Closer } // Or more explicitly type ReadWriteCloser interface { Read([]byte) (int, error) Write([]byte) (int, error) Close() error } Maelezo ya aina na aina ya switches // Type assertion - check concrete type func ProcessWriter(w io.Writer) { // Check if Writer also supports Closer if closer, ok := w.(io.Closer); ok { defer closer.Close() } // Check for buffering if buffered, ok := w.(*bufio.Writer); ok { defer buffered.Flush() } w.Write([]byte("data")) } // Type switch - handle different types func Describe(i interface{}) string { switch v := i.(type) { case string: return fmt.Sprintf("String of length %d", len(v)) case int: return fmt.Sprintf("Integer: %d", v) case fmt.Stringer: return fmt.Sprintf("Stringer: %s", v.String()) case error: return fmt.Sprintf("Error: %v", v) default: return fmt.Sprintf("Unknown type: %T", v) } } Maonyesho ya mchezo: The Gotchas // WARNING: classic mistake type MyError struct { msg string } func (e *MyError) Error() string { return e.msg } func doSomething() error { var err *MyError = nil // some logic return err // RETURNING nil pointer } func main() { err := doSomething() if err != nil { // TRUE! nil pointer != nil interface fmt.Println("Got error:", err) } } // CORRECT: explicitly return nil func doSomething() error { var err *MyError = nil // some logic if err == nil { return nil // return nil interface } return err } Kuangalia kwa nil // Safe nil check for interface func IsNil(i interface{}) bool { if i == nil { return true } // Check if value inside interface is nil value := reflect.ValueOf(i) switch value.Kind() { case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan, reflect.Func: return value.IsNil() } return false } Mifano halisi kutoka kwenye maktaba ya kawaida ya.Reader / Writer - msingi wa kila kitu // Copy between any Reader and Writer func Copy(dst io.Writer, src io.Reader) (int64, error) // Works with files file1, _ := os.Open("input.txt") file2, _ := os.Create("output.txt") io.Copy(file2, file1) // Works with network conn, _ := net.Dial("tcp", "example.com:80") io.Copy(conn, strings.NewReader("GET / HTTP/1.0\r\n\r\n")) // Works with buffers var buf bytes.Buffer io.Copy(&buf, file1) http.Handler — Web Server in One Method type Handler interface { ServeHTTP(ResponseWriter, *Request) } // Any type with ServeHTTP method can be a handler type MyAPI struct { db Database } func (api MyAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/users": api.handleUsers(w, r) case "/posts": api.handlePosts(w, r) default: http.NotFound(w, r) } } // HandlerFunc - adapter for regular functions type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) // call the function } // Now regular function can be a handler! http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") }) Mipango na Mipango ya Anti-Pattern Mfano: Utekelezaji wa interface ya kudumu // Optional interfaces for extending functionality type Optimizer interface { Optimize() error } func ProcessData(w io.Writer, data []byte) error { // Basic functionality if _, err := w.Write(data); err != nil { return err } // Optional optimization if optimizer, ok := w.(Optimizer); ok { return optimizer.Optimize() } return nil } Anti-Pattern: viungo vya kawaida vya kutosha // BAD: interface{} everywhere func Process(data interface{}) interface{} { // type assertions everywhere switch v := data.(type) { case string: return len(v) case []byte: return len(v) default: return nil } } // GOOD: specific interfaces type Sized interface { Size() int } func Process(s Sized) int { return s.Size() } Tips ya Kazi Kufafanua interface kwa upande wa watumiaji, sio utekelezaji Wanapenda viungo vidogo kwa viungo vingi Matumizi ya kuingizwa kwa interface ya usanidi Usijaribu kurejesha interfaces bila ya lazima Kumbuka interface ya nil vs pointer ya nil Tumia aina ya madai kwa makini mstari wa mwisho, sio wa kwanza. Sehemu ya Checklist - Interface ina 1-3 mbinu ya juu Upatikanaji wa interface karibu na matumizi - Functions kukubali interfaces, si aina za concrete - Functions kurudi aina ya concrete, si interface - Hakuna mbinu zisizotumika katika interface - Ufafanuzi wa aina huchukua kesi zote mbili (ok / sio ok) - interface{} kutumika tu ambapo inahitajika Mwisho wa Interfaces ni adhesive ambayo inahifadhi mipango ya Go pamoja. Wamewezesha msimbo wa kisasa, unaoweza kujaribiwa, na unaoweza kudumisha bila maagizo magumu ya urithi. Kumbuka: Katika Go, interfaces ni ya kimsingi, ndogo, na inaweza kuunganishwa. Katika makala inayofuata, tutashughulikia pakiti na mahusiano: jinsi ya kusanidi msimbo ili chati ya kuagiza iwezekanavyo na mahusiano ni ya moja kwa moja. Nini unachukua juu ya kubuni interface? Jinsi ndogo ni ndogo sana? Jinsi ya kuamua wakati wa kuunda interface vs kutumia aina ya beton? Share uzoefu wako katika maoni!