防止滥用专为运输而设计的结构

发布于 2024-10-23 20:55:08 字数 828 浏览 3 评论 0原文

我开发的产品有多个组件,其中大部分是用 C++ 编写的,但有一个是用 C 编写的。我们经常遇到这样的场景:一条信息通过 IPC 流经每个组件。

我们使用结构体定义这些消息,以便将它们打包到消息中并通过消息队列发送它们。这些结构仅设计用于“传输”目的,并且以仅服务于该目的的方式编写。我遇到的问题是:程序员保留该结构并将其用作信息的长期容器。

在我看来,这是一个问题,因为:

1)如果我们更改传输结构,那么它们的所有代码都会被破坏。这里应该进行封装,这样我们就不会遇到这种情况。

2)消息结构非常尴尬,并且仅设计用于传输信息...该结构似乎不太可能恰好是这些其他组件访问此数据(长期)的最方便形式。

我的问题是:如何以编程方式防止这种误用?我想强制要求这些结构只能用于运输。

编辑:我将尽力在这里提供一个示例:

struct smallElement {
    int id;
    int basicFoo;
};

struct mediumElement {
    int id;
    int basicBar;
    int numSmallElements;
    struct smallElement smallElements[MAX_NUM_SMALL];
};

struct largeElement {
    int id;
    int basicBaz;
    int numMediumElements;
    struct mediumElement[MAX_NUM_MEDIUM];
};

效果是人们只是保留“largeElement”,而不是从largeElement中提取他们需要的数据并将其放入满足他们需求的类中。

I work on a product which has multiple components, most of them are written in C++ but one is written in C. We often have scenarios where a piece of information flows through each of the components via IPC.

We define these messages using structs so we can pack them into messages and send them over a message queue. These structs are designed for 'transport' purposes only and are written in a way that serves only that purpose. The problem I'm running into is this: programmers are holding onto the struct and using it as a long-term container for the information.

In my eyes this is a problem because:

1) If we change the transport structure, all of their code is broken. There should be encapsulation here so that we don't run into this scenario.

2) The message structs are very awkward and are designed only to transport the information...It seems highly unlikely that this struct would also happen to be the most convenient form of accessing this data (long term) for these other components.

My question is: How can I programmatically prevent this mis-usage? I'd like to enforce that these structures are only able to be used for transport.

EDIT: I'll try to provide an example here the best I can:

struct smallElement {
    int id;
    int basicFoo;
};

struct mediumElement {
    int id;
    int basicBar;
    int numSmallElements;
    struct smallElement smallElements[MAX_NUM_SMALL];
};

struct largeElement {
    int id;
    int basicBaz;
    int numMediumElements;
    struct mediumElement[MAX_NUM_MEDIUM];
};

The effect is that people just hold on to 'largeElement' rather than extracting the data they need from largeElement and putting it into a class which meets their needs.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

无妨# 2024-10-30 20:55:08

当我定义消息结构(在 C++ 中,在 C 中无效)时,我确保:

  1. 消息对象是可复制的
  2. 消息对象必须仅构建一次
  3. 消息对象不能构建后更改

我不确定消息是否仍然是 Pod,但我想从内存的角度来看这是等效的。

要实现这一目标,需要做的事情:

  1. 有一个唯一的构造函数,它设置每个成员都
  2. 拥有所有成员私有的
  3. 成员访问器

例如,您可以拥有这个:

struct Message
{
   int id;
   long data;
   Info info;
};

那么您应该拥有这个:

class Message // struct or whatever, just make sure public/private are correctly set
{
public:
   Message( int id, long data, long info ) : m_id( id ), m_data( data ), m_info( info ) {}

   int id() const { return m_id; }
   long data() const { return m_data; }
   Info info() const { return m_info; }

private:

   int m_id;
   long m_data;
   Info m_info;

};

现在,用户将能够构建消息,以从中读取,但不会长时间更改它,从而使其无法用作数据容器。然而,他们可以存储一条消息,但以后无法更改它,因此它仅对记忆有用。


或者....您可以使用“黑匣子”

  1. 如果尚未分离库中的消息层,则将其分离。
  2. 客户端代码根本不应该暴露给消息结构定义。因此,不要提供标题,或隐藏它们或其他内容。
  3. 现在,提供发送消息的函数,它将(在内部)构建消息并发送它们。这甚至有助于阅读代码。
  4. 接收消息时,提供通知客户端代码的方式。但不要直接提供pessages!只需将它们保存在库中的某个地方(也许是暂时的或使用生命周期规则或其他东西),也许是在某种管理器中,无论如何,但一定要将它们保存在黑匣子内。只需提供一种消息标识符即可。
  5. 提供函数来从消息中获取信息而不暴露结构。要实现这一目标,您有多种方法。在这种情况下,我将提供聚集在命名空间中的函数。这些函数将询问消息标识符作为第一个参数,并且仅返回消息中的一个数据(如果需要,可以是完整的对象)。

这样,用户就无法使用结构作为数据容器,因为它们没有自己的定义。他们只能访问数据。

这样做有两个问题:明显的性能成本,并且编写和更改显然更繁重。也许使用一些代码生成器会更好。 Google Protobuf 在这个领域充满了好主意。


但最好的方法是让他们明白为什么他们的做法迟早会被打破。

When I define message structures (in C++, not valid in C ) I make sure that :

  1. the message object is copiable
  2. the message object have to be built only once
  3. the message object can't be changed after construction

I'm not sure if the messages will still be pods but I guess it's equivalent from the memory point of view.

The things to do to achieve this :

  1. Have one unique constructor that setup every members
  2. have all memebers private
  3. have const member accessors

