Tạm biệt, clean code (2020)
(overreacted.io)- Thông qua trải nghiệm viết lại rồi phải hoàn tác phần mã resize của trình biên tập đồ họa với trọng tâm là loại bỏ trùng lặp, bài viết cho thấy việc ám ảnh với clean code có thể làm tổn hại đến khả năng thay đổi trong thực tế
- Triển khai cũ lặp lại các công thức theo từng handle của Rectangle, Oval, Header, TextBlock; triển khai mới cố gắng loại bỏ trùng lặp bằng cách tách hướng và hình dạng để tổ hợp lại
- Ngay sau khi refactor, kích thước mã giảm một nửa và các điểm chỉnh sửa cũng tập trung vào một chỗ, nhưng khi về sau cần các hành vi đặc thù theo từng hình dạng và từng handle, chính phần trừu tượng hóa lại có thể trở thành vật cản
- Vấn đề lớn hơn cả phán đoán kỹ thuật là việc check-in thẳng lên master mà không trao đổi với đồng nghiệp đã viết đoạn mã gốc, điều này có thể làm tổn hại đến việc xây dựng niềm tin trong nhóm
- “Sự sạch sẽ” không phải mục tiêu mà chỉ là công cụ để xử lý các hệ thống phức tạp; quan trọng hơn hình thức của mã là cả nhóm có thể cùng ứng phó với thay đổi hay không
Sự lặp lại trong mã resize của trình biên tập đồ họa
- Trên canvas của trình biên tập đồ họa có đoạn mã dùng các handle nhỏ ở mép để resize những hình như hình chữ nhật và hình elip
- Cách triển khai hoạt động được, nhưng các công thức tính vị trí và kích thước bị lặp lại theo từng hình và từng hướng của handle
Rectanglecó các phương thức nhưresizeTopLeft,resizeTopRight,resizeBottomLeft,resizeBottomRightOvalcó các phương thức nhưresizeLeft,resizeRight,resizeTop,resizeBottomHeadervàTextBlockcũng có các phương thức resize riêng
- Khi người dùng giữ Shift, còn phải xử lý giữ nguyên tỷ lệ trong lúc resize, nên số lượng công thức lại càng nhiều hơn
Trừu tượng hóa để loại bỏ trùng lặp
- Sự lặp lại dường như xuất hiện trên hai trục
- Lặp giữa các handle cùng hướng:
Oval.resizeLeft()vàHeader.resizeLeft()đều là thao tác kéo handle bên trái - Lặp giữa các phương thức trong cùng một hình: nhiều phương thức của
Ovalđều cùng xử lý hình elip
- Lặp giữa các handle cùng hướng:
- Cấu trúc mới cố gắng chia mã thành
DirectionsvàShapesđể tổ hợp hành vi theo hướng với hành vi theo hình dạng- Trong
Directionscótop,left,bottom,right - Trong
ShapescóOval,Rectangle - Dùng
createHandle,createBoxđể tổ hợp handle và hình dạng
- Trong
- Theo cách này, tạo ra các cấu hình handle như
fourCorners,fourSides,twoSides, rồi dùng chúng để tạoRectangle,Oval,Header,TextBlock
Ảo giác ngay sau khi refactor
- Sau khi refactor, tổng kích thước mã giảm một nửa và trông như thể đã hết trùng lặp
- Tác giả tin rằng khi muốn thay đổi hành vi của một hướng hoặc một hình dạng cụ thể thì chỉ cần sửa ở một chỗ thay vì nhiều phương thức
- Đã check-in thay đổi lên master vào đêm khuya, và nghĩ rằng mình đã gỡ rối gọn gàng phần mã lộn xộn của đồng nghiệp
Vấn đề lộ ra vào ngày hôm sau
- Ngày hôm sau, sếp yêu cầu hoàn tác thay đổi; lúc đó tác giả tin rằng mã mới sạch hơn mã cũ nên rất khó chấp nhận
- Sau này nhìn lại, quyết định đó là đúng vì hai lý do
- Đã viết lại và check-in mã mà không hề trao đổi với người đã viết phần mã gốc
- Đổi lấy việc giảm trùng lặp bằng cách hy sinh khả năng thích ứng với thay đổi yêu cầu
- Một đội ngũ kỹ thuật lành mạnh cần liên tục xây dựng niềm tin, và việc viết lại quy mô lớn mã của đồng nghiệp có thể giáng một đòn mạnh vào hợp tác
Cái giá của trừu tượng hóa sai
- Về sau, cần rất nhiều trường hợp đặc biệt và hành vi đặc thù cho nhiều handle của nhiều hình khác nhau
- Với cấu trúc trừu tượng mới, để đáp ứng các yêu cầu như vậy thì mã có thể trở nên phức tạp hơn gấp nhiều lần
- Ngược lại, cấu trúc “lộn xộn” ban đầu lại có các phương thức tách biệt nên tương đối dễ áp dụng các thay đổi kiểu này
- Loại bỏ trùng lặp trong mã không phải là miễn phí; khi có được một khả năng nào đó, bạn cũng có thể mất đi một khả năng khác
Nghĩ lại về “sự sạch sẽ”
- Giai đoạn ám ảnh với “clean code” và việc loại bỏ trùng lặp là điều nhiều lập trình viên có thể trải qua
- Khi thiếu tự tin với đoạn mã mình viết, rất dễ đặt niềm tự hào vào những thứ đo được như quy tắc lint nghiêm ngặt, quy tắc đặt tên, cấu trúc thư mục, hay không có trùng lặp
- Khi đã biết cách tạo trừu tượng hóa, bạn có thể bắt đầu tin rằng trừu tượng hóa là một đức tính và muốn áp dụng nó mỗi khi nhìn thấy mã lặp
- Cần suy nghĩ sâu hơn về việc cảm giác “sạch” hay “bẩn” thực sự gắn với kết quả kỹ thuật nào
- Không chỉ hình thức của mã, mà cách mã thay đổi và được chỉnh sửa cùng với những người trong nhóm cũng rất quan trọng
Khi cần buông bỏ clean code
- Clean code không phải mục tiêu, mà là một nỗ lực để hiểu các hệ thống phức tạp
- Khi bạn không chắc một thay đổi trong codebase sẽ ảnh hưởng thế nào, nó có thể trở thành một cơ chế phòng vệ giúp định hướng
- Ban đầu, trải nghiệm đơn giản hóa mã phức tạp bằng cách trích xuất hàm hoặc refactor class có thể rất thú vị
- Nhưng không nên dừng ở đó để rồi trở thành một “tín đồ clean code”
- Hãy để clean code làm người dẫn đường, nhưng đến một thời điểm nào đó bạn phải buông nó ra
1 bình luận
Các ý kiến trên Hacker News
Trùng lặp mã đôi khi là chấp nhận được, nhưng điều đó không chứng minh rằng clean code là xấu
Có vẻ việc refactor đã hơi quá tay; nếu chỉ tách “10 dòng toán học lặp lại” thành một hàm thì có lẽ đã gọn gàng hơn
Đồng đội không biến 10 dòng đó thành hàm cũng không hẳn là làm tốt, nên nếu là tôi thì tôi đã từ chối PR, nhưng sẽ không tự viết lại
Viết lại PR của người khác là một sự xúc phạm nhanh chóng, đóng lại mọi khả năng thảo luận, và nhiều khi ta cũng không biết bối cảnh vì sao mã được viết như vậy
Nếu không có chuẩn coding, PR nên được review và từ chối; nếu cứ để commit theo kiểu “clean code không quan trọng” thì chỉ vài tháng sau codebase sẽ thành nơi không ai muốn làm việc
Điều cần học ở đây không phải là clean code, mà là giao tiếp và sự tôn trọng
Nó cũng lấy mất cơ hội để tác giả ban đầu học được mã chất lượng thấp thực sự lãng phí thời gian như thế nào
Dù cách tiếp cận clean code có tốt hơn, điểm cốt lõi ở đây không phải là mã, mà là việc người đó đã lãng phí thời gian của mình mà không đưa ra phản hồi hữu ích
Cách tốt nhất lẽ ra là để tác giả ban đầu nâng tiêu chuẩn, còn bản thân thì làm những việc khác thực sự cần sửa. Nói cách khác, câu trả lời đúng là code review
Những người như vậy thường tạo thêm cả vấn đề tương lai trong lúc giải những thứ vốn không phải là vấn đề, thay vì giải quyết vấn đề thật
Ở lần triển khai đầu tiên, tốt hơn là không trừu tượng hóa một đoạn mã rất nhỏ cỡ 10 dòng như vậy. Mã chỉ làm đúng việc đó thì sau này dễ sửa, còn trừu tượng hóa 10 dòng vì một lợi ích chưa được kiểm chứng thì không mấy ý nghĩa
Clean Code hoàn toàn không quan trọng, và phần lớn các quy tắc hay “nguyên tắc” tương tự cũng vậy. Trái lại, đó là những thứ mà những người muốn biện minh cho việc dần dần dựng lên trong codebase một cấu trúc như tổ ong thường bám vào
Kết quả là rất dễ có mã bị trừu tượng hóa quá mức, khó thay đổi, chậm và khó hiểu
Nếu cần một nguyên tắc, chỉ cần xem việc chương trình thực sự làm — biến đổi dữ liệu — có được biểu đạt rõ ràng và dễ thay đổi hay không
Phần lớn các nguyên tắc SOLID không giúp, thậm chí còn cản trở, việc nhìn ra biến đổi dữ liệu là gì và diễn ra như thế nào, nên nhìn chung ít hữu ích để hiểu chương trình thực tế
Nhìn vào bản refactor được đề xuất, 10 dòng đó thực ra không giống hệt nhau mà chỉ tương tự nhau
Công thức để thay đổi kích thước góc trên bên trái của hình chữ nhật và công thức để thay đổi góc dưới bên phải của hình elip là khác nhau. Bề ngoài chúng tương tự, nhưng có chỗ dùng
-thay vì+Trong trường hợp này, độ phức tạp nằm ở bản thân hình học, và dù “trừu tượng hóa” nó thì cũng chỉ chuyển sự lộn xộn sang chỗ khác chứ không làm nó biến mất tận gốc
Có thể có một trừu tượng toán học thông minh nào đó như một hệ số đặc biệt, nhưng nếu ngôn ngữ không tích hợp sẵn thì chi phí triển khai các phép toán đặc biệt bằng số nguyên cơ bản nhiều khả năng sẽ lớn hơn lợi ích thu được
“Clean code” cần được tái định vị thương hiệu
Trong hơn 25 năm kinh nghiệm, tôi đã liên tục thấy sự phản đối không dứt đối với design pattern, trừu tượng hóa, loại bỏ trùng lặp, v.v.
Lý do lúc nào cũng là trừu tượng hóa làm mã phức tạp hơn, không có thời gian viết clean code, tất cả chỉ là sở thích và ý kiến cá nhân
Nhưng mục đích của clean code là làm cho mã đơn giản hơn và dễ bảo trì hơn khi yêu cầu thay đổi
Giá trị của phần mềm nằm ở việc nó có thể thay đổi theo thời gian. Nếu không phải vậy, ta đã dùng mạch cố định, vốn dễ và rẻ hơn nhiều để triển khai và bảo trì
Nếu việc refactor không đạt được mục tiêu này, và cấp trên có thể lập luận thuyết phục rằng nó khiến việc bảo trì khi yêu cầu tương lai thay đổi trở nên khó hơn, thì đoạn mã đó đã không “clean”
Việc không trao đổi với tác giả ban đầu là vấn đề quy trình và phép lịch sự, nằm ngoài phạm vi clean code. Hành động thô lỗ và độc đoán không làm giá trị của clean code biến mất
Cuốn sách nổi tiếng Clean Code nêu “không trùng lặp” như một điều kiện của hệ thống được thiết kế tốt, và xem trùng lặp là kẻ thù chính của công việc phát sinh, rủi ro phát sinh và độ phức tạp không cần thiết
Theo định nghĩa này, loại bỏ trùng lặp trở thành đồng nghĩa với đơn giản hóa, nhưng điều đó là sai
Loại bỏ trùng lặp là đưa vào một phụ thuộc. Nếu phụ thuộc đó mô hình hóa vấn đề tốt, nó có thể là một trừu tượng tốt và là sự đơn giản hóa; nếu không, nó chỉ là nén dưới lớp vỏ trừu tượng
Nhân tiện, trích dẫn này là phần nhắc đến Simple Design của Kent Beck trong Clean Code
Một là cảm nhận chủ quan rằng mã nào đó tốt, hai là tuân theo cuốn sách Clean Code và các hướng dẫn liên quan
Ngày càng nhiều người cảm thấy hai điều này khác nhau khá nhiều. Làm theo một số hướng dẫn trong sách đôi khi lại tạo ra mã hoàn toàn không sạch
Trong một thế giới lý tưởng, ai cũng muốn viết clean code, nhưng ngày càng ít người cảm thấy Clean Code là cách tốt để đạt mục tiêu đó
Đặc biệt, trừu tượng hóa quá mức và loại bỏ trùng lặp đã trở thành nguồn gốc của mã lộn xộn trong mắt nhiều người
Đôi khi technical debt được mã hóa ngay trong chính yêu cầu sản phẩm, nên không thể sửa bằng refactor hay best practice, mà chỉ có thể giảm bớt đau đớn
Mục đích của clean code và kết quả thực hành thực tế khác nhau rất xa. Lý do là con người nhìn chung không dự đoán được trạng thái tương lai của mã, dù yêu cầu đang ở ngay trước mắt
Clean code gọi ra câu hỏi kinh khủng “nếu như thì sao?”, và điều này dễ trở thành chiếc hộp Pandora khiến việc phát hành bị trì hoãn vô tận
Hầu như lúc nào cũng tốt hơn nếu lập trình theo những gì đã biết, và để người có kinh nghiệm miền nghiệp vụ dẫn dắt kiến trúc
Nhiều trừu tượng tốt đã nằm sẵn trong thư viện. Dĩ nhiên, đáng tiếc là không phải lúc nào cũng vậy
Trừu tượng tệ ngốn thời gian, và vì mã cũng là cách giao tiếp với những người khác trong nhóm, việc các thành viên tiếp nhận cách giao tiếp đó ra sao quan trọng hơn nhiều so với cách tôi suy nghĩ
Đồng nghiệp đã viết rất nhiều mã bằng cách sao chép-dán, và bạn đã refactor sau khi commit
Đồng nghiệp phàn nàn với sếp, sếp mắng bạn, và rốt cuộc bạn kết luận lần sau sẽ để sự lộn xộn lại trong codebase
Có bài học cần rút ra ở đây, nhưng điều đó không có nghĩa là sao chép-dán tốt hơn viết hàm
Việc refactor đáng lẽ nên được đưa lên thành một thay đổi riêng, tag tác giả ban đầu để họ review
Tuy nhiên, việc đồng nghiệp không nói chuyện trực tiếp mà đưa lên sếp, và sếp yêu cầu hoàn tác, là một tín hiệu khá xấu
Câu trả lời có thể là “Không, vì…” hoặc hiếm hơn là “Đúng nhỉ, mai tôi sẽ làm”
Không gây ồn ào, mọi người đều tránh được vấn đề, và có thể ai đó còn học được điều gì đó
Nhưng tác giả đã bổ sung một ngữ cảnh quan trọng. Họ đã đánh đổi khả năng yêu cầu thay đổi lấy việc giảm trùng lặp, và về sau mỗi handle của từng hình dạng lại cần nhiều trường hợp đặc biệt và hành vi riêng
Điều này cho thấy khác biệt giữa DRY ngẫu nhiên và DRY bản chất
Thư viện A và B thường trông nhất quán với nhau nên mã trở nên giống nhau, nhưng thực tế đó có thể chỉ là trùng hợp
Nếu wrapper bọc lib A và wrapper bọc lib B chứa cùng một đoạn mã nhưng lại bọc hai thứ hoàn toàn khác nhau, thì trừu tượng hóa chúng sẽ cho kết quả tệ
“15 cái” cũng vậy. Nếu “mọi danh sách luôn phải hiển thị 15 mục” thì trừu tượng hóa số 15 là đúng, nhưng nếu đó lần lượt là “top 15 bài hát trên trang chủ” và “mặc định 15 bình luận bên dưới bài hát”, thì gom hai số 15 đó lại là lựa chọn tệ hơn
Dấu hiệu cảnh báo thứ hai là bản thân việc refactor cũng không phải một refactor tốt. Nó thiếu tính mô-đun, vẫn bị ghép nối, và chỉ giảm được chút ít trùng lặp
Nó có thể nghe như “Mã cậu viết dở quá nên tôi viết lại rồi. Có ý kiến gì không?”
Cách tốt nhất có lẽ là yêu cầu giải thích dưới dạng “review muộn”, đề xuất giải pháp, và hy vọng tác giả ban đầu không phớt lờ
Điểm cốt lõi là nhận ra mã sẽ thay đổi trong tương lai. Yêu cầu sẽ thay đổi, và mã liên quan cũng sẽ thay đổi
Nếu tạo ra một trừu tượng hạn chế khả năng thay đổi đó, bạn sẽ làm tổn hại khả năng thích nghi codebase của chính mình trong tương lai
Đặc biệt với những trừu tượng đã tốn nhiều thời gian và công sức, ta dễ bám víu về mặt cảm xúc, rồi lãng phí thêm thời gian để nhồi nhét thay đổi vào một giải pháp tao nhã
KISS là đúng
Trường hợp này trông giống vấn đề “không ai thích người tỏ ra ta đây”
Điều này đặc biệt hay xảy ra khi một lập trình viên junior chưa học được rằng công việc không chỉ là chuyện giữa mình và máy tính, mà là giữa mình, máy tính và những người khác
Một phiên bản gọn gàng hơn có lẽ đã được chấp nhận nếu họ thật sự trò chuyện với tác giả ban đầu và thống nhất một cách tốt hơn
Tiêu đề bài viết có vẻ nên là đã học cách làm việc trong đội nhóm hơn là “Tạm biệt, Clean Code”
Mã phức tạp hơn có thể đã bị xem là mối đe dọa, và người viết có thể bị coi là đối thủ cần bị dập xuống qua chuỗi quản lý
Nếu là junior, cần hiểu rằng câu này phụ thuộc vào ngữ cảnh. Ở nhiều nơi làm việc, sự đơn giản quan trọng hơn tính linh hoạt, các tính năng ngôn ngữ gây rối bị tránh né, và kiểu làm việc ngồi lâu trước bàn phím cùng những ngôn ngữ như vậy được ưa chuộng
Ngược lại, triết lý coi trọng học tập suốt đời, software craftsmanship và tự nhìn lại quy trình thì xung đột hoàn toàn với môi trường đó
Những nỗ lực để trở nên tốt hơn không được lòng những người không phát triển
Cả hai đều là lựa chọn sống hợp lệ. Phần mềm vừa có thể là nơi thuận tiện để làm 7 tiếng rồi về nhà, vừa có thể là thế giới khám phá mà chỉ trí tưởng tượng và trí tuệ là giới hạn
Nếu muốn khám phá những điều mới, hãy cẩn trọng với việc dành hàng chục năm bên những người sợ cái chưa biết
Nói ngắn hơn, nếu incentive của bạn và đồng nghiệp bị lệch nhau, hãy cân nhắc rời đi
Bài viết này có lý do để được yêu thích lâu dài, và đã giúp nhiều lập trình viên đang rơi vào cùng cái bẫy mở mắt
Ngay cả khi làm việc một mình, bạn vẫn có thể thiết kế quá mức một trừu tượng gọn gàng rồi bị chính trừu tượng đó làm khó trong tương lai
Vấn đề nằm ở chỗ thay đổi được commit thẳng vào nhánh main mà không qua review
Mọi thay đổi mã nên có peer review, dù là pair programming hay PR truyền thống, để ngăn tình huống leo thang kiểu này
Sếp là một người sếp tệ. Thay vì trách mắng, đáng lẽ phải nhận ra rằng đang thiếu một quy trình tốt
Nếu là một đội có văn hóa code review, khi một người mới quá nhiệt tình thử làm vậy, đồng nghiệp đã có thể nói “cảm ơn, nhưng cái này không ổn” hoặc “ý hay đấy, thử làm theo cách này nhé”
Riêng chuyện khác, tôi nghĩ không nên refactor mã nếu không có lý do chính đáng. Dù là mã xấu, nó vẫn có thể là phần gần như không bao giờ cần sửa
Thời điểm tốt nhất để refactor là khi bạn cần thay đổi hành vi nhưng nhận ra cấu trúc hiện tại khiến việc đó quá khó
Tiếc là ở đây có vẻ họ đã rút ra bài học sai
Không hiểu sao mọi người chỉ nói về việc lẽ ra ông ấy nên làm gì thay vào đó. Đây là một bài học hoàn toàn chính đáng
Trong tài chính, ta liên tục xử lý các sản phẩm phần nào giống nhau nhưng không hoàn toàn giống nhau. Quyền chọn cổ phiếu và quyền chọn ngoại hối đều là quyền chọn, đều có giá thực hiện và được định giá bằng một phiên bản nào đó của Black-Scholes, nhưng trên thực tế chúng khác nhau cực kỳ nhiều
Người mới tham gia rất dễ bị cám dỗ muốn gom chung nhiều hành vi, và chúng tôi thường ở vào vị trí của sếp trong bài gốc, phải bảo các lập trình viên trẻ bình tĩnh lại và cứ tách riêng ra
Không phải là chia tay với Clean Code, mà là phải tránh trừu tượng hóa quá mức thì mới thực sự giữ được code sạch
Những việc mà handle thông thường làm là biến đổi affine, tức là tịnh tiến, xoay, co giãn và phản xạ; nếu là co giãn đồng đều thì đó là phép biến đổi đồng dạng. Điều này được bảo đảm hoạt động với bất kỳ hình nào
Vấn đề thật sự là không nên trừu tượng hóa những thứ mình chưa hiểu đủ rõ. Đặc biệt là khi không biết trong tương lai sẽ xuất hiện khả năng nào
Ví dụ, nếu nghĩ oval chỉ gồm hình tròn và hình elip, bạn sẽ khá bất ngờ khi oval “thật” đầu tiên xuất hiện
Tác giả không chủ động phân biệt code bừa bộn với code “không clean”, nên người khác mới cố phân biệt thay
DRY có giá trị, nhưng trừu tượng hóa cũng có chi phí. Luôn là một sự đánh đổi
Một trong những lý do tôi thích các ngôn ngữ như Haskell là chủ nghĩa tối đa hóa trừu tượng hóa nằm ở cấp độ ngôn ngữ, chứ không phải cấp độ coding
Mọi người đều đồng ý Applicative nên làm gì. Thách thức được đẩy sang phía bên phải hơn của pipeline từ ý tưởng đến code, tức là tìm Applicative phù hợp với bài toán
Hầu hết các design pattern hướng đối tượng đều có đối ứng tự nhiên và được định nghĩa rõ trong môi trường như vậy, giúp giảm các trừu tượng theo từng dự án
Với người mới bắt đầu, chi phí ban đầu để học ngôn ngữ là lớn, và thường xuyên có chuyện mở code Haskell của người khác ra thì thấy khoảng 12 compiler extension chưa từng gặp và phải hiểu chúng
Dù vậy, những compiler extension đó có thể mang sang mọi dự án về sau, nên tôi xem đó là lợi ích ròng
Với chuyên gia, nhược điểm là muốn có cảm giác cats sẽ ăn bao nhiêu RAM thì cần một bằng tiến sĩ
Nếu một đoạn code dùng Applicative hay Monad, nhờ các định luật đó ta có thể biết hành vi của nó
Các design pattern hướng đối tượng không có những định luật như vậy. Chúng là các khái niệm hoặc pattern được định nghĩa lỏng lẻo, mở cho diễn giải cá nhân và tùy biến, nhưng không có những định luật cụ thể nói chính xác chúng là gì và nên kỳ vọng hành vi hay đầu ra nào
Tuy nhiên, với ví dụ trong bài blog, phải nói là đã có rất nhiều lần đi theo con đường clean code rồi tự bắn vào chân mình trong tương lai
Nếu sau này phải học lại phần trừu tượng để hiểu nó, thì trừu tượng đó không hữu ích
Nơi hướng đối tượng khớp tự nhiên nhất là widget GUI và triển khai trình biên tập
Quy tắc hợp nhất các quy tắc khác của hướng đối tượng là đừng hỏi, hãy ra lệnh
Trong ví dụ này, các handle sẽ được chia sẻ theo cách đã biết, và sẽ có các điểm điều khiển theo từng hình. Khi handle của hộp thay đổi, có thể gọi
updateControlPoints(changedHandle), và khi điểm điều khiển thay đổi, có thể gọiupdateBoxHandles(changedControlPoint)Có thể có cách tách bạch gọn gàng khác, nhưng nếu là tôi thì tôi sẽ làm như vậy
Nhưng lại khó đạt đồng thuận về việc dùng thư viện parsing nào, chọn
Data.Text.StricthayData.Text.Lazy, dùng phong cách point-free đến mức nào, dùng.và>>>hay combinator nào khác, hoặc dùng language extension nàoTôi thích Haskell, nhưng nó cũng gặp các vấn đề giống những ngôn ngữ khác. Mọi người thật sự không thể thống nhất đâu là cách tốt nhất để làm việc
Bạn có thể có hệ thống kiểu của Haskell cùng hiệu năng dự đoán được
Chỉ cần tách riêng phần toán học lặp lại thành một hàm riêng, rồi gọi nó trong hàm resize là được
Bản thân các hàm resize trông có vẻ lặp lại là vì chúng tạo thành interface, nên refactoring để loại bỏ chúng là sai
Nhưng phần toán học mà các hàm đó thực hiện thì hoàn toàn là đối tượng hợp lý để dọn dẹp code
Kết quả là sinh ra rất nhiều hàm nhỏ và thuần, nhưng vì dễ test và refactor nên không thành vấn đề
Có nhiều cách và pattern để giải cùng một vấn đề, nhưng việc xé bỏ interface hiện có mà người khác đang dùng không phải là khởi đầu tốt
Lợi ích của trừu tượng hóa là có thể thay đổi phần bên trong linh hoạt hơn mà không làm hỏng phần còn lại
Bước đầu tốt là, như đã nói, tạo các hàm toán học linh hoạt hơn ở tầng dưới và giữ nguyên phần còn lại
Sau đó có thể trao đổi sâu hơn với nhóm về cách giao tiếp để interface cung cấp một facade rõ ràng, còn phần triển khai thì được trừu tượng hóa
Điều được mô tả ở đây có cảm giác giống như trừu tượng hóa tệ
Vấn đề thật sự nằm ở chỗ mọi người chỉ nhìn hai ba ví dụ rồi thiết kế, mà không kiểm tra xem nó có đủ tổng quát để đáp ứng các cách dùng trong tương lai hay không
Nên trừu tượng hóa từ dưới lên, không phải từ trên xuống
Không cần tạo một điểm duy nhất nơi mọi thứ diễn ra và xử lý các tổ hợp
2^nDù vậy vẫn có thể giảm lặp, thay đổi ở một điểm, đồng thời giữ được khả năng tùy chỉnh khi cần
Vì vậy chỉ nói “hãy tránh trừu tượng hóa tệ” hay “hãy làm sao để đáp ứng được cách dùng trong tương lai” là chưa đủ
Nói chung không ai có thể dự đoán tương lai
Trừu tượng hóa phải được dùng cực kỳ thận trọng ngay cả khi nó có vẻ đúng. Tốt hơn nên trở thành một người hoài nghi cực đoan
Trong mã toán học, chạm vào một thứ là ảnh hưởng đến tất cả
Đến một lúc nào đó, sẽ dễ hơn nếu để lại một phần mã trùng lặp, dùng manh mối trong cấu trúc mã hoặc chú thích để chỉ ra sự tương đồng, và theo thời gian nhấn mạnh các khác biệt cốt lõi bằng chú thích
Cách tiếp cận clean code với loại mã này không xử lý tốt các yêu cầu thay đổi
Cuối cùng nó dễ trở thành một đống hàm helper với nhiều đường đi mã khác nhau tùy theo vị trí gọi
Đôi khi có thể trừu tượng hóa điều này; như một số người có lẽ đã nhận ra, câu trước đang mô tả tính đa hình trong lập trình hướng đối tượng
Nhưng làm sao biết chính xác yêu cầu từ trước được? Cố làm “clean code” ở giai đoạn sớm như vậy chỉ kết thúc bằng trừu tượng hóa tệ hoặc thiết kế quá mức
Cách tiếp cận đúng là không làm gì cả, ghi lại các vấn đề có thể xảy ra, rồi chờ đến khi hiểu thêm về miền bài toán
Tôi không quá lo về đoạn mã hơi trùng lặp nhưng dễ hiểu như ví dụ gốc
Nhưng nếu phải sửa cùng một issue ở ba chỗ, hoặc phải thêm mã cho tính năng mới ở ba chỗ, thì tôi bắt đầu lo
Khi đó tôi bắt đầu nghĩ xem liệu có cách nào đẩy phần mã cần sửa hoặc thêm vào một vị trí chung hay không
Mọi người đọc về design pattern hoặc DRY, vì đang trong quá trình học nên thường áp dụng sai, rồi lại đổ trách nhiệm cho nguyên bản
Rob Pike từng nói một trong những lý do Go trở thành như hiện nay là “sao chép nhỏ tốt hơn phụ thuộc nhỏ”
Càng lập trình nhiều, tôi càng hiểu ý đó
Bản thân sự lặp lại không phải là vấn đề. Điều quan trọng là nắm được đơn vị mà bạn đang đọc và có thể sẽ sửa
DRY thường đồng nghĩa với việc thêm một trừu tượng hóa khác, và nếu trừu tượng hóa đó không đủ trực giao, thì giờ bạn phải cân nhắc hai đơn vị thay vì một, khiến tình hình tệ hơn