Mã tuyến tính dễ đọc hơn
(blog.separateconcerns.com)- Thay vì tách thành các hàm nhỏ để phân tách mức độ trừu tượng, quan điểm này cho rằng mã tuyến tính chạy từ trên xuống dưới giúp dễ theo dõi toàn bộ luồng hơn
- Khi trích xuất hàm để tạo cấu trúc top-down, có thể phải qua lại để kiểm tra giữa các hàm có tên giống nhau như
bakevàbakePizza - Những chi tiết như vị trí làm nóng lò hay kết quả khi chuyển chiếc pizza hai lần, các hàm nhỏ có thể che giấu hành vi thực tế thay vì bộc lộ ý đồ
- Nếu thêm chú thích theo từng bước vào mã tuyến tính, có thể giải thích ý đồ của tác vụ mà không làm tăng tham chiếu gián tiếp, nên đôi khi dễ đọc hơn việc thêm lớp trừu tượng
- Việc trích xuất các hàm nhỏ chỉ dùng một lần có thể làm mất tính tuyến tính, và trong mã thực tế còn có thể bộc lộ cả vấn đề hiệu năng như cách tạo lò nướng trong ví dụ
Khi tính tuyến tính quan trọng hơn việc trích xuất hàm
- Ví dụ trên Google Testing Blog so sánh hai cách cài đặt
createPizza, và cho rằng cách ở bên phải dễ đọc hơn vì không trộn lẫn các mức trừu tượng và mang tính top-down hơn - Ở quan điểm ngược lại, điều quan trọng hơn là cách cài đặt bên trái là mã có thể đọc tuyến tính từ trên xuống dưới ngay trên màn hình
- Với cách bên phải, muốn hiểu toàn bộ hành vi thì phải di chuyển qua nhiều định nghĩa hàm nhỏ
- Ngay cả cách trình bày cũng khiến một phần mã bên phải bị lược đi, làm hai cách cài đặt trông có vẻ tương đương về kích thước, nhưng thực tế bên phải dài hơn
- Việc trích xuất hàm có thể khiến chỉ nhìn tên thôi không đủ để hiểu đầy đủ hành vi
- Khi cùng tồn tại
bakevàbakePizza, sẽ khó biết ngay hàm nào làm nóng lò - Nếu cùng chuyển một chiếc pizza hai lần, muốn biết nó có idempotent hay làm hỏng kết quả thì vẫn phải xem cài đặt bên trong
- Khi cùng tồn tại
Mã tuyến tính có chú thích và ví dụ về lò nướng
- Phiên bản dễ đọc nhất được đánh giá là mã tuyến tính bên trái nhưng gắn thêm tên các hàm ở bên phải dưới dạng chú thích
- Các chú thích như
Prepare pizza,Add toppings,Heat oven,Bake pizza,Box and slicebộc lộ ý đồ của từng bước - Tính dễ đọc không đến từ các tầng trừu tượng bổ sung và tham chiếu gián tiếp, mà từ việc mô tả đúng điều đang được thực hiện
- Các chú thích như
- Kết luận nghiêng về phía không nên tách khỏi mã tuyến tính những hàm nhỏ chỉ dùng một lần
- Lợi ích của việc trích xuất hàm nhỏ được cho là không bù đắp được sự mất mát tính tuyến tính
- Cách xử lý lò nướng trong ví dụ cũng có phần gượng ép về mặt cấu trúc
- Việc làm nóng lò là một hành vi tự hoàn chỉnh, nên phù hợp hơn nếu là một phương thức của lò
- Luồng tạo lò mới và làm nóng nó mỗi lần làm một chiếc pizza không phù hợp với cách sử dụng thực tế
- Cấu trúc như vậy cũng xuất hiện trong mã thực tế và đôi khi có thể gây ra vấn đề hiệu năng
- Nhiều khả năng lò nướng nên được nhận qua tham số thay vì tạo mới bên trong
createPizza- Việc cung cấp lò gần với trách nhiệm của phía gọi hơn
- Nếu luồng xử lý là đặt pizza vào hộp, thì một interface trả về hộp thay vì pizza có thể tự nhiên hơn
1 bình luận
Các ý kiến trên Hacker News
Đây là vấn đề về phong cách; cũng như nấu ăn, quá nhiều hay quá ít muối đều làm hỏng món
Hy vọng không ai ở đây đang đề xuất một hàm thần thánh dài 1000 dòng, và tối đa 5 dòng cho mỗi hàm cũng không dễ đọc. Chia ở đâu cần phán đoán, cảm nhận tốt và lặp lại nhiều lần. Không nên từ bỏ abstraction chỉ vì lần thử đầu tiên không hay; sau vài lần refactor, có thể xuất hiện các class và API phù hợp với domain nghiệp vụ
Đồng thời, không nên abstraction quá vội vàng hoặc hành xử như thể vài dòng lặp lại là vết thương chí mạng. Abstraction vội vàng rất dễ buộc chung những đoạn code không cần tiến hóa cùng nhau. Việc tách ra một hàm chỉ được gọi ở một nơi để che giấu một đơn vị công việc có thể làm thuật toán gọn gàng hơn, đặc biệt hữu ích khi che đi boilerplate hoặc sự pha trộn giữa business logic và các mối quan tâm hạ tầng như xử lý kết nối DB. Tuy nhiên cần dùng thận trọng, và nên tránh chia nhỏ các bước vốn phải nằm ở cùng một mức abstraction
Một người tôi từng làm việc cùng đã tách mọi điều kiện boolean thành hàm vì cho là “dễ đọc”, và hoàn toàn không viết comment vì “comment là xấu”. Tôi không thích cuốn sách đó vì nó tạo ra những kẻ cuồng tín mù quáng làm theo các lời khuyên tệ hại như vậy
Đôi khi domain có thể đòi hỏi một hàm thần thánh 1000 dòng, và việc logic cùng tác vụ nằm ở một nơi có thể dễ đọc hơn nhiều so với 20 hàm dài 50 dòng. Dù sao thì để hiểu toàn bộ, bạn vẫn phải đọc hết 20 hàm đó; rồi ai đó có thể cố tái sử dụng một phần trong đó, chỉnh nó để đáp ứng 2–3 yêu cầu vốn không có trong tác vụ ban đầu, và ràng buộc một logic cụ thể với một use case không liên quan
Nếu hàm đó là pure function thì dù 1000 hay 10000 dòng, tôi nghĩ vẫn không vấn đề gì
Công thức nấu ăn vốn đã được abstraction rất nhiều. Khi nói “xào sơ hành tây”, ta giả định người đọc đã biết cách cắt hành và thuật toán xào sơ. Nếu viết inline tất cả mọi thứ thì sẽ không thể đọc nổi
Code cũng tương tự. Nếu loại bỏ abstraction một cách nghiêm ngặt, bạn sẽ đi xuống mức thấp nhất mà ngôn ngữ cho phép, và đó chắc chắn không phải là code dễ đọc. Ví dụ, nếu cố tự làm giải mã Unicode thay vì dùng method
decodecủa Python, sẽ rất khó hiểu chương trình thực sự đang làm gì. Chỉ vì ngôn ngữ cung cấp một abstraction đơn giản và đã được kiểm chứng tốt nên không ai làm vậy; vậy điều đó khác gì với việc bạn tự tạo ra một abstraction đơn giản, được kiểm chứng tốt để dùng trong toàn bộ business logic?Phần khó là tạo ra một abstraction được chọn đủ tốt để không ai cần đụng lại nữa
Việc các dòng code hiện trông có vẻ giống nhau không có nghĩa là về sau chúng cũng phải giống nhau hoặc phải được giữ cho giống nhau. Nếu ép gộp hai use case khác nhau chỉ vì “code gần như bị lặp”, theo thời gian rất dễ biến nó thành một abstraction không abstraction được gì cả
Nếu các use case phân kỳ quá nhiều, implementation sẽ hoặc đẩy nhiều logic lên phía caller, hoặc phơi bày các khác biệt dưới dạng flag và đặt hai implementation khác nhau song song bên trong. Cách đầu là abstraction nông nên ít giá trị, cách sau thì kém rõ ràng hơn so với hai implementation độc lập
Mã ví dụ quá đơn giản nên hiển nhiên mã tuyến tính dễ đọc hơn, nhưng ý tưởng đó không mở rộng tốt
Cũng cần cân nhắc khả năng tái sử dụng và mức độ dễ viết unit test; nếu nhét toàn bộ mã vào một hàm duy nhất, tất cả biến cục bộ — có thể liên quan hoặc không liên quan đến khối mã đang đọc — đều nằm trong scope, khiến việc suy luận khó hơn
Tuy vậy, nhìn lại thời còn ít kinh nghiệm, tôi đã nhiều lần module hóa quá mức những đoạn mã tuyến tính vốn ổn, biến chúng thành mã khó bảo trì hơn vì phải nhảy qua nhảy lại khắp nơi. Dạng viết ban đầu gần với luồng suy nghĩ trong đầu lúc đó hơn, và người đọc có lẽ cũng có khả năng diễn giải theo cách đó; đó là một lợi thế. Refactor quá đà có thể làm mất điều này
Rốt cuộc lập trình gần với một nghề thủ công, nên kinh nghiệm sẽ giúp đưa ra lựa chọn phù hợp theo tình huống
Mục đích của nó chỉ có một. Đó là công việc chuyển đổi các trang HTML riêng lẻ dùng ở một góc của ứng dụng, trên một nền tảng, thành carousel mô phỏng cảm giác native của nền tảng khác; nó cực kỳ chuyên biệt cho nền tảng đó và khu vực đó của ứng dụng
Có thể biến từng scope trong 9 scope đó thành hàm riêng, nhưng khi đó các lập trình viên sẽ muốn tái sử dụng chúng. Mỗi bước có những giả định tinh vi về những gì đã xảy ra ở bước trước; nếu tách thành hàm riêng thì phải xem xét lại các giả định, khái quát hóa chúng, và xác minh từng method có thể hoạt động độc lập. Không có lý do gì để bỏ chi phí đó cho đoạn mã gần như không cần ở nơi nào khác
Việc debug cũng không khó hơn, đã có test end-to-end, và trạng thái ở các bước trung gian không bị rò ra ngoài hàm. Thực tế, 2 lập trình viên khác đã góp phần sửa đổi theo thời gian, nó vẫn hoạt động tốt, và viết cũng nhanh
Mã tuyến tính mở rộng tốt và giải quyết được vấn đề. Không phải lúc nào cũng là dạng ta muốn, nhưng trong nhiều tình huống hơn ta tưởng, nó giúp cuộc sống dễ dàng hơn rất nhiều
Khi lần đầu thấy con quái vật 2000 dòng, phản ứng không mấy tích cực, nhưng chỉ cần nhìn vào 5 phút là khó tìm ra lỗi thực sự; với vài test, những nỗi sợ kia chỉ là các lo ngại không thành hiện thực
Đến một lúc nào đó bạn nhận ra hàng chục hàm ấy chỉ được gọi theo một thứ tự cụ thể, và mỗi hàm chỉ được dùng một lần. Rốt cuộc chẳng khác nào ép bất kỳ ai muốn dùng các hàm đó một cách hữu ích phải biết thứ tự kết hợp như ma thuật
Lý do cốt lõi khiến một hàm tuyến tính khổng lồ đôi khi dễ đọc hơn và đáng mong muốn hơn là vì nó giúp ta giữ đồng thời nhiều khái niệm và quan hệ trong một khối duy nhất mà không phải chuyển ngữ cảnh, hỗ trợ việc hiểu mã. Một người ủng hộ cực đoan là Arthur Whitney, người phát minh ngôn ngữ K; ông viết mã cực kỳ súc tích, gần như không thể hiểu được với người khác, để nhét được càng nhiều càng tốt vào một màn hình
Ví dụ cá nhân: việc đọc, hiểu và debug một hàm xử lý thông điệp Windows khổng lồ, tức
WndProc, với business logic nằm trong một câu lệnhswitchlớn, dễ hơn nhiều so với phiên bản Visual C++ tách các message handler thành hàm riêngNgoài ra, trong mã mẫu cho microcontroller, có một phiên bản ví dụ dùng ADC nằm toàn bộ trong một file và một phiên bản chia thành nhiều file như
main.c,config.c,interrupts.c,timer.c; dù chưa tới 200 dòng, phiên bản thứ hai khó hiểu vì phải chuyển ngữ cảnhNhững mảnh mã này thường trở thành hàm
privatecủa class, và có trạng thái. Vì là hàmprivatenên thực tế cũng khó testGiờ thì xuất hiện cả đống hàm
privatechỉ được gọi một lần và thường sửa trạng thái bằng side effect. Nếu chúng nằm ngay cạnh caller thì trong trường hợp đơn giản vẫn còn đọc được, nhưng theo thời gian, ai đó thêm một hàm khác vào giữa hàm gọi và hàm đã được tách raKhi đó, trừ khi xem call graph hoặc tìm kiếm trong file class, bạn sẽ không biết các mảnh mã ấy được gọi từ đâu, trong khi chúng sửa các trạng thái side effect khác nhau
Nếu định làm mã trở nên phi tuyến tính, thì nếu ngôn ngữ hỗ trợ, ít nhất hãy cân nhắc biến hàm
privateđược tách ra thành hàm nội bộ của hàm gọi. Như vậy sẽ rõ ràng rằng nó không được gọi từ nơi khácTrong codebase thực tế, đây cũng không phải lựa chọn một trong hai; nó gần với nghệ thuật kết hợp cả hai để tạo ra dạng dễ đọc và dễ bảo trì
Liệu mọi người có test tất cả các nhánh đó không? Hay chỉ viết một test nhét vào một chiếc pizza rồi xem đại khái có chạy không? Test nhiều nhánh từ bên ngoài thường khá phiền, và rắc rối hơn so với test các hàm nhỏ, chuyên biệt, nên có vẻ khả năng cao là trường hợp sau
Nói rằng “code tuyến tính không mở rộng được” thì thực ra ngược lại. Trong các codebase lớn, cơn ác mộng thật sự là những hàm nhỏ gọn với call stack lồng sâu
Không rõ nên thêm code mới vào đâu, phải lần theo mọi đường đi mà code có thể được gọi, khiến độ khó khi xác định tác động của thay đổi tăng theo cấp số nhân, và còn sinh ra các subroutine trùng lặp
Trong 99% trường hợp, đó không phải là bạn đã tạo ra abstraction tốt, nên tốt hơn là cứ dùng code tuyến tính. Tôi thích copy/paste hơn là các ngữ nghĩa hàm đáng ngờ
print_table(), ai đó sẽ tìm thấy nó, dùng trong code của họ, rồi gắn thêm một flag nhỏ để chỉnh output cho phù hợp use case của mình12 tháng sau nó sẽ trông như thế này:
print_table(rows,headers = None,is_unicode = False,left_align = False,align = [],remove_emoji = None,max_width = 80,potato_mode = 7,_debug_frontend = not FLAGS.dont_debug,ellipsis_for = 0,no_print = False,)Nếu xem hai khái niệm này là trực giao, ngoại trừ thực tế rằng tính dễ đọc có thể ảnh hưởng đến khả năng mở rộng, thì code tuyến tính không mở rộng tốt bằng code dạng module. Sự phân đôi này đáng để biết và đáng cân nhắc tùy tình huống
Dù vậy tôi vẫn không đồng ý. Nếu các hàm nhỏ là hàm thuần thì chúng không gây vấn đề về tính dễ đọc. Nghĩa là chúng không đụng đến trạng thái, không tiêm logic vào code, và phải chủ động giảm thiểu dependency injection cũng như việc truyền hàm này vào hàm khác
Nếu tạo một pipeline gồm các hàm thuần chỉ truyền dữ liệu, code sẽ dễ đọc và có thể mở rộng. Sẽ ít phải viết lại logic hơn rất nhiều do lỗi thiết kế, và khi kết hợp các hàm thuần, code trở nên giống Lego. Refactoring cũng gần với việc tái cấu trúc và tái kết hợp các phần tử nguyên thủy sẵn có hơn
Code ví dụ đã bớt gây xao nhãng hơn nếu ít nhất nó cố giữ ẩn dụ pizza cho có ý nghĩa, hoặc nếu đó không phải là code Go kém chất lượng
preparelà một cái tên hàm tệ hại. Một Gopher có kinh nghiệm có lẽ sẽ đặt tên kiểuNewPizzaFromOrderTôi không thấy lý do để tách
addToppingsthành hàm riêng. Nếu thật sự cần, cá nhân tôi sẽ biến nó thành một method củaPizzanhưfunc (p *Pizza) WithToppings(topping ...Topping) *Pizza { /* ... */ }. Pizza ngoài đời là mutable, nên method sẽ thay đổi receiverTôi cũng không hiểu vì sao lại instantiate một cái lò mới mỗi lần nướng pizza. Nên bắt đầu với một cái lò đã có, gọi
oven.Preheat()rồi gọioven.Bake(pizza). Xa hơn nữa, có thể đểoven.Preheat()trả về một kiểu mới củaOvencó expose.Bake(), nhằm ngăn lỗi nướng khi chưa làm nóng ngay ở bước compile. Ở chỗ khác có thể có interfaceBaker, và cũng có thể có implementationToasterOvenkhông cần làm nóng trước vì việc đó không quá quan trọngNgay cả nếu không đổi code, tôi cũng sẽ sắp xếp lại thứ tự khai báo theo luồng có thể dự đoán. Như vậy khi lướt qua các hàm gọi lẫn nhau sẽ không phải nhảy lên xuống trong trang
Không có thời gian nên tôi dừng ở đây, nhưng đoạn code này đã là một ví dụ quá tệ để bắt đầu cuộc tranh luận “bên nào dễ đọc hơn”
John Carmack cũng đã nói gần như điều tương tự, và từ đó tôi vẫn làm theo. Code tuyến tính tất nhiên dễ đọc vì nó đi theo thứ tự thực thi, và giảm thiểu việc mắt phải nhảy qua lại
Một số code phải phi tuyến tính để tái sử dụng, và khi đó quá trình thực thi trở thành một đồ thị. Nếu code không tận dụng việc tái sử dụng cấu trúc đồ thị, thì không cần đưa thêm đỉnh vào nơi chỉ cần một cạnh là đủ
http://number-none.com/blow/blog/programming/2014/09/26/carm...
Trong trường hợp này, nếu code bên trái viết kiểu
pizza.Toppings = get_pizza_toppings(order.kind), thì có lẽ sẽ tốt hơn vì việc thay đổi pizza vẫn là trọng tâm trong hàm chínhTôi đồng ý phần nào rằng code tuyến tính dễ đọc hơn, nhưng chỉ riêng điều đó không biến nó thành một thực hành viết code tốt
Tôi cho rằng code tuyến tính tốt thì dễ đọc hơn, nhưng khả năng bảo trì và khả năng kiểm thử thì kém hơn nhiều. Tôi có kinh nghiệm nhiều thập kỷ và cũng làm giám khảo bên ngoài cho sinh viên CS; qua nhiều năm, trong các thực hành tốt tôi thấy ngoài thực tế, điều chắc chắn duy nhất là giữ hàm nhỏ
Tôi cũng không đặc biệt thích abstraction, và cũng không nghĩ rằng phải tránh trùng lặp code bằng mọi giá, nhưng nếu tạo các hàm gần với một mục đích duy nhất nhất có thể, bản thân bạn trong tương lai sẽ biết ơn
Nếu code như ví dụ chạy trong production suốt 10 năm, từng đoạn sẽ thay đổi. Nếu may mắn thì comment cũng được cập nhật, nhưng phần lớn sẽ không như vậy. Unit test cũng sẽ trở nên lớn và khó xử lý, dần dần cẩu thả hơn, và có thể ai đó sẽ quên sửa phần test không có vẻ liên quan rõ ràng đến thay đổi. Code cũng rất có khả năng trở nên kém dễ đọc hơn theo thời gian. Không phải do chủ ý hay bất tài, mà vì những lý do rất con người như áp lực thời gian
Trong một thế giới hoàn hảo thì không cần tách các mối quan tâm, nhưng chúng ta sống trong một thế giới không hoàn hảo, và hàm càng nhỏ, càng ít trách nhiệm thì càng dễ xử lý sự không hoàn hảo đó theo thời gian
Nếu bạn đang đưa một object đi qua một luồng các trạng thái nhất định, tôi nghĩ tốt hơn là tách nó ra và biểu thị các chuyển đổi bằng type, hoặc viết thành một hàm lớn. Ví dụ, nếu
bakePizzanhậnRawPizzavà trả vềBakedPizza, bạn có thể ép thứ tự gọi ở compile timeVì dễ đọc, tính đúng đắn và khả năng kiểm thử, tôi thích cách đầu hơn, nhưng trong hầu hết ngôn ngữ lập trình, để đổi type của object thì phải tạo object mới và có chi phí runtime. Nếu đó là hot code path, thay đổi tại chỗ là hợp lý, và trong trường hợp đó nên để trong một hàm tuyến tính duy nhất
https://mitpress.mit.edu/9780262045490/
Email liên quan của John Carmack: http://number-none.com/blow/blog/programming/2014/09/26/carm...
Thảo luận: https://news.ycombinator.com/item?id=12120752
Rất đồng ý. Trước đây tôi từng ở phe ngược lại
Sự giằng co cơ bản ở đây là giữa tính cục bộ của hành vi ở một phía và mong muốn thể hiện rõ cái nhìn “mục lục” cấp cao ở phía kia. Với code dễ đọc, tính cục bộ quan trọng hơn. Như bài viết nói, góc nhìn mục lục có thể được làm rõ đủ bằng comment cho từng section
Còn có một lý do quan trọng hơn để nên ưu tiên code tuyến tính. Khi điều hướng toàn bộ codebase, mọi thứ dễ hơn nhiều nếu các “khối”, tức các đơn vị do hàm, class hoặc ngôn ngữ áp đặt, tương ứng xấp xỉ với use case nghiệp vụ. Nếu không, không gian tìm kiếm sẽ quá lớn, và bạn phải tự tái dựng cái toàn thể từ các mảnh nhỏ. Cấu trúc code nên làm việc đó thay bạn
Nếu nhiều “thứ” đều liên quan đến một việc, ví dụ đăng ký hoặc mua hàng, thì trong code cũng nên để chúng thành một khối. Như vậy dễ tìm và dễ thay đổi hơn nhiều. Chỉ tách thành hàm con khi cần tái sử dụng, đừng tách chỉ để tổ chức code
[0] https://htmx.org/essays/locality-of-behaviour/
Lý do lớn nhất là trạng thái. Hàm càng dài thì scope của biến cục bộ càng rộng. Ở bất cứ đâu trong hàm cũng có thể thay đổi bất kỳ biến nào, và data flow không rõ ngay lập tức. Khi có nhiều hàm, scope được giữ nhỏ và data flow rõ ràng hơn
Tác dụng phụ là indentation cũng giảm
Đồng thời tôi không thích các hàm quá nhỏ, vì khi đó khó tìm nơi công việc thực sự diễn ra
Hãy nghĩ đến một quy trình chốt cuối ngày gồm 10 bước không thể tái sử dụng và phải chạy theo thứ tự, mỗi bước dài 100 dòng. Mỗi bước dùng dữ liệu tương tự bước trước nhưng không giống hệt. Bạn thật sự sẽ chọn một hàm đơn dài 1000 dòng sao?
Cả hai đều đọc theo kiểu tuyến tính. Phiên bản tách các hàm nhỏ có mục lục ở đầu trang và tóm tắt data flow giữa các bước. Nếu định đọc toàn bộ, đó có vẻ là một thứ tự đọc hấp dẫn
Tuy nhiên, để duy trì khả năng đọc này, khi thứ tự các bước thay đổi thì vị trí hàm cũng phải được di chuyển theo. Nếu đó là hàm
privatevà chỉ được gọi từ mục lục thì ổn. Nhưng không có gì ép buộc phải giữ thứ tự, cũng không ép buộc ai nghĩ đến luồng đọc tổng thểKhi hàm bắt đầu được tái sử dụng, thường sẽ xuất hiện những trường hợp không còn thể tuyến tính hóa được nữa. Đôi khi mọi người bỏ cuộc và sắp xếp theo alphabet, hoặc đơn giản là trở nên ngẫu nhiên
Theo kinh nghiệm, người càng quen với code thì càng nghĩ việc nhét code vào các hàm nhỏ là hướng đi đúng
Vì họ đã xây dựng sẵn mô hình tinh thần của đoạn code đó, nên với họ cách triển khai gọn gàng nhất là cách có rất ít dòng
Nhưng khi người tiếp theo đến, họ phải đi qua đi lại nhiều nơi để xây dựng cùng một mô hình tinh thần mà không có ngữ cảnh ban đầu, liên tục push/pop stack trong đầu, và điều đó khó hơn nhiều
Ví dụ, bạn có thường đọc mã nguồn thư viện chuẩn của ngôn ngữ mình dùng không? Gần như không; thường bạn nhìn method signature, và nếu hơi phức tạp hoặc mới thì đọc tài liệu
Cốt lõi của interface là khiến bạn chỉ quan tâm method làm gì, chứ không phải nó được triển khai thế nào. Điều đó được giải thích bằng sự kết hợp giữa ngữ cảnh, tên gọi và tài liệu. Nhưng nhiều developer không hiểu hoặc không quan tâm điều này, nên viết code vô nghĩa bất kể là tuyến tính hay module hóa
Ví dụ, nếu trong một service class bạn phải gọi một method để lấy dữ liệu nào đó, gọi method khác để lấy dữ liệu khác, rồi gọi method thứ ba để lấy dữ liệu cần kết hợp với hai dữ liệu trước đó, thì ý nghĩa của service đó là gì? Như vậy là phơi bày toàn bộ độ phức tạp nội bộ ra bên ngoài
Tôi không nói là nên ép mọi thứ thành method nhỏ. 20 hàm 5 dòng, mỗi hàm chỉ được gọi một lần, làm một việc rất cụ thể và phải được gọi đúng thứ tự, thì không có ý nghĩa gì. Đó không phải là code sạch, mà gần với lập trình cargo cult hơn
Điều quan trọng là abstraction phù hợp để code có lý với cả thành viên mới lẫn thành viên giàu kinh nghiệm, dễ suy luận, và độ phức tạp được giấu ở đúng chỗ. Không dễ, nhưng có thể làm được
Con trai tôi đủ thông minh nhưng vẫn chật vật ở trường, và một trong số các chuyên gia giải thích rằng trường học thường dạy theo kiểu từ dưới lên, trong khi con tôi là người học rất thiên về từ trên xuống. Nó cần có cái nhìn tổng quan trước khi đi vào chi tiết, còn những người khác cần nắm chi tiết trước rồi ghép thành bức tranh tổng thể. Trường học thường dạy theo cách phù hợp với nhóm thứ hai
Có thể giữa các lập trình viên cũng có khác biệt tương tự
Nếu developer trước đó đã viết hàm
BakePizza, bạn chỉ cần giả định rằng pizza sẽ được nướng đúng cách rồi chuyển sang dòng tiếp theo. Nếu bạn cố hiểu cách nhà hàng vận hành mà lại sa vào chi tiết như nhiệt độ lò, bạn sẽ vừa không hiểu nhà hàng hoạt động ra sao, vừa quên mất nhiệt độ lò chính xácTrình biên tập nên có một nút bật/tắt để tạm thời inline hàm. Khi đó không cần phải qua lại nữa