Малі функції порівняно із збереженням залежної функції в одній і тій же функції


15

У мене є клас, який встановлює масив вузлів і з'єднує їх один з одним у структурі, подібній графіку. Це найкраще:

  1. Зберігайте функціонал для ініціалізації та підключення вузлів в одній функції
  2. Майте функціонал ініціалізації та підключення у двох різних функціях (і має залежний порядок, за яким функції потрібно викликати, хоча майте на увазі, що ці функції є приватними.)

Спосіб 1: (Погано в тому, що одна функція робить дві речі, АЛЕ вона зберігає залежну функціональність, згруповану разом - вузли ніколи не повинні бути з'єднані без ініціалізації спочатку.)

init() {
    setupNodes()
}

private func setupNodes() {
    // 1. Create array of nodes
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

Спосіб 2: (Краще в тому сенсі, що це самодокументування, але НЕ connectNodes () ніколи не слід викликати перед setupNodes (), тому кожен, хто працює з внутрішніми класами, повинен знати про цей порядок.)

init() {
    setupNodes()
}

private func setupNodes() {
    createNodes()
    connectNodes()
}

private func createNodes() {
    // 1. Create array of nodes
}

private func connectNodes() {
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

Захоплено почути будь-які думки.



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

Відповіді:


23

Проблема, з якою ви маєте справу, називається тимчасовою зв'язкою

Ви праві турбуватися про те, наскільки зрозумілий цей код:

private func setupNodes() {
    createNodes();
    connectNodes();
}

Я можу здогадатися, що там відбувається, але скажіть, чи це робить те, що ще відбувається трохи зрозуміліше:

private func setupNodes() {
    self.nodes = connectNodes( createNodes() );
}

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

Це робить connectNodes()залежність від вузлів явною.


1
Дякуємо за посилання Оскільки мої функції приватні і викликаються від конструктора - init () у Swift - я не думаю, що мій код був би таким же поганим, як приклади, які ви зв'язали (для зовнішнього клієнта неможливо створити екземпляр із екземпляром нульовий екземпляр змінної), але у мене схожий запах.
mcfroob

1
Код, який ви додали, є більш читабельним, тому я перероблю в цьому стилі.
mcfroob

10

Окремі функції з двох причин:

1. Приватні функції є приватними саме для цієї ситуації.

Ваша initфункція є загальнодоступною, а її захист та зміна - це інтерфейс, поведінка та повернене значення. Результат, який ви очікуєте від цього методу, буде однаковим незалежно від того, якою реалізацією ви користуєтесь.

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

2. Підключення вузлів один до одного може не бути приватною функцією

Що робити, якщо в якийсь момент ви хочете додати до масиву інші вузли? Ви знищуєте встановлення, яке у вас є зараз, і повністю ініціалізуєте його повністю? Або ви додаєте вузли до існуючого масиву і знову запускаєте connectNodes?

Можливо, connectNodesможе бути справна відповідь, якщо масив вузлів ще не створений (викинути виняток? Повернути порожній набір? Ви повинні вирішити, що має сенс для вашої ситуації).


Я думав так само, як 1, і я міг би викинути виняток чи щось таке, якби вузли не були ініціалізовані, але це не особливо інтуїтивно. Дякуємо за відповідь.
mcfroob

4

Ви також можете виявити (залежно від того, наскільки складна кожна з цих задач), що це хороший шов для розділення іншого класу.

(Не впевнений, чи Swift працює таким чином, але псевдо-код :)

class YourClass {
    init(generator: NodesGenerator) {
        self.nodes = connectNodes(generator.make())
    }
    private func connectNodes() {

    }
}

class NodesGenerator {
    public func make() {
        // Return some nodes from storage or make new ones
    }
}

Це розділяє обов'язки створення та модифікації вузлів на окремі класи: NodeGeneratorпіклується лише про створення / вилучення вузлів, в той час YourClassяк стосується лише з'єднання вузлів, які йому надані.


2

На додаток до того, що це конкретна мета приватних методів, Swift дає можливість використовувати внутрішні функції.

Внутрішні методи ідеально підходять для функцій, які мають лише один сайт для викликів, але відчувають, що вони не виправдовують окремі приватні функції.

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

Ось приклад того, як це може виглядати в цьому випадку:

init() {
    self.nodes = setupNodes()

    func setupNodes() {
        var nodes = createNodes()
        connect(Nodes: nodes)
    }

    private func createNodes() -> [Node]{
        // 1. Create array of nodes
    }

    func connect(Nodes: [Node]) {
        // 2. Go through array, connecting each node to its neighbors 
        //    according to some predefined constants
    }
}

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


0

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

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

Якщо ваша мова підтримує це, ви все одно можете мати одну функцію-це-одне, використовуючи вкладені функції

function setupNodes ()  {
  function createNodes ()  {...} 
  function connectNodes ()  {...}
  createNodes() 
  connectNodes() 
} 

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

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

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

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


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

Щоб відповісти на деякі невизначеності цього питання: 1) Так, Свіфт підтримує внутрішні функції, і 2) Він має два рівні "приватного". privateдозволяє отримати доступ лише до типу "enclocing" (struct / class / enum), тоді як fileprivateдозволяє отримати доступ у всьому файлі
Alexander - Reinstate Monica
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.