Довгий метод рефакторингу: залишаючи як розділення на методи проти використання локальних функцій


9

Припустимо, у мене давно такий метод:

public void SomeLongMethod()
{
    // Some task #1
    ...

    // Some task #2
    ...
}

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

Є багато людей (включаючи мене), які думають, що довгі методи - це кодовий запах. Також мені не подобається ідея використання #region(-ів) тут, і є дуже популярна відповідь, яка пояснює, чому це погано .


Але якщо я розділяю цей код на методи

public void SomeLongMethod()
{
    Task1();

    Task2();
}

private void Task1()
{
    // Some task #1
    ...
}

private void Task2()
{
    // Some task #1
    ...
}

Я бачу такі проблеми:

  • Опилення області визначення класу з визначеннями, які використовуються внутрішньо за допомогою одного методу, і це означає, що я повинен десь документувати це Task1і Task2призначений лише для внутрішніх служб SomeLongMethod(або для кожної людини, яка читає мій код, доведеться зробити цю думку).

  • Запитання IDE автозаповнення (наприклад, Intellisense) методів, які будуть використовуватися лише один раз всередині одного SomeLongMethodметоду.


Тож якщо я розділю код цього методу на локальні функції

public void SomeLongMethod()
{
    Task1();

    Task2();

    void Task1()
    {
        // Some task #1
        ...
    }

    void Task2()
    {
        // Some task #1
        ...
    }
}

то у цього немає недоліків окремих методів, але це не виглядає краще (принаймні для мене), ніж оригінальний метод.


Яка версія програми SomeLongMethodє більш доступною та читаною для вас і чому?



@gnat моє запитання є специфічним для c # , а не для Java . Моє основне занепокоєння стосується класичного методу проти локальної функції, а не просто розділення на методи чи ні.
Вадим Овчинников

@gnat Також AFAIK Java (на відміну від C #) не підтримує вкладені функції.
Вадим Овчинников

1
приватне ключове слово напевно захищає вашу "область визначення класу"?
Еван

1
зачекайте ...... наскільки великий у вас клас?
Еван

Відповіді:


13

Самостійні завдання повинні бути перенесені на самостійні методи. Немає недостатньо великого недоліку, який би перекрив цю мету.

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

Звичайно, якщо завдання не є справді самостійними, але потребують переміщення стану, слід змінити об'єкт методу, хелперний об'єкт або подібну конструкцію. І якщо завдання повністю автономні і зовсім не пов'язані з доменом програми (наприклад, просто форматування рядків, то, можливо, вони повинні перейти в цілий інший (корисний) клас. Але це все не змінює факту, що " утримання методів на клас "в кращому випадку є дуже допоміжною метою.


Отже, ви для методів, а не локальних функцій, так?
Вадим Овчинников

2
@VadimOvchinnikov З сучасним IDE-кодом, що відрізняється, мало різниці. Найбільша перевага локальної функції полягає в тому, що вона може отримати доступ до змінних, які вам інакше доведеться передавати в якості параметрів, але якщо таких дуже багато, то завдання не були насправді самостійними.
Кіліан Фот

6

Мені не подобається жоден підхід. Ви не надали більш широкого контексту, але в більшості випадків це виглядає так:

У вас є клас, який виконує певну роботу, поєднуючи потужність кількох служб в єдиний результат.

class SomeClass
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;

    public void SomeLongMethod()
    {
        _service1.Task1();
        _service2.Task2();       
    }
}

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

class Service1
{
    public void Task1()
    {
        // Some task #1
        ...
    }

    private void SomeHelperMethod() {}
}

class Service2
{
    void Task2()
    {
        // Some task #1
        ...
    }
}

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


2

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

Якщо ви хочете, щоб додатковий рівень захисту "приватний до методу", ви можете створити новий клас

public class BigClass
{
    public void SomeLongMethod();

    private class SomeLongMethodRunner
    {
        private void Task1();
        private void Task2();
    }
}

Але заняття в класах великі некрасиві: /


2

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

Це говорить на користь використання локальних функцій.


1
Хм, безумовно, повторно використовує код == обмін методом серед багатьох абонентів. тобто максимізація його сфери
Ewan

1

Я погоджуюся з тим, що довгі методи часто кодовим запахом, але я не думаю, що це вся картина, скоріше саме цей метод довгий, що вказує на проблему. Моє загальне правило: якщо код виконує кілька різних завдань, то кожне з цих завдань має бути власним методом. Для мене важливо, повторюються чи ні ці розділи коду; якщо ви справді абсолютно не впевнені, що ніхто не захоче окремо їх називати в майбутньому (і це дуже важко в цьому! ви не очікуєте, що він буде використаний поза класом).

Мушу зізнатися, я ще не використовував локальні функції, тому моє розуміння тут далеко не повне, але приведене вами посилання передбачає, що вони часто використовуються аналогічно лямбда-вислову (хоча це кілька випадків використання де вони можуть бути більш доречними, ніж лямбда, або де ви не можете використовувати лямбда, див. Локальні функції порівняно з виразами лямбда для специфіки). Тоді я думаю, що це може зводитись до деталізації "інших" завдань; якщо вони досить тривіальні, то, можливо, локальна функція буде добре? З іншого боку, я бачив приклади виразів лямбда-бегемота (можливо, там, де розробник нещодавно виявив LINQ), які зробили код набагато менш зрозумілим та доступним, ніж якби його просто викликали з іншого методу.

На сторінці " Місцеві функції " зазначено, що:

"Місцеві функції дозволяють зрозуміти наміри вашого коду. Кожен, хто читає ваш код, може побачити, що метод не може викликати, крім методу, що містить. Для командних проектів вони також унеможливлюють, щоб інший розробник помилково викликав метод прямо з інших місць у класі або стук. '

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

Підсумовуючи це, загалом, я вважаю за краще розділити їх на методи, але MS написав локальні функції з причини, тому я, звичайно, не сказав би не використовувати їх,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.