هذه هي المقالة الثالثة في سلسلة "الصحة النفسية" الجزء السادس: الكود: وظائف وممارسة الأخطاء في Go: من الفوضى إلى الشفافية [جزء 1] الكود في النهاية (جزء 2): الهياكل والطرق والتكوين على التراث الرئيسية » Interfaces - Go's Secret Weapon لقد شاهدت فريقين إنشاء متصفحات 20 طرق التي أصبحت لا يمكن اختبارها أو إبلاغها أو الحفاظ عليها. ثم يلجأون إلى السؤال لماذا تشعر Go بقلق. "تقبيل متصفحات، إعادة هيكلات" - إذا سمعت فقط عن واحد متصفحات Go، فمن المحتمل أن يكون هذا. ولكن لماذا مهمة جدا؟ ولماذا متصفحات طريقة واحدة هي النمط في Go وليس استثناء؟ أخطاء التصفح الشائعة التي تواجهها: - مصطلحات مع 10 + طرق: ~45٪ من الكود Enterprise Go - تحديد المواقع في موقع التطوير: ~70٪ من البطاقات - إعادة العلاقات بدلاً من أنواع الحديد: ~55٪ من الوظائف - استخدام الحواسيب الخلفية في كل مكان: ~30% من API - nil interface vs nil pointer confusion: ~25% من البرامج الجميلة بعد 8 سنوات من العمل مع Go وتعديل العديد من القضايا ذات الصلة بالاتصال ، يمكنني أن أقول: استخدام الاتصال الصحيح هو الفرق بين الكود الذي يقاتل لغة وكود الذي يتدفق مثل الماء. التحديث: Duck Typing for Adults في Go ، يلجأ نوع إلى رابطة تلقائيًا ، دون بيان واضح: // 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 الحوافز الصغيرة: قوة البساطة قاعدة الطريقة الوحيدة انظر إلى المكتبة القياسية لـ 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 } طريقة واحدة - رابط واحد. لماذا؟ // 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 مبادئ التفرقة في العمل // 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! موافقة الأندرويد، استرداد الأندرويد لماذا يهم هذا // 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 } مثال عملي // 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 } رابط المكونات تثبيت الأندرويد // 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 } تقييمات نوعية و تقييمات نوعية // 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) } } نيل إنترنت: 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 } التحقق من نيل // 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 } أمثلة حقيقية من المكتبة القياسية io.Reader/Writer - أساس كل شيء // 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!") }) نموذجين و ضد نموذج النموذج: تطبيقات الحواسيب المعيارية // 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: Interfaces متعددة الجوانب // 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() } أدوات عمليّة تحديد المواقع على الجانب المستهلك، وليس التطبيق ترغب في الحواسيب الصغيرة إلى أكبر استخدامه لتصميم interface composition لا تترك المواقع دون الحاجة تداول الفوركس NIL POINTER استخدم أنواع التأكيدات بعناية الوساطة هي الطريقة الأخيرة، وليس الأولى رابط التحقق - الاتصال لديه 1-3 طرق أعلى - رابط محدد بالقرب من الاستخدام الوظائف تستند إلى الحواسيب وليس إلى أنواع معينة - العناوين يعودون إلى أنواع الحشيش، وليس الحواسيب - لا طرق غير مستخدمة في الاتصال - تقييمات نوعية تتعامل مع كلا الحالاتين (Ok/not ok) - تستخدم interface{} فقط عند الضرورة النتيجة المرابطات هي الملح الذي يحافظ على برامج Go معًا.إنها تتيح رمزًا مرنًا، قابلًا للتحقق، ومتواصلًا، دون تركيزات التراث المعقدة.انتهى: في Go، تكون المرابطات ملموسة، صغيرة، ومتوافرة. في المقال التالي، سنناقش البطاقات والالتزامات: كيفية تنظيم الكود بحيث يتم تصنيف التصدير بصفة مستوحاة وأيضًا يتم تصنيف التزامات بصفة فردية. ما هو اتجاهك في تصميم الشبكة؟ كم صغيرة هي صغيرة جدا؟ كيف تقرر عند إنشاء الشبكة مقابل استخدام نوع الحديد؟ مشاركة تجربتك في التعليقات!