微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

将代码组织成模块化的、自包含的块

如何解决将代码组织成模块化的、自包含的块

我正在寻求智慧的帮助。 :-) 这不是关于测试本身的技术帮助,而是代码组织问题。 我正在使用 libopencm3 HAL 和 ceedling 作为测试套件使用 STM32 开发一个项目。

我保持这个问题简短,只是引用了一小部分来证明这一点,显然代码中还有其他这样的模块。

深入探讨这个问题,主函数调用一个函数“Init”,它存在于它自己的模块“Init”中。 Init 从 IO 模块调用几个函数 - IOInit 和 IOBlink,它们初始化 GPIO 并以特定模式闪烁 GPIO。

IOInit和IOBlink的形式如下

IOInit(GPIO)
IOBlink(PATTERN)

其中 GPIO 和 PATTERN 分别是 STATUS_LEDS、POWERUP 之类的东西,可以是枚举或 #define,具体取决于我选择如何构造它们。

所以第一个问题就在这里,在 IOInit 和 IOBlink 的参数定义中,这里有什么意义?如果我想要一些在这里冗长但稍后在较低级别的函数调用解决的参数?我将如何定义论点?作为 uint8_t,枚举,#define?

所以它们看起来像

IOInit(GPIO)
{
    //Something like this 
    if (GPIO == STATUS_LED)
        {
            InitIO(STATUS_LED_PORT,OUTPUT,STATUS_LED);
        }
    else (GPIO == MODE_IO)
        {
            //Do something else..
        }
    ...
}

IOBlink(PATTERN)
{
    if (PATTERN == POWERUP)
        {
            //GPIOToggle(Pin,delay,repetitions)
            GPIOToggle(STATUS_LED,500,4);
        }
         else if (PATTERN == ERROR)
          // Do something else
}

现在,IOInit 和 IOBlink 在位于 HAL 正上方的“DriverIO”模块中调用它们的低级函数。 DriverIO 具有相应的功能 - InitIO 和 GPIOToggle,它们可能看起来像这样

InitIO(PORT,Mode,Pins)
{
    rcc_periph_clock_enable(PORT);
    gpio_set_mode(PORT,GPIO_MODE_OUTPUT_2_MHZ,Pins);
    //Need to find a way to resolve PORT,Mode and Pins into a HAL compatible format
}

GPIOToggle(Pin,repetitions)
{
    for (uint8_t i = 0; i<= repetitions,i++)
        {
            gpio_toggle(STATUS_LED_PORT,Pin);
            delay_ms(delay);
        }
}

现在有一些我认为可以而且应该改进的明显明显的地方。

所有三层,Init、IO 和 DriverIO 都需要一个共同的包含,其中包含 STATUS_LED_PORT 和 STATUS_LED 的定义 - 这闻起来很糟糕。 这个想法是让每一层更加模块化和自包含,如果我必须在所有三层中都包含 BSP.h 或类似的东西,这就会失败。

2a。 DriverIO 无法模拟和测试 - 因为它位于 HAL 的正上方。 - 解决这个问题的一种方法,从我在这里读到并且可以理解的是添加一个“垫片层”或一个“包装器”,它基本上位于 DriverIO 和 HAL 之间,我可以简单地仅将标头包含在“包装器”中DriverIO,模拟包装器并测试 DriverIO。

2b。垫片层还有另一个问题。低级 HAL 使用 typedef 枚举作为参数,填充层和以上层将无法访问这些参数。那么 shim 层中的翻译函数会是什么样子的??

例如 - libopenCM3 HAL 中的时钟使能函数如下所示

void rcc_periph_clock_enable(enum rcc_periph_clken clken);

它在 shim 层中会是什么样子??考虑到 DriverIO 不知道 enum rcc_periph_clken 是什么样的??

整体架构在设计时感觉还可以……但在实施过程中它真的崩溃了。原则上,main 只调用状态函数,每个状态函数调用存在大量控制逻辑的中间层函数,中间层调用位于 HAL 上的驱动层函数。 我为这篇长文章道歉,但我在设计和架构阶段花了大量时间在这方面工作,我无法直接和连贯地思考。我正在遭受一些分析瘫痪。这可能是一个非常简单的解决方案,但我无法直视。

再次,如果我没有多大意义,我深表歉意。我已经盯着这个看了好几天了,我认为对其他人来说没有多大意义(如果有的话)。我试图让它变得可以理解,但这可能只是我缺乏睡眠的原因。请让我知道是否有一个特定的部分,我可以用更好的方式解释并说清楚。

真诚感谢任何帮助。

解决方法

所以第一个问题就在这里,在 IOInit 和 IOBlink 的参数定义中,这里有什么意义??

对于 IOInit,它将需要端口、引脚和掩码。如果您允许在同一端口上使用其他不相关的功能,或者驱动程序对其具有独占控制权,则必须小心。从程序中多个不相关的地方更新 GPIO 寄存器是一个坏主意,因为这可能导致竞争条件和其他类似的奇怪现象。

根据我的经验,在简单的 GPIO 上编写抽象层往往弊大于利。

在您的情况下,GPIO 驱动程序实际上应该是一个“LED 闪烁应用程序模块”,它比 GPIO 的级别稍高,因为它还必须使用定时器和/或 PWM 硬件外设。因此,请考虑将其命名为其他名称。

对不同的闪烁模式使用枚举是个好主意。然后,驱动程序内部可以将该枚举用作查找表等的索引,其中指定了时序和模式。除非您出于某种原因希望计时器周期是可变的,否则它们也需要作为参数传递。

现在,IOInit 和 IOBlink 在位于 HAL 正上方的“DriverIO”模块中调用它们的低级函数。

为什么!?然后你有一些 3-4 个抽象层,用于完全简单的东西,不需要抽象。这是非常糟糕的设计!

1 层有意义,处理 LED 图案的一层。其余无用的臃肿软件中间层必须消失:它们只占用资源并充当错误的来源。您应该直接从 LED 模式模块访问寄存器。 ST 所谓的“HAL 库”通常是有害的,应避免使用。这意味着您必须重新设计其中的大部分内容。

DriverIO 不能被模拟和测试

以在功能、可读性、可维护性和速度方面最有意义的方式设计您的程序。不要设计你的程序来适应某些 TDD 流行语测试套件模板。在设计代码时,您可以通过各种方式牢记测试,但您不应该让它支配设计。

例如,您可以设计特定数量的测试函数,而不是模拟,这些函数与驱动程序一起提供并且可以直接访问驱动程序的私有成员,但仅在调试版本中链接。这允许进行比模拟更深入的测试,模拟实际上只是一个“黑盒”测试。

值得注意的是,在某种模式后使 LED 闪烁是非常容易测试的,你真的不需要功能模拟或任何东西,只需要强制性的示波器。这也是对所有嵌入式固件进行基准测试的正确工具。


关于 TDD 和一般测试,它应该是这样的:

规范 -> 程序设计 -> 程序实现,包括测试 -> 测试。

因此,对于每个需求,程序中都有一个模块,并且对于每个这样的模块,您都有一个测试。测试是为了查看代码是否符合其编写要求,以便产品符合指定的功能。

这意味着您必须有一个规范才能开始。程序设计不应该是突然发明的。你不应该实现你没有用的功能。测试应该测试产品的需求,而不仅仅是测试一般的随机内容或适合某种“测试套件”。

但请注意,程序设计是,它需要大量的经验,并且只能学习到一定程度,其余的你必须边做边学。您完成设计、编写程序,然后最终意识到您可以以更好的方式完成设计,这是很正常的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。