Один метод з багатьма параметрами проти багатьох методів, які потрібно викликати по порядку


16

У мене є кілька необроблених даних, для яких мені потрібно зробити багато речей (змістити їх, повернути їх, змінити масштаб уздовж певної осі, повернути до остаточного положення), і я не впевнений, який найкращий спосіб зробити це для підтримки читабельності коду. З одного боку, я можу зробити єдиний метод з багатьма параметрами (10+), щоб робити те, що мені потрібно, але це кошмар, який читає код. З іншого боку, я міг би зробити декілька методів з 1-3 параметрами кожен, але для отримання правильного результату ці методи потрібно буде викликати в дуже конкретному порядку. Я читав, що найкраще для методів робити одну справу і робити це добре, але, схоже, наявність багатьох методів, які потрібно викликати, щоб відкрити код для важко знайдених помилок.

Чи можу я використовувати парадигму програмування, яка б мінімізувала помилки та полегшила читання коду?


3
Найбільшою проблемою є не «не викликати їх по порядку», а «не знати», що ви (а точніше, майбутній програміст) повинні викликати їх по порядку. Переконайтеся, що будь-який програміст з технічного обслуговування знає деталі (Це багато в чому залежатиме від того, як ви документуєте вимоги, дизайн та технічні характеристики). Використовуйте тести, коментарі та надайте допоміжні функції, які приймають усі параметри та дзвонять іншим
mattnz

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

Приклади некомутативних операцій: перетворення зображення (обертання, масштабування та обрізання), матричне множення тощо
rwong

Можливо, ви можете використовувати currying: це унеможливить застосування методів / функцій у неправильному порядку.
Джорджіо

Який набір методів ви працюєте тут? Я маю на увазі, я думаю, що стандартним є передача об'єкта трансформації (наприклад, Affine Transform Java для 2D-матеріалів), який ви передаєте якомусь методу, який його застосовує. Вміст перетворення відрізняється залежно від порядку, яким ви називаєте початкові операції над ним, за дизайном (так це "ви називаєте його в порядку, який вам потрібен", а не "в порядку, який я хочу").
Годинник-Муза

Відповіді:


24

Остерігайтеся тимчасової зв’язки . Однак це не завжди є проблемою.

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

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

Ось як повинен виглядати складний код :

public List<Widget> process(File file) throws IOException {
  try (BufferedReader in = new BufferedReader(new FileReader(file))) {
    List<Widget> widgets = new LinkedList<>();
    String line;
    while ((line = in.readLine()) != null) {
      if (isApplicable(line)) { // Filter blank lines, comments, etc.
        Ore o = preprocess(line);
        Ingot i = smelt(o);
        Alloy a = combine(i, new Nonmetal('C'));
        Widget w = smith(a);
        widgets.add(w);
      }
    }
    return widgets;
  }
}

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

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

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


2
Дякую, я думаю, що це хороший спосіб вирішити цю проблему. Незважаючи на те, що збільшує кількість об’єктів (і це може здатися непотрібним), воно змушує порядок, зберігаючи розбірливість.
tomsrobots

10

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

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


5

Я створив би « ImageProcesssor » (або будь-яку назву, яка відповідає вашому проекту) та об’єкт конфігурації ProcessConfiguration , який містить усі необхідні параметри.

 ImageProcessor p = new ImageProcessor();

 ProcessConfiguration config = new processConfiguration().setTranslateX(100)
                                                         .setTranslateY(100)
                                                         .setRotationAngle(45);
 p.process(image, config);

Всередині процесора зображення ви інкапсулюєте весь процес за одним мехтодом process()

public class ImageProcessor {

    public Image process(Image i, ProcessConfiguration c){
        Image processedImage=i.getCopy();
        shift(processedImage, c);
        rotate(processedImage, c);
        return processedImage;
    }

    private void rotate(Image i, ProcessConfiguration c) {
        //rotate
    }

    private void shift(Image i, ProcessConfiguration c) {
        //shift
    }
}

Цей метод викликає трансформаційні методи в правильному порядку shift(), rotate(). Кожен метод отримує відповідні параметри від пройденої ProcessConfiguration .

public class ProcessConfiguration {

    private int translateX;

    private int rotationAngle;

    public int getRotationAngle() {
        return rotationAngle;
    }

    public ProcessConfiguration setRotationAngle(int rotationAngle){
        this.rotationAngle=rotationAngle;
        return this;
    }

    public int getTranslateY() {
        return translateY;
    }

    public ProcessConfiguration setTranslateY(int translateY) {
        this.translateY = translateY;
        return this;
    }

    public int getTranslateX() {
        return translateX;
    }

    public ProcessConfiguration setTranslateX(int translateX) {
        this.translateX = translateX;
        return this;
    }

    private int translateY;

}

Я використовував рідкі інтерфейси

public ProcessConfiguration setRotationAngle(int rotationAngle){
    this.rotationAngle=rotationAngle;
    return this;
}

що дозволяє вишукану ініціалізацію (як показано вище).

Очевидна перевага, інкапсулювання необхідних параметрів в одному об’єкті. Ваші підписи методу стають читабельними:

private void shift(Image i, ProcessConfiguration c)

Йдеться про зрушуючи в зображення і докладні параметри, так чи інакше налаштовані .

Крім того, ви можете створити ProcessingPipeline :

public class ProcessingPipeLine {

    Image i;

    public ProcessingPipeLine(Image i){
        this.i=i;
    };

    public ProcessingPipeLine shift(Coordinates c){
        shiftImage(c);
        return this;
    }

    public ProcessingPipeLine rotate(int a){
        rotateImage(a);
        return this;
    }

    public Image getResultingImage(){
        return i;
    }

    private void rotateImage(int angle) {
        //shift
    }

    private void shiftImage(Coordinates c) {
        //shift
    }

}

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

public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}

3

Чи думали ви використовувати якусь карі ? Уявіть, що у вас є клас Processeeта клас Processor:

class Processor
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T1 a1, T2 a2)
    {
        // Process using a1
        // then process using a2
    }
}

Тепер ви можете замінити клас Processorна два класи Processor1та Processor2:

class Processor1
{
    private final Processee _processee;

    public Processor1(Processee p)
    {
        _processee = p;
    }

    public Processor2 process(T1 a1)
    {
        // Process using argument a1

        return new Processor2(_processee);
    }
}

class Processor2
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T2 a2)
    {
        // Process using argument a2
    }
}

Потім ви можете зателефонувати в потрібному порядку за допомогою:

new Processor1(processee).process(a1).process(a2);

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


У нас була майже однакова ідея;) Різниця полягає лише в тому, що ваш Трубопровід виконує суворе замовлення на обробку.
Томас Джунк

@ThomasJunk: Наскільки я зрозумів, це вимога: "для отримання правильного результату ці методи потрібно називати в дуже конкретному порядку". Суворий порядок виконання звучить дуже схоже на склад функції.
Джорджіо

Так само і я. Але, якщо змінити порядок обробки, вам доведеться зробити багато рефакторингу;)
Томас Джунк

@ThomasJunk: Правда. Це дійсно залежить від програми. Якщо кроки обробки можна міняти дуже часто, то, ймовірно, ваш підхід кращий.
Джорджіо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.