Як я можу керувати дуже великим набором правил і магічних чисел у своїй програмі?


21

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

На основі лише кількох входів (точніше 6) мені потрібно зробити сотні викликів API, які можуть приймати до десятка параметрів кожен; усі вони породжені набором правил, які я зібрав після опитування всіх, хто займається частиною. Розділ з правилами та параметрами мого коду набирає 250 рядків і зростає.

Отже, який найкращий спосіб зберегти свій код читабельним та керованим? Як я можу розділити всі свої магічні числа, усі правила, алгоритми та процедурні частини коду? Як я маю справу з дуже детальним та детальним API?

Моя головна мета - вміти передавати комусь своє джерело і давати їм зрозуміти, що я робив, без мого внеску.


7
Чи можете ви навести кілька прикладів цих дзвінків API?
Роберт Харві


"Усі проблеми з інформатики можна вирішити іншим рівнем непрямості" - Девід Уілер
Філ Фрост

... за винятком занадто багато рівнів непрямості :)
Dan Lyons

1
Важко відповісти на ваше запитання, не бачачи свого коду. Ви можете розмістити свій код на codereview.stackexchange.com та отримати консультацію від інших програмістів.
Гілберт Ле Бланк

Відповіді:


26

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

Майте на увазі, "бази даних" не обов'язково означають MySQL або MS-SQL. Спосіб збереження даних буде багато залежати від того, як програма використовується, як ви їх записуєте тощо. Це може означати базу даних типу SQL або просто означає форматований текстовий файл.


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

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

1
Звучить як м'яке кодування . Бази даних призначені для стану, що змінюється. Магічні числа за визначенням не змінюються.
Філ Мороз

1
@PhilFrost: Ви можете зробити їх незмінними. Просто не пишіть їм після створення початкової таблиці.
Роберт Харві

1
@PhilFrost: Ну, я зараз бачив API, з яким він має справу. Він чудовий лише своїм чистим розміром. Йому може взагалі не потрібна база даних, якщо вона цього не зробить.
Роберт Харві

14

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

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

В ідеалі ви отримаєте код, який говорить:

