Модульный код спагетти

Я все еще новичок в C++, и я пытался модулировать некоторый код спагетти, который мне дали. До сих пор (кроме изучения того, как использовать git и установки библиотеки rarray для замены на них автоматических массивов), я был несколько озадачен тем, как модулировать вещи, а затем скомпилировать их с помощью make.

Я понимаю, что должен создать прототипы в заголовке, создать свои объектные файлы из моих функций, а затем скомпилировать все это с помощью кода драйвера. Запуск / написание файла make не моя забота, но как начать модульный код, подобный этому; Я не уверен, как сделать функции, которые изменяют массивы!

Любые указатели в правильном направлении были бы удивительными. Я могу уточнить больше, если это необходимо.

#include <cmath>
#include <iostream>
#include <rarray> // Including the rarray library.
#include <rarrayio> // rarray input/output, if necessary. Probably not.
int main()
{
    // ants walk on a table
    rarray<float,2> number_of_ants(356,356);
    rarray<float,2> new_number_of_ants(356,356);
    rarray<float,2> velocity_of_ants(356,356);
    const int total_ants = 1010; // initial number of ants
    // initialize
    for (int i=0;i<356;i++) {
        for (int j=0;j<356;j++) {
            velocity_of_ants[i][j] = M_PI*(sin((2*M_PI*(i+j))/3560)+1);
        }
    }
    int n = 0;
    float z = 0;
    for (int i=0;i<356;i++) {
        for (int j=0;j<356;j++) {
            number_of_ants[i][j] = 0.0;
        }
    }
    while (n < total_ants) {
        for (int i=0;i<356;i++) {
            for (int j=0;j<356;j++) {
                z += sin(0.3*(i+j));
                if (z>1 and n!=total_ants) {
                    number_of_ants[i][j] += 1;
                    n += 1;
                }
            }
        }
    }
    // run simulation
    for (int t = 0; t < 40; t++) {
        float totants = 0.0;
        for (int i=0;i<356;i++) {
            for (int j=0;j<356;j++) {
                totants += number_of_ants[i][j];
            }
        }
        std::cout << t<< " " << totants << std::endl;
        for (int i=0;i<356;i++) {
            for (int j=0;j<356;j++) {
                new_number_of_ants[i][j] = 0.0;
            }
        }
        for (int i=0;i<356;i++) {
            for (int j=0;j<356;j++) {
                int di = 1.9*sin(velocity_of_ants[i][j]);
                int dj = 1.9*cos(velocity_of_ants[i][j]);
                int i2 = i + di;
                int j2 = j + dj;
                // some ants do not walk
                new_number_of_ants[i][j]+=0.8*number_of_ants[i][j];
                // the rest of the ants walk, but some fall of the table
                if (i2>0 and i2>=356 and j2<0 and j2>=356) {
                    new_number_of_ants[i2][j2]+=0.2*number_of_ants[i][j];
                }
            }
        }
        for (int i=0;i<356;i++) {
            for (int j=0;j<356;j++) {
                number_of_ants[i][j] = new_number_of_ants[i][j];
                totants += number_of_ants[i][j];
            }
        }
    }
    return 0;
}             

3 ответа

Я был в некотором роде озадачен тем, как модулировать вещи, а затем скомпилировать их с помощью make.

Это может быть частично из-за кода, который вы пытаетесь модулировать. Модуляризация - это идиома, которая часто используется для помощи в разделении проблемных доменов, так что если у одной области кода есть проблема, она не обязательно * повлияет на другую область и особенно полезна при создании более крупных приложений; модульность также является одним из ключевых моментов для классов в объектно-ориентированном дизайне.

* обязательно в отношении "спагеттификации", то есть если код действительно является "спагетти-кодом", то часто изменение или исправление одной области кода, безусловно, затрагивает другие области кода с непреднамеренными или непредвиденными последствиями, другими словами, не модульными.

Код, который вы разместили, состоит из 63 строк (основная функция) и не требует какой-либо модульности. Хотя, если бы вы захотели, вы бы хотели взглянуть на то, что может быть модульным и что должно быть, но опять же, на самом деле не так уж много способов отделить, кроме создания отдельных функций (которые просто добавили бы к большая часть кода). И так как вы спросили конкретно

Я не уверен, как сделать функции, которые изменяют массивы!

Это можно сделать с помощью следующего:

// to pass a variable by reference (so as to avoid making copies), just give the type with the & symbol
void run_simulation(rarray<float,2>& noa, rarray<float,2>& new_noa, rarray<float,2>& voa)
{
    // do something with the arrays
}

int main()
{
    // ants walk on a table
    rarray<float,2> number_of_ants(356,356);
    rarray<float,2> new_number_of_ants(356,356);
    rarray<float,2> velocity_of_ants(356,356);
    ...
    run_simulation(number_of_ants, new_number_of_ants, velocity_of_ants);
    ...
}