For example you could have this :

struct Message
{
   int id;
   long data;
   Info info;
};

Then you should have this :

class Message // struct or whatever, just make sure public/private are correctly set
{
public:
   Message( int id, long data, long info ) : m_id( id ), m_data( data ), m_info( info ) {}

   int id() const { return m_id; }
   long data() const { return m_data; }
   Info info() const { return m_info; }

private:

   int m_id;
   long m_data;
   Info m_info;

};

Now, the users will be able to build the message, to read from it, but not change it in the long way, making it unusable as a data container. They could however store one message but will not be able to change it later so it's only useful for memory.


OR.... You could use a "black box".

  1. Separate the message layer in a library, if not already.
  2. The client code shouldn't be exposed at all to the message struct definitions. So don't provide the headers, or hide them or something.
  3. Now, provide functions to send the messages, that will (inside) build the messages and send them. That will even help with reading the code.
  4. When receiving messages, provide a way to notify the client code. But don't provide the pessages directly!!! Just keep them somewhere (maybe temporarly or using a lifetime rule or something) inside your library, maybe in a kind of manager, whatever, but do keep them INSIDE THE BLACK BOX. Just provide a kind of message identifier.
  5. Provide functions to get informations from the messages without exposing the struct. To achieve this you have several ways. I would be in this case, I would provide functions gathered in a namespace. Those functions would ask the message identifier as first parameter and will return only one data from the message (that could be a full object if necessary).

That way, the users just cant use the structs as data containers, because they dont have their definitions. they conly can access the data.

There a two problems with this : obvious performance cost and it's clearly heavier to write and change. Maybe using some code generator would be better. Google Protobuf is full of good ideas in this domain.


But the best way would be to make them understand why their way of doing will break soon or later.

打小就很酷 2024-10-30 20:55:08

程序员这样做是因为这是获得他们想要的功能的最简单的途径,阻力最小。如果数据位于具有适当访问器的类中,他们可能会更容易访问数据,但随后他们必须编写该类并编写转换函数。

利用他们的懒惰,为他们制定最简单的道路,做正确的事。对于您创建的每个消息结构,创建一个相应的类,使用带有转换方法的良好接口来存储和访问数据,使其成为将消息放入类中的一个行。由于该类将具有更好的访问器方法,因此他们使用它比做错误的事情更容易。例如:

msg_struct inputStruct = receiveMsg();
MsgClass msg(inputStruct);
msg.doSomething()
...
msg_struct outputStruct = msg.toStruct();

与其想方设法强迫他们不走捷径,不如让他们以最简单的方式使用代码。事实上,多个程序员正在使用这种反模式,这让我认为库中缺少一个应该由库提供来适应这一点的部分。您正在将这个必要组件的创建推回到代码的用户身上,并且不喜欢他们提出的解决方案。

The programmers are doing this because its the easiest path of least resistance to getting the functionality they want. It may be easier for them to access the data if it were in a class with proper accessors, but then they'd have to write that class and write conversion functions.

Take advantage of their laziness and make the easiest path for them be to do the right thing. For each message struct you creat, create a corresponding class for storing and accessing the data using a nice interface with conversion methods to make it a one liner for them to put the message into the class. Since the class would have nicer accessor methods, it would be easier for them to use it than to do the wrong thing. eg:

msg_struct inputStruct = receiveMsg();
MsgClass msg(inputStruct);
msg.doSomething()
...
msg_struct outputStruct = msg.toStruct();

Rather than find ways to force them to not take the easy way out, make the way you want them to use the code the easiest way. The fact that multiple programmers are using this antipattern, makes me think there is a piece missing to the library that should be provided by the library to accomodate this. You are pushing the creation of this necessary component back on the users of the code, and not likeing the solutions they come up with.

活泼老夫 2024-10-30 20:55:08

您可以根据 const 引用来实现它们,以便服务器端构造传输结构,但客户端使用只允许对它们进行 const 引用,并且不能实际实例化或构造它们来强制执行您想要的用法。

不幸的是,如果没有您的消息、打包、正确用法和不正确用法的代码片段,我无法真正提供有关如何在您的情况下实现此功能的更多详细信息,但我们在数据模型中使用类似的内容来防止不当使用。我还导出并提供模板存储类,以减轻客户端在确实想要存储检索到的数据时使用的消息的数量。

You could implement them in terms of const references so that server side constructs the transport struct but client usage is only allowed to have const references to them and can't actually instantiate or construct them to enforce the usage you want.

Unfortunately without a code snippets of your messages, packaging, correct usage, and incorrect usage I can't really provide more detail on how to implement this in your situation but we use something similar in our data model to prevent improper usage. I also export and provide template storage classes to ease the population from the message for client usage when they do want to store the retrieved data.

哭泣的笑容 2024-10-30 20:55:08

通常将传输消息定义为结构是一个坏主意。最好为其定义“正常”(对程序员有用)结构和序列化器/反序列化器。为了自动化序列化器/反序列化器编码,可以在单独的文件中使用宏定义结构,并自动生成 typedef 结构和序列化器/反序列化器(增强预处理器库也可能有帮助)

usually there is a bad idea to define transport messages as structures. It's better to define "normal" (useful for programmer) struct and serializer/deserializer for it. To automate serializer/deserializer coding it's possible to define structure with macroses in a separate file and generate typedef struct and serializer/deserializer automatically(boost preprocessor llibrary may also help)

小红帽 2024-10-30 20:55:08

我不能说比这更简单:使用协议缓冲区。从长远来看,它会让你的生活变得更加轻松。

i can't say it more simple than this: use protocol buffers. it will make your life so much easier in the long run.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文