Tái cấu trúc - có thích hợp để viết lại mã, miễn là tất cả các bài kiểm tra vượt qua?


9

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 tickhà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, tickchứ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 *_tickchứ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 *_tickchức năng khác với hành vi của chức năng ban đầu tick. Ví dụ, @days_remaining <= 0trong brie_ticknên là < 0- vì vậy brie_tickkhông hoạt động chính xác khi được gọi với days_remaining == 1quality < 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?


2
Tôi không chắc chắn tôi nhận được câu hỏi. Tất nhiên là ổn để viết lại mã. Tôi không chắc ý của bạn cụ thể là gì bởi " đơn giản là viết lại mã." Nếu bạn đang hỏi "Có ổn không khi viết lại mã mà không suy nghĩ nhiều về nó", thì câu trả lời là không, cũng như việc viết mã theo cách đó là không ổn .
John Wu

Điều này thường xảy ra do các kế hoạch kiểm tra chủ yếu tập trung vào các trường hợp sử dụng thử nghiệm thành công và rất ít (hoặc hoàn toàn không) trong việc bao gồm các trường hợp sử dụng lỗi hoặc các trường hợp sử dụng phụ. Vì vậy, nó chủ yếu là một rò rỉ bảo hiểm. Một rò rỉ của thử nghiệm.
Laiv

@JohnWu - Tôi có ấn tượng rằng việc tái cấu trúc thường được thực hiện dưới dạng một loạt các phép biến đổi nhỏ thành mã nguồn ("phương thức giải nén", v.v.) chứ không phải bằng cách viết lại mã (mà tôi có nghĩa là viết lại từ đầu mà không cần viết lại nhìn vào mã hiện có, như được thực hiện trong phần trình bày được liên kết).
user200783

@ John John - Viết lại từ đầu có phải là một kỹ thuật tái cấu trúc chấp nhận được không? Nếu không, thật đáng thất vọng khi thấy một bài thuyết trình được đánh giá cao như vậy về tái cấu trúc thực hiện phương pháp đó. OTOH nếu được chấp nhận, thì những thay đổi ngoài ý muốn trong hành vi có thể bị đổ lỗi cho các bài kiểm tra bị thiếu - nhưng có cách nào để tự tin rằng các bài kiểm tra bao gồm tất cả các trường hợp cạnh có thể?
user200783

@ User200783 Đó là một câu hỏi lớn hơn, phải không (làm cách nào để đảm bảo các bài kiểm tra của tôi toàn diện?) Về mặt thực tế, tôi có thể sẽ chạy một báo cáo bảo hiểm mã trước khi thực hiện bất kỳ thay đổi nào và kiểm tra cẩn thận bất kỳ lĩnh vực mã nào không được tập thể dục, đảm bảo nhóm phát triển chú ý đến họ khi họ viết lại logic.
John Wu

Câu trả lời:


11

Đâ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?

Cả hai. Tái cấu trúc chỉ sử dụng các bước tiêu chuẩn từ sách gốc của Fowlers chắc chắn ít xảy ra lỗi hơn so với viết lại, vì vậy thường chỉ nên sử dụng các loại bước này. Ngay cả khi không có kiểm tra đơn vị cho mọi trường hợp cạnh và ngay cả khi môi trường không cung cấp phép tái cấu trúc tự động, một thay đổi mã đơn lẻ như "giới thiệu biến giải thích" hoặc "hàm trích xuất" có cơ hội thay đổi chi tiết hành vi của mã hiện có hơn là viết lại hoàn toàn của một chức năng.

Tuy nhiên, đôi khi, viết lại một phần mã đó là những gì bạn cần hoặc muốn làm. Và nếu đó là trường hợp, bạn cần xét nghiệm tốt hơn.

Lưu ý rằng ngay cả khi sử dụng công cụ tái cấu trúc, luôn có rủi ro nhất định khi đưa ra lỗi khi bạn thay đổi mã, bất kể áp dụng các bước nhỏ hơn hay lớn hơn. Đó là lý do tại sao tái cấu trúc luôn cần các bài kiểm tra. Cũng lưu ý rằng các thử nghiệm chỉ có thể làm giảm xác suất lỗi, nhưng không bao giờ chứng minh sự vắng mặt của chúng - tuy nhiên sử dụng các kỹ thuật như xem mã và bảo hiểm chi nhánh có thể mang lại cho bạn mức độ tin cậy cao và trong trường hợp viết lại phần mã, thì đó là thường có giá trị để áp dụng các kỹ thuật như vậy.