LeftBearingHoleDepth = BearingWidth + HoleDepthTolerance;
if (not CheckPartWidth(LeftBearingHoleDepth, {other parameters})
    {whatever you need to adjust}

Залежно від того, наскільки локальні константи, я б спокусився оголосити їх у функціях, які вони використовують, де це можливо. Досить корисно звернути:

SomeAPICall(10,324.5, 1, 0.02, 6857);

в

const NumberOfOilDrainHoles = 10
const OilDrainHoleSpacing = 324.5
{etc}
SomeAPICall(NumberOfOilDrainHoles, OilDrainHoleSpacing, {etc}

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

Одна порада для імен: поставте найважливіше слово зліва. Він може не читати також, але це полегшує пошук речей. Більшу частину часу ви дивитесь на відстійник і дивитесь на болт, не дивлячись на болт і цікавитесь, де це робиться, тому називайте це SumpBoltThreadPitch, а не BoltThreadPitchSump. Потім сортуйте список констант. Пізніше, щоб витягнути всі нарізки потоку, ви можете отримати список у текстовому редакторі та або скористатися функцією пошуку, або скористатися інструментом типу grep, щоб повернути лише рядки, що містять "ThreadPitch".


1
також розгляньте можливість створення інтерфейсу Fluent
Ian

Ось фактичний рядок з мого коду. Чи має сенс те, що відбувається тут (аргументи - x1, y1, z1, x2, y2, z2 як подвійні), якби ви знали, що означають назви змінних? .CreateLine(m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flange_thickness, m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flask_height + m_flange_thickness)
користувач2785724

Ви також можете використовувати ctags з інтеграцією редактора для пошуку констант.
Філ Мороз

3
@ user2785724 Це безлад. Що це робить? Це виготовлення канавки певної довжини та глибини? Тоді ви можете створити функцію під назвою createGroove(length, depth). Вам потрібно реалізувати функції, які описують те, що ви хочете виконати, як ви описали їх інженеру-механіку. Саме про це грамотне програмування.
Філ Мороз

Це виклик API, щоб намалювати одну лінію в 3d-просторі. Кожен із 6 аргументів знаходиться в різних рядках програми. Весь API божевільний. Я не знав, де зробити безлад, тому я встиг там. Якби ви знали, що таке виклик API та його аргументи, ви бачили б, якими були кінцеві точки, використовуючи знайомі вам параметри, і зможете відновити його назад до частини. Якщо ви хочете ознайомитись з SolidWorks, API абсолютно лабіринтний.
користувач2785724

4

Я думаю, що ваше питання зводиться до: як я будую обчислення? Зауважте, ви хочете керувати "набором правил", які є кодом, і "набором магічних чисел", які є даними. (Ви можете бачити їх як "дані, вбудовані у ваш код", але вони все ж є даними).

Крім того, зробити свій код "зрозумілим для інших" насправді є загальною метою всіх парадигм програмування (див., Наприклад, " Шаблони впровадження " Кента Бека або " Чистий код " Роберта К. Мартіна для авторів програмного забезпечення, які заявляють ту саму мету як ви, для будь-якої програми).

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

  1. Використовуйте названі константи та перерахунки для заміни магічних чисел

    Приклад констант :

    if (partWidth > 0.625) {
        // doSomeApiCall ...
    }
    return (partWidth - 0.625)
    

    його слід замінити на іменну константу, щоб наступні зміни не могли ввести помилку друку і порушити ваш код, наприклад, змінивши перше, 0.625але не друге.

    const double MAX_PART_WIDTH = 0.625;
    
    if (partWidth > MAX_PART_WIDTH) {
        // doSomeApiCall ...
    }
    return (partWidth - MAX_PART_WIDTH)
    

    Приклад перерахунків :

    Перерахування можуть допомогти зібрати дані, які належать разом. Якщо ви використовуєте Java, пам’ятайте, що Enums є об’єктами; їх елементи можуть зберігати дані, а ви можете визначити методи, які повертають усі елементи, або перевірити якесь властивість. Тут Enum використовується для побудови іншого Enum:

    public enum EnginePart {
        CYLINDER (100, Materials.STEEL),
        FLYWHEEL (120, Materials.STEEL),
        CRANKSHAFT (200, Materials.CARBON);
    
        private final double maxTemperature;
        private final Materials composition;
        private EnginePart(double maxTemperature, Materials composition) {
            this.maxTemperature = maxTemperature;
            this.composition = composition;
        }
    }
    
    public enum Materials {
        STEEL,
        CARBON
    }
    

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

  2. Модель Стратегії та модель фабричного методу описують , як инкапсулировать «правила» і передати їх іншій об'єкт , який використовує їх (у разі шаблону Factory, використання є що - то будувати, а в разі шаблону стратегії, то використання - все, що завгодно).

    Приклад схеми заводського методу :

    Уявіть , у вас є два типи двигунів: один , де кожна частина має бути підключена до компресора, і один , де кожна частина може бути вільно з'єднана з будь іншими частинами. Адаптовано з Вікіпедії

    public class EngineAssemblyLine {
        public EngineAssemblyLine() {
            EnginePart enginePart1 = makeEnginePart();
            EnginePart enginePart2 = makeEnginePart();
            enginePart1.connect(enginePart2);
            this.addEngine(engine1);
            this.addEngine(engine2);
        }
    
        protected Room makeEngine() {
            return new NormalEngine();
        }
    }
    

    А потім в іншому класі:

    public class CompressedEngineAssemblyLine extends EngineAssemblyLine {
        @Override
        protected Room makeRoom() {
            return new CompressedEngine();
        }
    }
    

    Цікава частина: тепер ваш конструктор AssemblyLine відокремлений від типу двигуна, яким він керує. Можливо, addEngineметоди викликають віддалений API ...

    Приклад стратегії :

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

    class PartWithStrategy:
    
        def __init__(self, func=None) :
            if func:
                self.execute = func
    
        def execute(self):
            # ... call API of quality review ...
            print "Part will be reviewed"
    
    
    def polish():
        # ... call API of polishing department ...
        print "Part will be polished"
    
    
    def paint():
        # ... call API of painting department ...
        print "Part will be painted"
    
    if __name__ == "__main__" :
        strat0 = PartWithStrategy()
        strat1 = PartWithStrategy(polish)
        strat2 = PartWithStrategy(paint)
    
        strat0.execute()  # output is "Part will be reviewed"
        strat1.execute()  # output is "Part will be polished"
        strat2.execute()  # output is "Part will be painted"
    

    Ви можете розширити це до списку дій, які потрібно виконати, а потім по черзі викликати їх із executeметоду. Може це узагальнення можна було б краще описати як модель Builder , але ей, ми не хочемо бути вибагливими, чи не так? :)


2

Можливо, ви хочете використовувати двигун правил. Система правил дає вам DSL (Domain Specific Language), призначений для зрозумілого моделювання критеріїв, необхідних для певного результату, як це пояснено в цьому запитанні .

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

Якщо вам пощастило, існує готова до використання система для мов програмування, яку ви використовуєте.

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


1

Моє рішення цієї проблеми зовсім інше: шари, налаштування та LOP.

Спочатку загортайте API в шар. Знайдіть послідовності викликів API, які використовуються разом, і об'єднайте їх у власні виклики API. Врешті-решт не повинно бути прямих дзвінків до базового API, а лише дзвінки на ваші обгортки. Виклики обгортки повинні починати виглядати як міні-мова.

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

Baseplate.name="Base plate"
Baseplate.length=1032.5
Baseplate.width=587.3

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

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


0

Я не впевнений, що я правильно поставив це питання, але це здається, що ви повинні згрупувати речі в деяких структурах. Скажіть, якщо ви використовуєте C ++, ви можете визначити такі речі, як:

struct SomeParametersClass
{
    int   p1;  // this is for that
    float p2;  // this is a different parameter
    ...
    SomeParametersClass() // constructor, assigns default values
    {
        p1 = 42; // the best value that some guy told me
        p2 = 3.14; // looks like a know value, but isn't
    {
};

struct SomeOtherParametersClass
{
    int   v1;  // this is for ...
    float v2;  // this is for ...
    ...
    SomeOtherParametersClass() // constructor, assigns default values
    {
        v1 = 24; // the best value 
        v2 = 1.23; // also the best value
    }
};

Ви можете створити ці дані на початку програми:

int main()
{
    SomeParametersClass params1;
    SomeOtherParametersClass params2;
    ...

Тоді ваші дзвінки API будуть мати вигляд (якщо ви не можете змінити підпис):

 SomeAPICall( params1.p1, params1.p2 );

Якщо ви можете змінити підпис API, ви можете передати всю структуру:

 SomeAPICall( params1 );

Ви також можете згрупувати всі параметри в більшу обгортку:

struct AllTheParameters
{
    SomeParametersClass      SPC;
    SomeOtherParametersClass SOPC;
};

0

Я здивований, що ніхто більше не згадав про це ...

Ти сказав:

Моя головна мета - вміти передавати комусь своє джерело і давати їм зрозуміти, що я робив, без мого внеску.

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

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

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

var x = y + z;

Або:

//Where bandwidth, which was previously defined is (1000 * Info Rate) / FEC Rate / Modulation * carrier spacing / 1000000
float endFrequency = centerFrequency + (1/2 bandwidth);

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

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

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