Как мне управлять очень большим набором правил и магических чисел в моей программе?


21

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

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

Итак, каков наилучший способ сделать мой код читабельным и управляемым? Как разделить все мои магические числа, все правила, алгоритмы и процедурные части кода? Как мне работать с очень многословным и детальным API?

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


7
Можете ли вы привести некоторые примеры этих вызовов API?
Роберт Харви


«Все проблемы в информатике могут быть решены с помощью другого уровня косвенности» - Дэвид Уилер
Фил Фрост

... за исключением слишком большого количества уровней косвенности :)
Дэн Лайонс

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

Ответы:


26

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

Имейте в виду, что «базы данных» не обязательно означают MySQL или MS-SQL. То, как вы храните данные, во многом зависит от того, как используется программа, как вы ее пишете и т. Д. Это может означать базу данных типа SQL или просто форматированный текстовый файл.


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

Если бы я создавал программу, которая состоит из совершенно разных частей, да, это был бы путь. Это всего лишь одна часть с четырьмя слегка отличающимися конфигурациями. Это никогда не будет огромной вещью (если они не нанимают разработчика, чтобы сделать что-то подобное, в этом случае это не имеет значения). Хотя, я думаю, это был бы отличный учебный опыт после того, как я закончил и хотел провести рефакторинг.
user2785724

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}

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

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


1
Также подумайте о создании интерфейса Fluent
Ian

Вот фактическая строка из моего кода. Имеет ли смысл то, что здесь происходит (аргументы x1, y1, z1, x2, y2, z2 как double), если вы знали, что означают имена переменных? .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)
user2785724

Вы также можете использовать ctags с интеграцией редактора, чтобы найти константы.
Фил Фрост

3
@ user2785724 Это беспорядок. Что это делает? Это делает паз определенной длины и глубины? Тогда вы можете создать функцию с именем createGroove(length, depth). Вам необходимо реализовать функции, которые описывают то, что вы хотите выполнить, как вы бы описали их для инженера-механика. Вот что такое грамотное программирование.
Фил Фрост

Это вызов API для рисования одной линии в трехмерном пространстве. Каждый из 6 аргументов находится в разных строках программы. Весь API сумасшедший. Я не знал, где сделать беспорядок, поэтому я сделал это там. Если бы вы знали, что такое вызов API и его аргументы, вы бы увидели, какие были конечные точки, используя параметры, знакомые вам, и смогли бы связать его обратно с деталью. Если вы хотите ознакомиться с SolidWorks, API-интерфейс абсолютно лабиринтный.
user2785724

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 отделен от того, какой тип Engine он обрабатывает. Возможно 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 (предметно-ориентированный язык), который предназначен для моделирования критериев, необходимых для определенного результата понятным способом, как объясняется в этом вопросе .

В зависимости от реализации механизма правил, правила могут даже быть изменены без перекомпиляции кода. И поскольку правила написаны на их собственном простом языке, они также могут быть изменены пользователями.

Если вам повезет, есть готовый механизм правил для используемого вами языка программирования.

Недостатком является то, что вы должны познакомиться с механизмом правил, который может быть сложным, если вы начинающий программист.


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.