Що допомогло б переробляти великий метод, щоб я нічого не порушив?


10

Наразі я переробляю частину великої бази кодів, не маючи тестування. Я спробував перефактурувати код грубим способом, тобто намагаючись відгадати, що робить код і які зміни не змінять його значення, але без успіху: він випадковим чином розбиває функції по всій базі коду.

Зауважте, що рефакторинг включає переміщення застарілого коду C # у більш функціональний стиль (застарілий код не використовує жодної з функцій .NET Framework 3 і пізніших версій, включаючи LINQ), додаючи генеричні файли, де код може отримати користь від них тощо.

Я не можу використовувати формальні методи , враховуючи, скільки вони коштують.

З іншого боку, я припускаю, що принаймні "будь-який реконструйований застарілий код має бути випробуваними з одиницею", слід суворо дотримуватися, незалежно від того, скільки це коштувало б. Проблема полягає в тому, що коли я переробляю крихітну частину приватного методу 500 LOC, додавання одиничних тестів виявляється важким завданням.

Що може допомогти мені дізнатись, які одиничні тести є актуальними для даного фрагмента коду? Я здогадуюсь, що статичний аналіз коду якимось чином був би корисним, але які інструменти та методи я можу використовувати:

  • Точно знати, які одиничні тести я повинен створити,

  • І / чи знаєте, чи вплинула на зміна оригінального коду зміна, яку він виконував інакше?


Як ви міркуєте, що написання одиничних тестів збільшить час для цього проекту? Багато прихильників не погоджуються, але це також залежить від вашого вміння писати їх.
JeffO

Я не кажу, що це збільшить загальний час для проекту. Що я хотів сказати, це те, що це збільшить короткочасний час (тобто негайний час, який я витрачаю зараз під час рефакторингу коду).
Арсеній Муренко

1
Ви б не хотіли використовувати formal methods in software developmentйого, оскільки він використовується для доведення правильності програми за допомогою логіки предикатів і не буде застосований до рефакторингу великої бази даних коду. Формальні методи, які зазвичай використовуються для доказування коду, працюють правильно в таких областях, як медичне застосування. Ви маєте рацію зробити це дорого, тому його не використовують часто.
Мюші

Хороший інструмент, такий як параметри рефактора в ReSharper, полегшує таку задачу. У подібних ситуаціях варто коштувати грошей.
billy.bob

1
Не повна відповідь, але німий прийом, який я вважаю напрочуд ефективним, коли всі інші методи рефакторингу мене не вдається: Створіть новий клас, розбийте функцію на окремі функції з точно таким кодом, який вже є, щойно зламаний кожні 50 або близько рядків, рекламуйте будь-який місцевих жителів, які поділяються між функціями на членів, тоді окремі функції краще вписуються мені в голову і дають мені можливість бачити в членах, які фрагменти пронизані цілою логікою. Це не кінцева мета, а лише безпечний спосіб отримати безладдя, щоб бути готовим до безпечного рефакторингу.
Джиммі Хоффа

Відповіді:


12

У мене були подібні виклики. Книга Work with Legacy Code - це чудовий ресурс, але є припущення, що ви можете взути ріг в одиничних тестах, щоб підтримати свою роботу. Іноді це просто неможливо.

У моїй археологічній роботі (мій термін підтримки старого коду, як цей) я дотримуюся аналогічного підходу щодо того, що ви окреслили.

  • Почніть з чіткого розуміння того, що зараз виконує рутина.
  • Одночасно визначте, що повинно було виконувати рутина . Багато хто думає, що ця куля та попередня є однаковими, але є тонка різниця. Часто, якби рутина робила те, що мала робити, тоді ви не застосовували б зміни обслуговування.
  • Запустіть декілька зразків під рутиною і переконайтеся, що ви потрапили на крайові випадки, відповідні шляхи помилок, а також шлях основного рядка. Мій досвід полягає в тому, що пошкодження застави (поломка функції) відбувається від граничних умов, які не реалізуються точно так само.
  • Після цих вибіркових випадків визначте, що зберігається, що не обов'язково потрібно зберігати. Знову ж таки, я виявив, що подібні побічні ефекти призводять до заставної шкоди в інших місцях.

