使用Ada进行软件工程:存根;单独和编译单位

时间:2010-07-11 13:38:33

标签: compilation ada stub

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

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

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然后编译stub 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语句,稍后再返回后者。这是软件工程在实践中的完成方式吗?

非常感谢...

3 个答案:

答案 0 :(得分:7)

Q1:那是优秀的练习。

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

Q2:我认为AdaGIDE实际上使用GNAT编译器进行所有编译,因此它实际上是负责可接受文件名的GNAT。 (这可以配置,但除非你有一个非常令人信服的理由这样做,简单地使用GNAT / AdaGIDE的文件命名约定要简单得多。)但是,与你的问题更相关的是,没有强有力的理由包括父母单位作为单独单位名称的一部分。但是看看Q3的答案......

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

答案 1 :(得分:4)

将问题分解为组件(包)是很正常的,每个组件都支持不同的方面。如果你已经学会了Ada,那么首先编写软件包的规格是正常的,并且(或许与你自己)争论为什么这是正确的设计,然后实现它们。我认为,这在任何支持规范和实体的语言中都是正常的 - 例如,C。

我个人会在去的时候检查编辑,只是为了确保我没有做任何愚蠢的事情。

至于分离 - 一个(不是很好)的原因是减少杂乱,停止单位过长。另一个原因(对于我编写的代码生成器)是因为代码生成器不需要担心在UML模型中保留开发人员的手写代码;所有代码主体都是分开的。第三种可能是依赖于环境的实现(例如,Windows vs Unix),在这种情况下,您可以让编译器为每个环境看到不同版本的单独主体(尽管人们通常使用库包)。

编译器有自己的关于文件名的规则,以及可以编译的顺序。当GNAT看到

procedure Foo is
   procedure Bar is separate;

它希望在名为foo.adb的文件中找到Foo的主体,在foo-bar.adb中找到Bar的主体(我相信,你可以告诉它不同的是gnatmake的包{{1} } - 但它可能不值得麻烦)。最好顺其自然;

Naming

很清楚。

您可以编译separate (Foo) procedure Bar is ,这将进行全面分析并捕获代码中的几乎所有错误;但GNAT无法自行生成代码。相反,当您编译foo-bar.adb时,它包含一个生成的目标文件中的所有单独的主体。这样做肯定不是错误

使用GNAT,无需担心编译顺序,您可以按照自己喜欢的顺序进行编译。但最好使用foo.adb并让计算机承受压力!

答案 2 :(得分:1)

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

更为正常
begin
   null;
end;

所有功能都实现为:

begin
   return The_Return_Type'first; --'
end;

至于分居......我不喜欢他们。对我来说,我更愿意遵循规则,即包的所有代码都在其包体中。如果由于某种原因例程 huge ,则分离是可以接受的,但在这种情况下,更好的解决方案几乎总是重构代码。所以每次看到一个,都是一个大红旗。

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