4

我具有机械工程背景,但我有兴趣与 Ada 一起学习良好的软件工程实践。我有几个疑问。

Q1。如果我理解正确,那么有人可以编写一个包规范(广告)文件,编译它,然后编译使用该包的主程序。稍后,当一个人知道要在包体中包含什么时,就可以编写和编译后者。之后,现在可以运行主程序。我已经尝试过了,我想确认这是一个很好的做法。

Q2。我的第二个问题是关于存根(子单元)和 SEPARATE 的使用。假设我有一个主程序如下:

    WITH Ada.Float_Text_IO;
    WITH Ada.Text_IO;
    WITH Ada.Integer_Text_IO;

    PROCEDURE TEST2 IS
    A,B      : FLOAT;
    N        : INTEGER;

    PROCEDURE INPUT(A,B: OUT FLOAT; N: OUT INTEGER) IS SEPARATE;

    BEGIN -- main program
      INPUT(A,B,N);
      Ada.Float_Text_IO.Put(Item => A);
      Ada.Text_IO.New_line;
      Ada.Integer_Text_IO.Put(Item => N);
    END TEST2;

然后我将过程 INPUT 放在一个单独的文件中:

separate(TEST2)
PROCEDURE INPUT(A,B: OUT FLOAT; N: OUT INTEGER) IS
   BEGIN
      Ada.Float_Text_IO.Get(Item => A);
      Ada.Text_IO.New_line;
      Ada.Float_Text_IO.Get(Item => B);
      Ada.Text_IO.New_line;
      Ada.Integer_Text_IO.Get(Item => N);
   END INPUT;

我的问题:

a) AdaGIDE 建议我将 INPUT 过程文件保存为 input.adb。但是在编译主程序 test2 时,我收到警告:

warning: subunit "TEST2.INPUT" in file "test2-input.adb" not found
cannot generate code for file test2.adb (missing subunits)

对于 AdaGIDE 来说,这更像是一个错误,因为上述警告出现在消息之前:

Compiling...
Done--error detected

所以我将 input.adb 文件重命名为 test2-input.adb,正如 AdaGIDE 在编译时向我建议的那样。现在在编译主文件时,我没有任何警告。我现在的问题是可以写吗

PROCEDURE INPUT(A,B: OUT FLOAT; N: OUT INTEGER) IS

就像我在子单元文件 test2-input.adb 中所做的那样,还是写一个更具描述性的术语更好

PROCEDURE TEST2-INPUT(A,B: OUT FLOAT; N: OUT INTEGER) IS

强调程序输入有一个父程序 test2 ?这个想法来自 AdaGIDE 提示我关于 test2-input.adb ,正如我上面提到的。

b)我的下一个问题:

如果我理解编译顺序,那么我应该先编译主文件 test2.adb ,然后编译存根 test2-input.adb 。在编译存根时,我收到错误消息:

cannot generate code for file test2-input.adb (subunit)
Done--error detected

但是,我现在可以为 test2.adb 进行绑定和链接并运行程序。

我想知道我尝试编译存根 test2-input.adb 是否做错了,还是不应该编译?

Q3。有子单位有什么用?只是将一个大程序分解成更小的部分吗?我知道如果没有在子单元中的 BEGIN 和 END 之间放置任何语句,则会出现错误。所以这意味着人们总是必须在那里发表声明。如果以后想写语句,总是可以在子单元中的 BEGIN 和 END 之间放置一个 NULL 语句,稍后再回到后者。这就是软件工程在实践中的完成方式吗?

非常感谢...

4

3 回答 3

7

Q1:这是很好的做法。

通过将包规范视为规范,您可以将其提供给其他开发人员,以便他们知道如何与您的代码交互。

Q2:我相信 AdaGIDE 实际上使用 GNAT 编译器进行所有编译,所以实际上是 GNAT 负责可接受的文件名。(这可以配置,但除非您有非常令人信服的理由这样做,否则简单地使用 GNAT/AdaGIDE 的文件命名约定要简单得多。)不过,与您的问题更相关的是,没有充分的理由包含父级单位作为单独单位名称的一部分。但请参阅 Q3 的答案...

Q3:Ada 的第一个版本——Ada 83——引入了子单元,部分是为了帮助模块化代码,并允许延迟开发和编译。然而,Ada 软件开发实践几乎放弃了子单元的使用,所有的过程/函数/任务/等主体都简单地全部维护在包的主体中。它们仍在某些领域中使用,例如可能需要特定于平台的子程序版本,但在大多数情况下它们很少使用。它留下了更少的文件来跟踪,并将包的实现代码放在一起。因此,我强烈建议您忽略子单元功能并将所有实现代码放在包体中。

于 2010-07-11T18:59:36.037 回答
4

It's pretty normal to split a problem up into component parts (packages), each supporting a different aspect. If you've learnt Ada, it'd be normal to write the specs of the packages first, argue (perhaps with yourself) why that's the right design, and then implement them. And this would be normal, I think, in any language that supports specs and bodies - for example, C.

Personally I would do check compilations as I went, just to make sure I'm not doing anything stupid.

As for separates - one (not very good) reason is to reduce clutter, to stop the unit getting too long. Another reason (for a code generator I wrote) was so that the code generator didn't need to worry about preserving developers' hand-written code in the UML model; all code bodies were separates. A third might be for environment-dependent implementation (eg, Windows vs Unix), where you'd let the compiler see a different version of the separate body for each environment (people normally use library packages for this, though).

Compilers have their own rules about file names, and what order things can be compiled in. When GNAT sees

procedure Foo is
   procedure Bar is separate;

it expects to find Foo's body in a file named foo.adb and Bar's body in foo-bar.adb (you can, I believe, tell it different - gnatmake's package Naming - but it's probably not worth the trouble). It's best to go with the flow here;

separate (Foo)
procedure Bar is

is clear enough.

You can compile foo-bar.adb, and that will do a full analysis and catch almost all errors in the code; but GNAT can't generate code for this on its own. Instead, when you compile foo.adb it includes all the separate bodies in the one generated object file. It certainly isn't wrong to do this.

With GNAT, there's no need to worry about compilation order, you can compile in any order you like. But it's best to use gnatmake and let the computer take the strain!

于 2010-07-11T19:09:12.310 回答
1

您确实可以按照您描述的方式工作,当然除非您的程序在所有包体都有某种实现之前不会链接。出于这个原因,我认为编写一个虚拟包体更正常,所有程序都实现为:

begin
   null;
end;

并且所有功能都实现为:

begin
   return The_Return_Type'first; --'
end;

至于分开......我不喜欢它们。对我来说,我更愿意遵循一个包的所有代码都在它的包体中的规则。如果由于某种原因例程很大,那么分隔是勉强可以接受的,但在这种情况下,更好的解决方案几乎总是重构您的代码。所以任何时候我看到一个,这是一个很大的危险信号。

至于文件名,这是一个 gnat 问题,而不是 Ada 问题。Gnat 对编译器采取了不同寻常的立场,即文件内容的名称决定了文件本身必须命名的内容。世界上可能还有其他编译器可以做到这一点,但我在 30 年的编码中还没有找到一个。

于 2010-07-12T13:17:30.427 回答