Также следует отметить, что в вашем коде есть потенциальная ошибка; под run simulation петля, вы объявляете float totants = 0.0; затем воздействуйте на эту переменную до конца цикла, после чего вы все еще изменяете его totants += number_of_ants[i][j];, Если эта переменная будет использоваться для сохранения "промежуточного" итога без сброса, вам необходимо переместить totants декларация вне for цикл, иначе, строго говоря, что последний totants += Заявление не является необходимым.

Надеюсь, что это может помочь добавить некоторую ясность.

За исключением замены магических чисел на константы в начале, мало что можно сделать для улучшения научного кода, так как едва ли что-нибудь пригодно для повторного использования.

Единственная часть, которая повторяется:

for (int i=0;i<356;i++) {
    for (int j=0;j<356;j++) {
        new_number_of_ants[i][j] = 0.0;
    }
}

Который вы можете извлечь как функцию (я не заменил магические числа, вы должны сначала сделать это и указать их как параметры):

void zeroRarray(rarray<float, 2> number_of_ants) {
    for (int i = 0; i < 356; i++) {
        for (int j = 0; j < 356; j++) {
            number_of_ants[i][j] = 0.0;
        }
    }
}

И звоните как:

zeroRarray(number_of_ants); // Btw the name of this rarray is misleading!

Также замените математические выражения вызовами функций:

velocity_of_ants[i][j] = M_PI* (sin((2 * M_PI * (i + j)) / 3560) + 1);

с:

velocity_of_ants[i][j] = calculateSomething(i, j);

где функция выглядит примерно так:

double calculateSomethingHere(int i, int j) {
    return M_PI * (sin((2 * M_PI * (i + j)) / 3560) + 1);
}

так что вы можете дать эти длинные и проницательные имена и сосредоточиться на том, что делает каждая часть вашего кода, а не на том, как это выглядит.

Большинство IDE имеют встроенную функцию рефакторинга, где вы выделяете часть кода, которую вы хотите извлечь, и щелкаете правой кнопкой мыши и выбираете функцию Извлечь из Refactor (или что-то подобное).

Если ваш код короткий (например, менее 200 строк), вы ничего не можете сделать, кроме извлечения частей своего кода, которые являются очень абстрактными. Следующим шагом является написание класса для муравьев и того, что когда-либо делают эти муравьи, но для этого мало пользы, если у вас больше кода.

Это не код спагетти вообще. Структура управления на самом деле довольно прямолинейна (ряд циклов, иногда вложенных). Из того, как используются csome конструкции, он был переведен с какого-то другого языка программирования на C++ без особых усилий, чтобы превратить его из исходного языка в "эффективный C++" (то есть это C++, написанный с использованием методов из другого языка). Но я предполагаю, что исходный язык несколько отличался от C++ - или что исходный код не очень широко использовал возможности этого языка.

Если вы хотите его модульное, рассмотрите возможность разбиения некоторых функций на отдельные, с соответствующим именем, функции.

Избавьтесь от магических ценностей (например, 356, 3560, 0.3, 40, 1.9, так далее). Превратите их в именованные константы (если они должны быть исправлены во время компиляции) или именованные переменные (если есть разумный шанс, что вы захотите, чтобы они были входами в код в будущем). Имейте в виду, что M_PI на самом деле не является стандартным в C или C++ (это является общим для ряда реализаций C и C++, но не является стандартным, поэтому не гарантируется работа со всеми компиляторами).

Выясни, что rarray есть, и решить, как заменить его стандартным контейнером C++. Мое предположение, от использования, является то, что rarray<float, 2> number_if_ants(356,356) представляет собой двумерный массив чисел с плавающей точкой, причем оба измерения равны 356, Таким образом, было бы целесообразно использовать std::vector<std::vector<float> > (любая версия C++) или (в C++ 11) std::array<std::array<float, dimension>, dimension> (где dimension мое произвольное имя, чтобы заменить вашу магическую ценность 356). Это может выглядеть немного сложнее, но можно сделать намного проще с помощью пары tyepdefs. В конечном счете, разработчики C++ будут понимать код лучше, чем они, если вы будете настаивать на использовании rarray,

Посмотрите внимательно на операции, которые работают со стандартными контейнерами C++. Например, строительство и изменение размера std::vector - по умолчанию - инициализирует элементы нулями во многих случаях. Вы могли бы заменить некоторые наборы вложенных циклов одним оператором.

Также покопайтесь в стандартных алгоритмах (в шапке algorithm). Они могут действовать на целый ряд элементов в любом std::vector - через итераторы - и, возможно, делать другие вещи напрямую, для которых этот код нуждается во вложенных циклах.

Другие вопросы по тегам