1
Cảm ơn, điều đó có ý nghĩa. Vì vậy, nếu giải pháp cuối cùng cho những thay đổi không mong muốn trong hành vi là có các xét nghiệm toàn diện, có cách nào để tự tin rằng các xét nghiệm bao gồm tất cả các trường hợp cạnh có thể? Ví dụ, có thể có phạm vi bảo hiểm 100% brie_ticktrong khi vẫn không bao giờ kiểm tra @days_remaining == 1trường hợp có vấn đề bằng cách, ví dụ, thử nghiệm với @days_remainingthiết lập thành 10-10.
user200783

2
Bạn không bao giờ có thể tuyệt đối chắc chắn rằng các thử nghiệm bao gồm tất cả các trường hợp cạnh có thể, vì không thể thử nghiệm với tất cả các đầu vào có thể. Nhưng có rất nhiều cách để có được sự tự tin hơn trong các bài kiểm tra. Bạn có thể xem xét kiểm tra đột biến , đây là một cách để kiểm tra tính hiệu quả của các xét nghiệm.
bdsl

1
Trong trường hợp này, các nhánh bị bỏ lỡ có thể đã bị bắt với một công cụ bao phủ mã trong khi phát triển các thử nghiệm.
cbojar

2

Đ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?

Một trong những điều thực sự khó khăn khi làm việc với mã kế thừa: có được sự hiểu biết đầy đủ về hành vi hiện tại.

Mã kế thừa mà không có kiểm tra ràng buộc tất cả các hành vi là một mô hình phổ biến trong tự nhiên. Điều này khiến bạn phải đoán: điều đó có nghĩa là các hành vi không bị ràng buộc là các biến miễn phí? hoặc các yêu cầu chưa được xác định rõ?

Từ cuộc nói chuyện :

Bây giờ đây là tái cấu trúc thực sự theo định nghĩa của tái cấu trúc; Tôi sẽ cấu trúc lại mã này. Tôi sẽ thay đổi sự sắp xếp của nó mà không thay đổi hành vi của nó.

Đây là cách tiếp cận bảo thủ hơn; nếu các yêu cầu có thể không được xác định rõ, nếu các bài kiểm tra không nắm bắt được tất cả logic hiện có, thì bạn phải rất cẩn thận về cách bạn tiến hành.

Chắc chắn, bạn có thể khẳng định rằng nếu các bài kiểm tra mô tả không đầy đủ hành vi của hệ thống, thì bạn có "thất bại trong kiểm tra". Và tôi nghĩ điều đó công bằng - nhưng không thực sự hữu ích; đây là một vấn đề phổ biến phải có trong tự nhiên.

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?

Vấn đề không được khá rằng biến đổi cần phải có được bước-by-step; nhưng đúng hơn là sự lựa chọn công cụ tái cấu trúc (toán tử bàn phím con người? thay vì tự động hóa có hướng dẫn) không phù hợp với phạm vi kiểm tra, vì tỷ lệ lỗi cao hơn.

Điều này có thể đã được giải quyết hoặc bằng cách sử dụng các công cụ refactoring với độ tin cậy cao hơn hoặc bằng cách giới thiệu một pin lớn hơn các xét nghiệm để cải thiện những hạn chế trên hệ thống.

Vì vậy, tôi nghĩ rằng sự kết hợp của bạn được lựa chọn kém; ANDkhông OR.


2

Tái cấu trúc không nên thay đổi hành vi hiển thị bên ngoài của mã của bạn. Đó là mục tiêu.

Nếu bài kiểm tra đơn vị của bạn thất bại, điều đó cho thấy bạn đã thay đổi hành vi. Nhưng vượt qua các bài kiểm tra đơn vị không bao giờ là mục tiêu. Nó giúp ít nhiều để đạt được mục tiêu của bạn. Nếu tái cấu trúc thay đổi hành vi có thể nhìn thấy bên ngoài và tất cả các kiểm tra đơn vị đều vượt qua, thì việc tái cấu trúc của bạn không thành công.

Kiểm tra đơn vị làm việc trong trường hợp này chỉ cho bạn cảm giác sai lầm của thành công. Nhưng điều gì đã xảy ra? Hai điều: Việc tái cấu trúc là bất cẩn, và các bài kiểm tra đơn vị không được tốt lắm.


1

Nếu bạn định nghĩa "chính xác" là "các bài kiểm tra vượt qua", thì theo định nghĩa , việc thay đổi hành vi chưa được kiểm tra là không sai.

Nếu một hành vi cạnh cụ thể cần được xác định, hãy thêm một thử nghiệm cho nó, nếu không, thì không quan tâm đến những gì xảy ra. Nếu bạn thực sự phạm tội, bạn có thể viết một bài kiểm tra để kiểm tra truekhi trong trường hợp cạnh đó để ghi lại rằng bạn không quan tâm hành vi đó là gì.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.