Gần đây tôi đã xem "Tất cả những điều nhỏ nhặt " từ RailsConf 2014. Trong buổi nói chuyện này, Sandi Metz tái cấu trúc một chức năng bao gồm một câu lệnh if lồng nhau lớn:
def tick
if @name != 'Aged Brie' && @name != 'Backstage passes to a TAFKAL80ETC concert'
if @quality > 0
if @name != 'Sulfuras, Hand of Ragnaros'
@quality -= 1
end
end
else
...
end
...
end
Bước đầu tiên là chia hàm thành nhiều phần nhỏ hơn:
def tick
case name
when 'Aged Brie'
return brie_tick
...
end
end
def brie_tick
@days_remaining -= 1
return if quality >= 50
@quality += 1
@quality += 1 if @days_remaining <= 0
end
Điều tôi thấy thú vị là cách các chức năng nhỏ hơn này được viết. brie_tick
, ví dụ, không được viết bằng cách trích xuất các phần có liên quan của tick
hàm ban đầu , mà từ đầu bằng cách tham khảo các test_brie_*
bài kiểm tra đơn vị. Khi tất cả các bài kiểm tra đơn vị này được thông qua, brie_tick
đã được xem xét thực hiện. Khi tất cả các chức năng nhỏ đã được thực hiện, tick
chức năng nguyên khối ban đầu đã bị xóa.
Thật không may, người trình bày dường như không biết rằng cách tiếp cận này đã dẫn đến ba trong số bốn *_tick
chức năng bị sai (và chức năng khác là trống rỗng!). Có các trường hợp cạnh trong đó hành vi của các *_tick
chức năng khác với hành vi của chức năng ban đầu tick
. Ví dụ, @days_remaining <= 0
trong brie_tick
nên là < 0
- vì vậy brie_tick
không hoạt động chính xác khi được gọi với days_remaining == 1
và quality < 50
.
Điều gì đã đi sai ở đây? Đây có phải là một thất bại của thử nghiệm - bởi vì không có thử nghiệm cho các trường hợp cạnh cụ thể này? Hoặc thất bại trong việc tái cấu trúc - bởi vì mã nên được chuyển đổi từng bước thay vì viết lại từ đầu?