如何在程序中管理大量规则和幻数?


21

我是编程的新手(我是行业的机械工程师),并且在停机期间正在开发一个小程序,该程序将根据工厂周围各个人员的输入生成一个(solidworks)零件。

仅基于少量输入(准确地说是6个),我需要进行数百个API调用,每个API调用最多可以包含十二个参数。所有这些都是我在采访处理零件的每个人之后收集的一组规则所产生的。我的代码的规则和参数部分为250行,并且还在不断增加。

那么,保持我的代码可读性和可管理性的最佳方法是什么?如何分隔我所有的幻数,所有规则,算法和代码的程序部分?如何处理非常冗长而细致的API?

我的主要目标是能够在没有我输入的情况下将我的消息传递给某人,并让他们了解我在做什么。


7
您能否提供这些API调用的一些示例?
罗伯特·哈维


“计算机科学中的所有问题都可以通过另一种间接解决方案来解决” – David Wheeler
Phil Frost 2014年

...除了过多级别的间接操作外:)
Dan Lyons

1
如果不查看代码,很难回答您的问题。您可以将代码发布在codereview.stackexchange.com上,并从其他程序员那里获取建议。
吉尔伯特·勒·布朗克

Answers:


26

根据您的描述,您可能想要探索数据库的美好世界。听起来您描述的许多魔术数字(尤其是如果它们是部分依赖的)实际上是数据,而不是代码。如果您可以对数据与零件之间的关系进行分类并为其定义数据库结构,那么运气会更好,并且从长远来看,扩展应用程序会容易得多。

请记住,“数据库”不一定表示MySQL或MS-SQL。存储数据的方式将在很大程度上取决于程序的使用方式,编写方式等。这可能表示SQL类型的数据库,或者可能只是表示格式化的文本文件。


7
同意将数据库中的数据进行编码,尽管看起来他的问题更大。
罗伯特·哈维

是的,如果我要创建一个由完全不同的部分组成的程序,那将是一条路。不过,它只有一部分具有四个略有不同的配置。这绝不是一件大事(除非他们雇用开发人员来制作类似的东西,在这种情况下这无关紧要)。虽然,我想在完成并想重构后,这将是一次很棒的学习经历。
user2785724 2014年

1
听起来像是软编码。数据库是可变状态。根据定义,幻数不是可变的。
Phil Frost

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。然后对常量列表进行排序。以后,要提取所有螺距,可以在文本编辑器中获取列表,并使用find函数,或使用grep之类的工具仅返回包含“ ThreadPitch”的行。


1
还考虑创建Fluent界面
Ian

这是我的代码中的实际行。如果您知道变量名的含义,这里发生了什么(参数x1,y1,z1,x1,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 2014年

您还可以将ctags编辑器集成一起使用以查找常量。
Phil Frost

3
@ user2785724真是一团糟。到底在做什么 它在制作特定长度和深度的凹槽吗?然后,您可以创建一个名为的函数createGroove(length, depth)。您需要实现描述要完成的功能的功能,就像向机械工程师描述功能一样。那就是识字编程的目的。
Phil Frost 2014年

这就是API调用来绘制一个三维空间线。6个参数中的每个参数都在程序中的不同行上。整个API太疯狂了。我不知道在哪里弄乱,所以我就到了。如果您知道API调用是什么及其参数,则可以使用您熟悉的参数来查看端点是什么,并能够将其重新关联到零件。如果您想熟悉SolidWorks,该API绝对是迷宫般的。
user2785724

4

我认为您的问题可以简化为:如何构造计算?请注意,您要管理“一组规则”(即代码)和“一组魔术数字”(即数据)。(您可以将它们视为“嵌入在代码中的数据”,但它们仍然是数据)。

此外,使您的代码“对其他人而言不可理解”实际上是所有编程范例的总体目标(例如,对于陈述相同目标的软件作者,请参见Kent Beck的“ 实现模式 ”或Robert C. Martin的“ 简洁的代码 ”)。就像您一样,对于任何程序)。

这些书中的所有提示都将适用于您的问题。让我提取一些专门针对“幻数”和“规则集”的提示:

  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,请记住枚举是对象。它们的元素可以保存数据,并且您可以定义返回所有元素的方法或检查某些属性。这里,一个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,也没有人可以引入称为“ asdfasdf”的EnginePart,就像要对内容进行检查的字符串一样。

  2. 策略模式工厂方法模式描述了如何封装“规则”,并将其传递给另一个对象,利用他们的(在工厂模式的情况下,使用正在建设的东西;在战略模式的情况下,用法随您便)。

    工厂方法模式的示例

    想象一下,你有两种类型的发动机:一个其中每个部分具有连接到压缩机,以及一个其中每个部分可以自由连接到任何其他地方。改编自维基百科

    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示例,改编自Stack Overflow

    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(特定领域语言),该DSL被设计为以某种易于理解的方式为特定结果建模所需的标准,如本问题所述

根据规则引擎的实现,甚至可以在不重新编译代码的情况下更改规则。由于规则是用自己的简单语言编写的,因此用户也可以更改它们。

如果幸运的话,可以使用一种现成的规则引擎来处理所使用的编程语言。

缺点是您必须熟悉规则引擎,如果您是编程初学者,这可能很难。


1

我对这个问题的解决方法是完全不同的:图层,设置和LOP。

首先将API包装在一层中。查找一起使用的API调用序列,并将其组合到您自己的API调用中。最终,不应直接调用基础API,而应直接调用包装器。包装器调用应开始看起来像迷你语言。

第二,实施“设置经理”。这是一种将名称与值动态关联的方法。这样的事情。另一种迷你语言。

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

最后,实现自己的迷你语言来表达设计(这是面向语言的编程)。贡献规则和设置的工程师和设计人员应该可以理解这种语言。想到这种产品的第一个示例是Gnuplot,但还有许多其他示例。您可以使用Python,但就我个人而言,不会。

我了解这是一种复杂的方法,可能会因您的问题或要求您尚未掌握的技能而被矫kill过正。这就是我要做的。


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的评论处理得很好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.