Trừu tượng hoá quá sớm là khi bạn dựng một khung tổng quát cho những trường hợp tương lai chưa hề xuất hiện, khiến mã phức tạp lên mà chẳng đổi lại được gì. Nguyên tắc an toàn là chờ tới khi thấy cùng một khuôn mẫu lặp lại vài lần rồi mới gộp, vì lúc đó bạn đã biết thật sự cái gì giống nhau, cái gì khác nhau.
Chào bạn, đây là Nhà Lập Trình. Có một giai đoạn mà hầu như người lập trình nào cũng đi qua: sau vài năm, bạn học được những mẫu thiết kế, những cách tổ chức mã tinh vi, và bạn muốn dùng chúng ở mọi nơi. Mã của bạn trở nên rất thông minh, và rất khó đọc. Tập này là về giai đoạn đó.
Trừu tượng hoá là gì, nói bằng lời thường
Trừu tượng hoá là gom những thứ giống nhau lại thành một khuôn chung, để không phải viết lại nhiều lần. Bạn có ba chỗ gửi thư, mỗi chỗ viết lại cách kết nối máy chủ thư. Gom lại thành một hàm gửi thư dùng chung là một bước trừu tượng hoá tốt.
Làm đúng, trừu tượng hoá giúp mã ngắn hơn, sửa một chỗ là đúng mọi nơi, và người đọc chỉ cần hiểu khuôn chung một lần.
Nhưng trừu tượng hoá có giá. Mỗi khuôn chung là một tầng người đọc phải hiểu. Mỗi tham số tuỳ chọn để khuôn chung phục vụ nhiều trường hợp là một nhánh nữa trong đầu. Khuôn chung càng tổng quát, nó càng xa với trường hợp cụ thể mà người đọc đang quan tâm.
Vấn đề không phải là trừu tượng hoá, mà là trừu tượng hoá khi chưa có đủ thông tin để biết khuôn chung nên có hình dạng gì.
Cái bẫy của câu biết đâu sau này
Kịch bản quen thuộc: bạn làm chức năng xuất báo cáo ra bảng tính. Và bạn nghĩ, biết đâu sau này cần xuất ra định dạng khác. Thế là thay vì viết thẳng, bạn dựng một khung có lớp giao diện chung, có bộ chọn định dạng, có cơ chế đăng ký định dạng mới.
Mất gấp ba thời gian. Mã dài gấp năm. Người đọc sau phải đi qua ba tầng để hiểu một việc đơn giản là xuất bảng tính. Và hai năm sau, vẫn chỉ có đúng một định dạng.
Tệ hơn, khi yêu cầu thứ hai thật sự đến, nó thường không giống điều bạn đoán. Không phải định dạng khác, mà là xuất theo lịch tự động, hoặc gửi qua thư. Cái khung bạn dựng không giúp gì, thậm chí cản đường vì nó được thiết kế cho một hướng khác.
Người trong nghề có một câu nhắc nhau rằng bạn sẽ không cần nó đâu. Nghĩa là đừng xây cho tương lai mà bạn chỉ đang đoán. Hãy xây cho hiện tại, và giữ mã đủ đơn giản để sửa được khi tương lai thật sự đến.
Chờ lặp lại vài lần rồi mới gộp
Một nguyên tắc thực tế nhiều người dùng: lần đầu cứ viết thẳng. Lần thứ hai gặp việc tương tự, chấp nhận chép lại và sửa cho hợp, dù hơi khó chịu. Lần thứ ba, lúc đó mới gộp.
Vì sao phải chờ tới lần thứ ba? Vì với một trường hợp, bạn không biết cái gì là chung, cái gì là riêng. Với hai trường hợp, bạn thấy hai điểm, nhưng hai điểm có thể giống nhau do tình cờ. Với ba trường hợp, khuôn mẫu thật bắt đầu hiện rõ.
Khi gộp ở lần thứ ba, khuôn chung của bạn được thiết kế từ ba ví dụ thật chứ không phải từ tưởng tượng. Nó vừa vặn hơn, ít tham số thừa hơn.
Con số ba không phải luật cứng. Có khi hai lần đã đủ rõ, có khi năm lần vẫn chưa nên gộp. Tinh thần là để thực tế dẫn đường, không để trí tưởng tượng dẫn đường.
Trùng lặp nhìn giống nhưng không phải là một
Có một dạng trừu tượng hoá sai rất hay gặp: gộp hai đoạn mã trông giống nhau nhưng thật ra phục vụ hai mục đích khác nhau. Ví dụ, cách tính phí giao hàng và cách tính phí đổi trả hiện tại có cùng công thức. Gộp lại thành một hàm cho gọn.
Rồi phòng kinh doanh đổi chính sách phí giao hàng. Bạn sửa hàm chung. Phí đổi trả cũng bị đổi theo, trong khi không ai muốn thế. Thế là bạn thêm một tham số để phân biệt, rồi thêm một điều kiện, rồi thêm nữa. Hàm chung thành một mớ điều kiện chồng chéo.
Hai đoạn mã giống nhau về hình thức nhưng khác nhau về lý do tồn tại thì không nên gộp. Câu hỏi đúng không phải là chúng có giống nhau không, mà là chúng có thay đổi cùng nhau vì cùng một lý do không.
Có câu nói trong nghề rằng trùng lặp rẻ hơn nhiều so với một khuôn chung sai. Trùng lặp thì dễ gộp sau này. Khuôn chung sai thì rất khó gỡ.
Đơn giản là dấu hiệu của tay nghề
Người mới viết mã đơn giản vì chưa biết cách viết phức tạp. Người vừa giỏi lên viết mã phức tạp vì vừa biết và muốn dùng. Người thật sự giỏi quay lại viết mã đơn giản, nhưng lần này là đơn giản có chủ đích.
Họ biết mọi mẫu thiết kế, nhưng chỉ dùng khi vấn đề thật sự cần. Họ viết mã mà người mới vào đội đọc là hiểu. Họ chấp nhận chút trùng lặp thay vì một tầng trừu tượng khó hiểu.
Khi xem lại mã của chính mình, hãy hỏi: nếu bỏ tầng này đi, mã có tệ hơn không? Nếu câu trả lời là không, hoặc chỉ tệ hơn một chút, hãy bỏ.
Một anh kỹ sư ở Huế kể năm thứ ba đi làm, anh viết một khung xử lý đơn hàng rất tinh vi và tự hào về nó. Năm thứ sáu, anh là người xoá nó đi và thay bằng một trăm dòng mã thẳng tuột. Anh bảo đó là lần anh thấy mình lớn lên nhiều nhất trong nghề.
Câu hỏi thường gặp
Trừu tượng hoá trong lập trình là gì?
Là gom những phần giống nhau thành một khuôn chung như hàm, lớp hay thành phần dùng lại được, để không phải viết lặp và để người đọc chỉ cần hiểu khuôn chung một lần.
Làm sao biết mình đang trừu tượng hoá quá sớm?
Khi bạn dựng khung tổng quát cho những trường hợp chưa có, khi phần lớn tham số tuỳ chọn chỉ có một giá trị được dùng, hoặc khi giải thích lý do tồn tại của một tầng phải dùng chữ biết đâu sau này.
Mã trùng lặp có phải lúc nào cũng xấu không?
Không. Trùng lặp một hai lần thường chấp nhận được và dễ gộp sau. Hai đoạn giống nhau nhưng thay đổi vì những lý do khác nhau thì không nên gộp.
Học mẫu thiết kế có còn cần thiết không?
Vẫn cần, vì chúng là từ vựng chung và lời giải cho những vấn đề thường gặp. Điều quan trọng là biết khi nào vấn đề thật sự cần chúng, thay vì dùng ở mọi nơi.
Làm sao gỡ một khuôn chung đã trừu tượng hoá sai?
Có thể chép lại phần mã chung vào từng nơi dùng, bỏ những nhánh không liên quan ở mỗi nơi, rồi xoá khuôn chung. Làm từng bước nhỏ và chạy thử sau mỗi bước.