Code tởm #2 - Biết quá nhiều ...
Alex và những chiếc xe Alex, một anh chàng mộc mạc với chiếc xe đạp cũ kĩ. Mỗi sáng anh đều đi làm trên chiếc xe này. Việc đầu tiên anh làm sẽ là kiểm tra chiếc xe, nếu có gì không ổn, xe buýt sẽ là lựa chọn thay thế. Với tư duy nhanh nhẹn, coder chúng ta có thể nhanh chóng dịch cuộc đời của ...
Alex và những chiếc xe
Alex, một anh chàng mộc mạc với chiếc xe đạp cũ kĩ.
Mỗi sáng anh đều đi làm trên chiếc xe này. Việc đầu tiên anh làm sẽ là kiểm tra chiếc xe, nếu có gì không ổn, xe buýt sẽ là lựa chọn thay thế.
Với tư duy nhanh nhẹn, coder chúng ta có thể nhanh chóng dịch cuộc đời của Alex thành mấy dòng code sau:
class Person def initialize(phuong_tien) @phuong_tien = phuong_tien end def di_lam if @phuong_tien.banh_xe.do_cang > 70 puts "xe không non hơi thì ta chơi xe đạp" @phuong_tien.phong_veo_veo else puts "xe không đi được ta nhảy xe buýt" end end end class XeDap attr_accessor :banh_xe def initialize(banh_xe)... def phong_veo_veo # lốp còn hơi là còn đi end end xe_dap = XeDap.new xe_dap.do_cang_lop_xe = 90 # độ căng lốp xe lên tới 90%, còn được gọi là căng vl # tuy nhiên, khi bảo ai đó "căng vl", thì ko có nghĩa là độ căng lốp # xe của họ lên tới 90% alex = Person(xe_dap) alex.di_lam # => xe không non hơi thì ta chơi xe đạp
Chẳng mấy lâu sau, anh ta đã dành dụm đủ tiền mua xe máy. Tuy nhiên, từ lúc thay xe, anh ta luôn phải đi xe buýt :v
class XeMay attr_accessor :banh_xe attr_accessor :binh_xang def initialize(banh_xe, binh_xang)... def phong_veo_veo # lốp còn hơi là còn đi # xe còn xăng là còn vút end end xe_may = XeMay.new xe_may.banh_xe.do_cang = 90 alex.di_lam # => xe không non hơi thì ta chời xe đạp (??? xe máy mà ta) # => xăng hết cmnr anh ơi
Logic #di_lam của Alex rõ ràng là đang có vấn đề, nào ta cùng giúp Alex
class Person ... def di_lam if @phuong_tien.banh_xe.do_cang > 70 && @phuong_tien.binh_xang.dung_tich > 0 puts "xe không non hơi thì ta chơi xe máy" puts "xe còn xăng là ta phóng vèo vèo" @phuong_tien.phong_veo_veo else puts "xe không đi được ta nhảy xe buýt" end end end alex.di_lam # => xe không đi được ta nhảy xe buýt xe_may.binh_xang.dung_tich = 100 alex.di_lam # => xe không non hơi thì ta chơi xe máy # => xe còn xăng là ta phóng vèo vèo
Một thời gian sau, Alex giàu hơn và chuyển sang ô tô, lại một lần nữa chúng ta phải giải quyết việc #di_lam của Alex, và lúc này đây, mùi code thối đã bốc lên nồng nàn. Mỗi khi đổi xe, anh ta lại phải chỉnh đốn lại cách đi làm. Nguyên do? Alex đã biết quá nhiều về cấu tạo của chiếc xe, quá nhiều hơn mức cần thiết.
Giải quyết vấn đề lần này, chúng ta sẽ nghịch thử Law of Demeter
Law of Demeter
Theo Wikipedia
The Law of Demeter (LoD) or principle of least knowledge is a design guideline for developing software, particularly object-oriented programs. In its general form, the LoD is a specific case of loose coupling. The guideline was proposed by Ian Holland at Northeastern University towards the end of 1987, and can be succinctly summarized in each of the following ways:
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
Each unit should only talk to its friends; don't talk to strangers.
Only talk to your immediate friends.
Hoặc. Hiểu một cách đơn giản. Hãy chỉ dùng một dấu chấm!!!
Hay tôi thích hiểu theo kiểu khác là: Hãy đùn đẩy trách nghiệm
Nào ta cùng tìm những chỗ dùng nhiều hơn 1 dấu chấm ở code trên, đó là những chỗ bốc mùi:
@phuong_tien.banh_xe.do_cang # thằng này @phuong_tien.binh_xang.dung_tich # và cả thằng này nữa
Để refactor, trước hết hãy đi từ kết quả mà chúng ta mong muốn:
def di_lam if @phuong_tien.di_duoc? puts "xe không non hơi thì ta chơi xe máy" puts "xe còn xăng là ta phóng vèo vèo" @phuong_tien.phong_veo_veo else puts "xe không đi được ta nhảy xe buýt" end
Như nầy thì cứ miễn là cái xe mới có hàm #di_duoc?, Alex sẽ chả bao giờ phải thay đổi cách đi làm của mình cả. Nói cách khác, anh ta méo có hiểu biết gì về cái xe cả, anh ta không còn phải có trách nhiệm kiểm tra xem với điều kiện nào thì xe đi được cả, mọi việc anh ta làm chỉ đơn giản là: "ê xe đạp/máy/ô tô/vẹo gì cũng đc! đi được ko bây?"
Ớ vậy thì trách nghiệm kiểm tra các điều kiện của xe đi được sẽ nằm ở đâu? Ở trong chính chiếc xe!
class XeDap ... def di_duoc? banh_xe.do_cang > 70 end end class XeMay ... def di_duoc? banh_xe.do_cang > 70 && binh_xang.dung_tich > 0 end end
Và giờ, ta chẳng cần bao giờ phải quan tâm Alex sẽ đi làm như thế nào mỗi khi anh ta đổi xe nữa. Easy life!!
Kết luận
Law of Demeter, theo tôi thấy, là một trong những cách xử lí dễ dàng nhất mà khiến code khô thoáng và dễ đổi hơn mà không cần phải suy nghĩ siêu nhân gì. Bản chất của cách dùng chỉ đơn giản là gói các chuỗi hàm vào một hàm khác với một cái tên nhiều ý nghĩa hơn. Tuy nhiên, nếu bạn để ý, mỗi lần ta thực hiện delegation, thì ta đang tạo thêm 1 hàm nữa, đây là cái giá của Law of Demeter. Và nếu vận dụng sai, nó sẽ dẫn đến code thối kiểu khác, với cái tên là Middle man (nên là cái gì nhiều quá nó cũng không có tốt :v). Chúng ta sẽ bàn tới ở kì sau:
-
Làm sao để áp dụng LoD với cái giá rẻ nhất.
-
Xử lý Middle man.