Lập trình phần mềm: Bí quyết sử dụng anti-pattern nên tránh để phát triển sản phẩm bền vững

“Learn the rules, break the rules, make up new rules, break the new rules.” ― Marvin Bell

Chúng ta học 23 Design Pattern để làm gì? Chúng ta học chuẩn hóa database (Normalization) để làm gì? 

Tại sao chúng ta lại phá chuẩn Database (de-normalization)? Và bài viết dưới đây lại cho chúng ta thấy tại sao anti-pattern lại cần thiết?

SQL ANTIPATTERNS – NHỮNG SAI LẦM CHẾT NGƯỜI KHI LÀM VIỆC VỚI SQL
SQL ANTIPATTERNS – NHỮNG SAI LẦM CHẾT NGƯỜI KHI LÀM VIỆC VỚI SQL

Trả lời các câu hỏi trên, chúng ta sẽ hiểu rõ bức tranh tổng thể của "thực tế" và ý nghĩa sâu xa của Marvin Bell - học giả Mỹ đương đại.

10 anti-pattern trong lập trình phần mềm

Thiết kế cấu trúc website và ứng dụng, hoặc thiết lập workflow hiệu quả thường khiến ta vướn vào nhiều vấn đề nan giải. Ta không cần phải tìm giải pháp từ con số 0, vì ta có thể “tái sử dụng” nhiều giải pháp cấp cấu trúc tương tự như code cấp độ siêu vi.

Design patterns cũng là giải pháp “tái sử dụng” được cho một vài trường hợp, và có thể tối ưu code rất tốt.

 

Design patterns là công cụ hỗ trợ lập trình tuyệt vời với nhiều công thức đã được kiểm chứng. Tuy vậy, design pattern cũng có hiệu quả tiêu cực. Lúc này, ta gọi chúng là antipattern.

Antipattern là gì?

Thuật ngữ “antipattern” được giới thiệu lần đầu trong cuốn sách mang tên AntiPatterns 1998. Nó dùng để chỉ các giải pháp được tái sử dụng, ban đầu trong có vẻ rất hiệu quả, nhưng dần dần lại hại nhiều hơn lợi.

Quá trình này có nhiều nguyên nhân, như: sử dụng pattern không đúng bối cảnh, thời gian,… hoặc cả mô hình (paradigm) ngay từ đầu đã không được tốt.

Antipattern cũng thường được gọi là pattern thất bại. Thật may, ta hoàn toàn có thể nhận biết và tránh chúng.

Trong bài này tôi sẽ điểm qua 10 antipattern thường thấy trong lập trình web. (chú ý rằng những antipattern trong bài viết này không hoàn toàn giống với những antipattern trong cuốn sách trên.)

1. Premature optimization

Một nhân tố quan trọng trong tối ưu code là “thời gian chuẩn”. Nếu chúng ta để ý đến chi tiết và tối ưu chúng quá sớm trước khi biết rõ cần làm gì, ta có thể dễ dàng phạm phải antipattern “Tối ưu “non””.

Theo câu nói nổi tiếng của Donald Knuth “tối ưu sớm là gốc rễ mọi tội ác”, có hơi thái quá, nhưng có vậy mới thể hiện được các vấn đề nghiêm trọng trong tương lai của antipattern này.

Nếu ta tối ưu trước khi thiết lập cấu trúc hiệu quả, ta có thể làm code khó đọc hơndebug và bảo trì khó khăn hơn, và thêm nhiều code dư thừa.

Để tránh tối ưu non, bạn có thể theo nguyên tắc lập trình YAGNI (You Aren’t Gonna Need It), theo đó “cần khi nào thì thêm khi đó”, chứ không phải theo kiểu “chắc là sau này sẽ cần”.

2. Reinventing the Wheel

Antipattern “reinventing the wheel” (tạm dịch “xây lại từ đầu”) xảy ra khi ta muốn tự mình làm tất tần tật mọi thứ và viết code từ đầu đến cuối, mà không tham khảo những phương pháp, API hay thư viện đã có.

Reinventing the wheel không chỉ lãng phí thời gian; mà các giải pháp tùy chọn, đặc biệt là các functionality cơ bản, thường không tốt như các functionality chuẩn đã được nhiều người dùng và lập trình viên thử nghiệm.

3. Dependency Hell

Ngược với “xây lại từ đầu”, ta có “địa ngục dựa dẫm”.

Nếu, thay vì cặm cuội code từ đầu đến cuối, ta lại dùng quá nhiều thư viện của nhiều bên thứ ba thì sao? Sẽ có lúc ta muốn cập nhật, và những thư viện này sẽ không tương thích với nhau.