На цьому етапі у вас має бути список кандидатів щодо того, що було викрито та / або маніпульовано цією процедурою. Деякі з цих маніпуляцій, ймовірно, будуть ненавмисними. Тепер я використовую findstrі IDE, щоб зрозуміти, які ще сфери можуть посилатися на елементи зі списку кандидатів. Я проведу деякий час, розуміючи, як працюють ці посилання та яка їхня природа.

Нарешті, як тільки я заблудив себе в думці, що розумію вплив початкової програми, я вношу свої зміни одночасно і повторюю кроки аналізу, які я окреслював вище, щоб переконатися, що зміни працюють, як я очікую це працювати. Я спеціально намагаюся уникати зміни декількох речей одночасно, оскільки виявив, що це підривається на мене, коли я намагаюся перевірити вплив. Іноді ви можете піти з декількох змін, але якщо я можу дотримуватися окремого маршруту, це мої переваги.

Коротше кажучи, мій підхід схожий на те, що ви виклали. Це багато підготовчої роботи; потім внести обережні, індивідуальні зміни; а потім перевірити, перевірити, перевірити.


2
+1 за використання лише "археології". Це той самий термін, який я використовую для опису цієї діяльності, і я думаю, що це чудовий спосіб викласти це (також думав, що відповідь хороша - я насправді не такий дрібний)
Ерік Дітріх

10

Що допомогло б переробляти великий метод, щоб я нічого не порушив?

Коротка відповідь: маленькими кроками.

Проблема полягає в тому, що коли я переробляю крихітну частину приватного методу 500 LOC, додавання одиничних тестів виявляється важким завданням.

Розгляньте наступні кроки:

  1. Перемістіть реалізацію в іншу (приватну) функцію та делегуйте виклик.

    // old:
    private int ugly500loc(int parameters) {
        // 500 LOC here
    }
    
    // new:    
    private int ugly500loc_old(int parameters) {
        // 500 LOC here
    }
    
    private void ugly500loc(int parameters) {
        return ugly500loc_old(parameters);
    }
    
  2. Додайте код реєстрації (переконайтеся, що журнал не провалюється) у свою оригінальну функцію для всіх входів та виходів.

    private void ugly500loc(int parameters) {
        static int call_count = 0;
        int current = ++call_count;
        save_to_file(current, parameters);
        int result = ugly500loc_old(parameters);
        save_to_file(current, result); // result, any exceptions, etc.
        return result;
    }
    

    Запустіть свою програму і робіть все, що можете, з нею (дійсне використання, недійсне використання, типове використання, нетипове використання тощо).

  3. Тепер у вас є max(call_count)набори входів і виходів, з якими можна писати свої тести; Ви можете написати єдиний тест, який повторює всі наявні у вас параметри / набори результатів і виконує їх у циклі. Ви також можете написати додатковий тест, який виконує певну комбінацію (використовується для швидкої перевірки передачі певного набору вводу-виводу).

  4. Перемістити // 500 LOC hereназад в ugly500locфункцію (і видалити при вході функції).

  5. Почніть витягувати функції з великої функції (не робіть нічого іншого, просто витягайте функції) та запускайте тести. Після цього у вас повинно бути більше невеликих функцій для рефактора, замість 500LOC.

  6. Живіть щасливо назавжди.


3

Зазвичай блок-тести - це дорога.

Зробіть необхідні тести, які доводять, що поточний працює як очікувалося. Витратьте свій час, і останній тест повинен зробити вас впевненим у результатах.

Що може допомогти мені дізнатись, які одиничні тести є актуальними для даного фрагмента коду?

Ви переробляєте фрагмент коду, ви повинні точно знати, що він робить і на що впливає. Тому в основному потрібно протестувати всі зони впливу. Це забере у вас багато часу ... але це очікуваний результат будь-якого процесу рефакторингу.

Тоді ви можете відірвати все без проблем.

AFAIK, для цього немає техніки для кулезахисту ... вам просто потрібно бути методичним (на якому б методі ви почували себе комфортно), багато часу і багато терпіння! :)

Привіт і удачі!

Олексій


Тут важливі засоби висвітлення коду. Підтвердити, що ви обстежили кожен шлях великим складним методом за допомогою перевірки, важко. Інструмент, який показує, що в сукупності KitchenSinkMethodTest01 () ... KitchenSinkMethodTest17 () охоплює рядки 1-45, 48-220, 245-399 та 488-500, але не торкаються коду між; дозволить з’ясувати, які додаткові тести потрібно написати набагато простіше.
Дан вигадує Firelight
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.