Ta có thể giải quyết địa ngục dựa dẫm bằng cách sử dụng package manager cho phép ta cập nhật các thư viện độc lập thông minh. Nếu ta vấp phải quá nhiều vấn đề, refactoring có thể là ý hay.

4. Spaghetti Code

“Code mì ý” chắc hẳn là antipattern nổi tiếng nhất. cụm từ miêu tả ứng dụng khó debug hoặc điều chỉnh do thiếu cấu trúc đúng đắn.

Kết quả của thiết kế phần mềm hời hợt là một đống code ná ná nhau chồng chất thành một tô spaghetti, rối như tơ vò. code mì ý vô cùng khó đọc, và ta không cách nào hiểu được quy luật hoạt động chính xác của “mớ” code này.

5. Programming by Permutation

“Programming by Permutation” hay “programming by accident” xảy ra khi bạn cố gắng tìm giải pháp cho một vấn đề bằng cách thử nghiệm liên tục những thay đổi nhỏ (giống như kiểu hoán vị vậy), test và đánh giá từng sự thay đổi nhỏ và cuối cùng lựa chọn cách đầu tiên làm cho code của bạn hoạt động đúng. Cách tiếp cận này đôi khi có vẻ hấp dẫn khi mà lập trình viên không hoàn toàn hiểu rõ về đoạn code đó và họ tin rằng một hoặc nhiều sửa đổi nhỏ sẽ có thể làm cho code của họ hoạt động đúng.

Programming by Permutation có thể dễ dàng gây phát sinh ra nhiều lỗi mới, tệ hơn nữa, những lỗi đó chúng ta khó có thể nhận ra ngay được ==’. Đã có nhiều trường hợp thực tế là PM tham gia chỉnh sửa nhỏ để thử nghiệm các yêu cầu của khách hàng, cho dù là các đoạn code nhỏ, đơn giản và đem lại giá trị tiện ích rất lớn cho sản phẩm, thì cái giá phải trả là dễ mất kiểm soát trong tương lai, dễ để lại nợ kỹ thuật (technical debt) và do vậy rất cần thêm effort cho các hoạt động kiểm thử hồi quy (test lại các lỗi cũ, đặc biệt là các potential issues và boundary test cases - kịch bản lỗi tại biên).

Trong nhiều trường hợp, tìm được giải pháp hiệu quả cho tất cả các tình huống có thể xảy ra là một nhiệm vụ bất khả thi.

6. Copy and Paste Programming

“Lập trình chép dán” xảy ra khi ta không tuân theo nguyên tắc lập trình Don’t Repeat Yourself (DRY), và thay vì tìm giải pháp chung, ta lại đi nhập “vụn” code hết chỗ này đến chỗ khác, rồi còn cố chỉnh đi chỉnh lại cho phù hợp.

Vì những đoạn code này chỉ khác nhau ở một vài điểm nhỏ, kết quả là code của chúng ta sẽ lặp lại liên tục.

Ta không những thấy antipattern này ở "ma" mới, mà vẫn có ở những lập trình viên kỳ cựu, bởi nhiều người thường dùng code viết và tỗi ưu sẵn cho nhiều nhiệm vụ cụ thể, từ đó vô tình gây lặp lại.

7. Cargo-Cult Programming

Cái tên “lập trình cargo-cult” bắt nguồn từ một hiện tượng dân tộc học mang tên “cargo cult”. Cargo cults khởi nguồn tại khu vực Nam Thái Bình Dương sau thế chiến II, sự tiếp xúc giữa dân bản địa với nền văn minh mới khiến họ nghĩ rằng sản phẩm công nghiệp như Coca-Cola, TV, tủ lạnh trong những thùng hàng được chuyển lên đảo là kết quả của thế lực siêu nhiên; và nếu họ thực hiện “nghi thức màu nhiệm” như nền văn hóa phương Tây, những thùng hàng như vậy sẽ lại đến.

Antipattern này cũng tương tự. Ta cứ dùng những framework, thư viện, phương pháp, pattern thiết kế,… cơ lợi cho ta, mà không hiểu tại sao ta phải dùng tới chúng, hay cơ chế hoạt động của những công nghệ này ra sao.

Nhiều khi, lập trình viên cứ nhắm mắt thấy cái gì đang “hip” thì đưa vào mà không có mục đích cụ thể. Antipattern này không chỉ bơm ứng dụng của ta “căng phồng”, mà còn gây ra nhiều lỗi không đáng có.

8. Lava Flow

Ta nói đến antipattern “dòng dung nham”  khi ta làm việc với code có nhiều phần dư thừa, chất lượng kém, đồng thời tích nguyên với cả chương trình, nhưng ta lại chẳng hiểu vai trò và ảnh hưởng của nó là gì. Vì vậy, việc loại bỏ code này mang lại nhiều rủi ro.

Hiện tượng này thường thấy ở legacy code, hoặc khi code này do người khác viết (thiếu tài liệu chính xác), hoặc khi project chuyển từ giai đoạn phát triển sang production quá nhanh.

Cái tên của antipattern này thể hiện sự tương đồng với dung nham núi lửa, ban đầu thì nhanh và êm nhẹ khó phòng ngừa, sau thì đặc quánh và khó chuyển dời.

Về mặt lý thuyết, ta có thể loại bỏ dòng dung nham khi đã kiểm tra và refactor kỹ lưỡng, nhưng trong thực tế, việc áp dụng sẽ rất khó, gần như bất khả thi. Dòng dung nham thường có chi phí thực hiện rất cao, vẫn tốt hơn nếu ngay từ đâu ta có workflow và cấu trúc vững chắc.

Một thí dụ trong thực tế: Tại sao chiếc lốp ô tô có những chiếc lông? Đó chính là "sản phẩm phụ" (by-product) sinh ra trong quá trình sản xuất. Mặc dù những chiếc lông này không có tác dụng gì, những việc nhổ chiếc lông không đem lại giá trị cho sản phẩm chính (chiếc lốp), nhưng việc tồn tại của những chiếc lông cũng không ảnh hưởng đến sản phẩm khi đi vào sử dụng thực tế.

9. Hard Coding

Lập trình dính cứng” là antipattern được nhắc đến rất nhiều trong các cuốn sách lập trình web. Lập trình cứng xảy ra khi ta lưu trữ cấu hình hoặc dữ liệu đầu vào (như đường dẫn file hoặc tên máy chủ từ xa) trong mã nguồn, chứ không phải một file, database, user input,… từ bên ngoài.

Và vấn đề ở đây là phương pháp này chỉ hoạt động chính xác trong một mội trường nhất định. Khi điều kiện thay đổi, ta phải thực hiện điều chỉnh lên mã nguồn, thường là ở nhiều chỗ phân tán.

Tại sao lập trình viên hay hard-code một số chỗ mà họ nghĩ sẽ không ai nhìn thấy? Về bản chất thì việc làm này giúp release feature nhanh hơn, nhất là đối với những dự án có thay đổi yêu cầu là rất lớn, thời gian quá ngắn để làm "đúng quy trình". Hard-code giống như khi ta thiết kế một cái bàn ăn được đóng sẵn, không thể gấp lại để có thể chở bằng xe máy. Vì nhà sản xuất hiểu rằng nếu một cái bàn có thể lắp đặp, tháo dỡ giống như xếp đồ chơi lego, thì khi đó giá thành sẽ không thể rẻ. Liệu người mua có chấp nhận giá cao cho một sản phẩm Việt nhưng hiện đại và tiện lợi như đồ gỗ châu Âu?

10. Soft Coding

Nếu “cố quá” tránh lập trình cứng, ta có thể dễ dàng phạm phải một antipattern ngược lại, mang tên “lập trình mềm”.

Với lập trình mềm, ta đưa dữ liệu cần có trong mã nguồn ra các tài nguyên bên ngoài, ví dụ như khi ta lưu trữ business logic trong database. Lý do là vì ta lo lắng business rule sẽ thay đổi trong tương lai, và lúc đó phải viết lại code.

Trong những trường hợp cực đoan, chương trình quá mềm sẽ trở nên quá trừu tượng và rối rằm, không cách nào nắm bắt được (đặc biệt là với các thành viên khác trong team), và cực kỳ khó bảo dưỡng, debug.

Một thí dụ thực tế: Các phần mềm như CMS (quản trị tin tức) thương lưu đường dẫn tương đối của link (Relative Url) vào cơ sở dữ liệu. Cách thiết kế này rất hữu ích cho migration (thí dụ chuyển kho sang server mới), nhưng "gót chân achilles" là thiết kế này không phù hợp với mô hình phân tán, đa nền tảng như MicroServices. Điều gì xảy ra nếu một Service khác kết nối vào API của CMS (đặc biệt là Headless CMS) và lấy nguồn dữ liệu từ nguồn cấp (RSS Feed)? Rõ ràng các links lẫn vào nguồn dữ liệu Feed sẽ là những link không có tên miền, máy khách sẽ không thể biết các link đó được sinh ra từ "node" nào trong hệ sinh thái MicroServices.

Biên tập và cập nhật lại từ nguồn Techtalk.vn