Esse documento Rational Unified Process - Diretrizes de Programação Ada é um gabarito que pode ser utilizado para derivar um padrão de codificação para sua própria organização. Ele especifica como os programas Ada devem ser gravados. Seu público alvo inclui todos os designers de software aplicativo e desenvolvedores que utilizam o Ada como o idioma de implementação ou como um idioma de design para especificar as interfaces ou as estruturas de dados, por exemplo.
As regras descritas nesse documento abrangem a maioria dos aspectos da codificação. As regras gerais se aplicam ao layout do programa, convenções de nomenclatura e uso dos comentários. As regras específicas se aplicam aos recursos Ada selecionados e especificam construções proibidas, padrões de uso recomendados e dicas gerais para aprimorar a qualidade do programa.
Há um determinado grau de sobreposição entre as diretrizes de design do projeto e as diretrizes de programação presentes e isso é intencional. Várias regras de codificação, especialmente na área das convenções de nomenclatura, foram introduzidas para suportar ativamente e reforçar uma abordagem orientada ao objeto para o design do software.
As diretrizes foram escritas originalmente para Ada 83. Elas incluem as regras de compatibilidade com Ada 95, mas não com diretrizes específicas para uso do novos recursos do idioma introduzido no padrão de idioma revisado, como os tipos marcados, unidades filhas e tipos decimais.
A organização do documento segue abertamente a estrutura do Manual de Referência Ada [ISO 8052].
O Capítulo 2, Introdução, explica os princípios fundamentais nos quais as diretrizes são baseadas e apresenta uma classificação das diretrizes.
O Capítulo 3, Layout do Código, trata da organização visual geral do texto dos programas.
O Capítulo 4, Comentários, fornece orientação sobre como utilizar os comentários para documentar o código de uma forma estruturada, útil e preservável.
O Capítulo 5, Convenções de Nomenclatura, fornece algumas regras gerais sobre as entidades do idioma de nomenclatura e os exemplos. Esse capítulo deve ser ajustado para se adequar às necessidades do seu projeto específico ou organização.
O Capítulo 6, Declarações, e o Capítulo 7, Expressão e Instruções, fornecem avisos adicionais sobre cada tipo de construção de idioma.
O Capítulo 8, Problemas de Visibilidade e o Capítulo 9, Estrutura do Programa e Problemas de Compilação, fornecem a orientação sobre a estruturação global e a organização dos programas.
O Capítulo 10, Simultaneidade, trata do tópico especializado quanto à utilização de tarefas e recursos relacionados ao tempo da linguagem.
O Capítulo 11, Tratamento de Erro e Exceções fornece alguma orientação sobre como utilizar ou não utilizar a exceção para tratar dos erros de uma forma sistemática e reduzida.
O Capítulo 12, Programação de Baixo Nível, trata dos problemas das cláusulas de representação.
O Capítulo 13, Resumo, recapitula as diretrizes mais importantes.
Esse documento substitui Diretrizes Ada: Recomendações para os Designers e Programadores, Nota do Aplicativo Nº15, Rational, Santa Clara, CA., 1990.
O Ada foi explicitamente projetado para suportar o desenvolvimento do software de alta qualidade, confiável, reutilizável e portátil [ISO 87, seção. 1.3]. No entanto, nenhuma linguagem de programação de sua propriedade pode assegurar que isso seja alcançado. A programação precisa ser feita como parte de um processo bem disciplinado.
Limpo, o código fonte Ada compreensível é o principal objetivo da maioria das diretrizes fornecidas aqui. Esse é um fator principal de contribuição para a confiabilidade e a sustentabilidade. O que entende-se por código compreensível e limpo pode ser capturado nos três seguintes princípios fundamentais.
Superior a sua existência, o código fonte é lido com mais freqüência do que é escrito, especialmente as especificações. Em condições ideais, o código deve ser lido como uma descrição de idioma inglês daquilo que está sendo feito, com o benefício adicional que ele executa. Os programas são mais escritos para as pessoas do que para os computadores. O código de leitura é um processo mental complexo que pode ser suportado pela uniformidade, também conhecido nesse guia como princípio da surpresa mínima. Um estilo uniforme em um projeto inteiro é uma razão principal para que uma equipe de desenvolvedores de software concorde com os padrões de programação e não deve ser percebido como algum tipo de punição ou como um obstáculo à criatividade e à produtividade.
Outro importante princípio sustentado nesse guia é o princípio do ponto-único-de-manutenção. Sempre que possível, uma decisão de design deve ser expressa em apenas um ponto na origem Ada e a maioria das conseqüências deve ser derivada programaticamente a partir desse ponto. As violações desse princípio representam grande risco à manutenção e à confiança, bem como ao entendimento.
Finalmente, como uma contribuição maior à legibilidade, o princípio de ruído mínimo foi aplicado. Ou seja, foi feito um esforço para evitar a confusão do código fonte com o "ruído" visual: barras, caixas e outros textos com conteúdo de poucas informações ou informações que não contribuam para o entendimento do propósito do software.
A portabilidade e a possibilidade de reutilização também são razões para várias diretrizes. O código terá que ser portado para diferentes compiladores por diferentes computadores de destino e eventualmente para uma versão mais avançada do Ada, denominada "Ada 95" [PLO92, TAY92].
As diretrizes apresentadas aqui criam um pequeno número de premissas básicas:
O uso de recursos avançados Ada é encorajado sempre que for benéfico, em vez de ser desencorajado na base, na qual alguns programadores não estão familiarizados. Essa é a única maneira na qual o projeto pode realmente se beneficiar do uso do Ada. O Ada deve ser utilizado como se fosse Pascal ou FORTRAN. A paráfrase do código em comentários é desencorajado; caso contrário, o Ada deve ser utilizado no lugar dos comentários, sempre que viável.
Várias das convenções de nomenclatura são baseadas no inglês, tanto no vocabulário quanto na sintaxe. Além disso, as palavras-chave Ada são palavras comuns em inglês e misturá-las com outros idiomas dificulta a legibilidade.
As convenções de nomenclatura e algumas outras regras assumem que as cláusulas "use" não são utilizadas.
Várias regras oferecem maior valor em grandes sistemas Ada, embora eles também possam ser utilizados em um pequeno sistema, se destinados apenas à prática e à uniformidade no nível de projeto ou corporativo.
Ao utilizar o Ambiente Rational, problemas como o layout do código, os identificadores no fechamento das construções e assim por diante são tratados pelo editor e formatador Ada. No entanto, as recomendações de layout contidas nesse documento podem ser aplicadas em qualquer plataforma de desenvolvimento.
Várias regras suportarão um mapeamento sistemático dos conceitos orientados ao objeto (OO) para os recursos Ada e convenções de nomenclatura específicas.
Essas diretrizes não são de igual importância. Elas seguem a grosso modo essa escala:
A diretriz é uma parte simples do aviso; não há prejuízo real feito por não segui-la e ela pode ser selecionada ou rejeitada por uma questão de opinião. As dicas são marcadas nesse documento com o símbolo acima.
A diretriz geralmente é baseada em fundamentos mais técnicos; a portabilidade ou a capacidade de reutilização pode ser afetada, bem como o desempenho em algumas implementações. As recomendações devem ser seguidas, a menos que haja um bom motivo para não segui-las. Algumas exceções são mencionadas nesse documento. As recomendações são marcadas nesse documento pelo símbolo acima.
O recurso em questão é perigoso de ser utilizado, mas não é completamente descartado; a decisão de utilizá-lo deve ser uma decisão no nível de projeto e ela deve ficar altamente visível. As restrições são marcadas nesse documento pelo símbolo apresentado acima.
Uma violação conduziria definitivamente ao código inválido, não confiável ou não portátil. Os requisitos não podem ser violados. Os requisitos são marcados nesse documento com a mão indicadora acima.
O Recurso de Design Rational será utilizado para sinalizar o uso de recursos restritos e reforçar as regras requeridas e várias das recomendações.
Em oposição a vários outros padrões de codificação Ada, muito poucos recursos Ada são realmente descartados por inteiro nessas diretrizes. A chave para o bom software reside em:
Utilize o senso comum.
Quando não for possível localizar uma regra ou diretriz, quando a regra não for aplicada de maneira óbvia ou quando todo o resto falhar: utilize o senso comum e verifique os princípios fundamentais. Essa regra substitui todas as outras. O senso comum é requerido.
O layout de uma unidade de programa está sob o controle completo do Formatador de Ambiente Rational e o programador não terá que se preocupar muito com o layout de um programa, exceto nos comentários e no espaço em branco. As convenções de formatação adotadas por essa ferramenta são as expressas no Apêndice E do Manual de Referência para a Linguagem de Programação Ada [ISO87]. Em específico, sugere-se que as palavras-chave iniciadas e encerradas com uma construção estruturada sejam verticalmente alinhadas. Além disso, o identificador de uma construção é sistematicamente repetido ao final da construção.
O comportamento preciso do formatador é controlado por uma série de comutações de biblioteca que recebem um conjunto uniforme de valores em todo o projeto, com base em um mundo de modelos comuns. As comutações relevantes são listadas abaixo com seus valores reais para o mundo de modelos que recomendamos.
Especifica o tipo dos identificadores em unidades Ada: a primeira letra e cada primeira letra após um sublinhado ficam em maiúsculas. O formulário em maiúsculas é reconhecido como o mais legível pelos leitores humanos, com a tela mais moderna e fontes de impressora laser.
Especifica o tipo de letra das palavras-chave Ada. Isso os distingue ligeiramente dos identificadores.
Especifica o tipo da letra "E" nos literais de ponto flutuante e dígitos baseados ("A" até "F") em literais baseados.
Uma unidade Ada é formatada de acordo com as convenções gerais expressas no Apêndice E do Manual de Referência Ada [ISO87]. Isso significa que as palavras-chave que iniciadas e encerradas com uma construção estruturada estão alinhadas. Por exemplo, "loop" e "end loop", "record" e "end record". Os elementos que estão dentro de construções estruturadas são recuados para à direita.
Especifica o número de colunas que o formatador recua nas construções estruturadas (principais) como instruções "if", instruções "case" e instruções "loop".
Especifica o número de colunas que o formatador recua nas construções menores: declarações de registro, declarações de registro variáveis, declarações de tipo, rotinas de tratamento de exceção, alternativas, instruções de caso e instruções nomeadas e identificadas.
Especifica o número de colunas utilizadas pelo formatador para as linhas de impressão em unidades Ada antes de agrupá-las. Isso permite a exibição das unidades formatadas com o VT100 tradicional como terminais.
Especifica o número de colunas que o formatador recua na segunda linha e nas linhas subseqüentes de uma instrução quando a instrução tiver que ser quebrada porque é superior a Line_Length. O formatador recua o número de colunas do Statement_Indentation apenas se não houver construção léxica com a qual o código recuado pode ser alinhado.
Especifica o número de colunas reservadas em cada linha para exibir uma instrução. Se o nível atual de recuo permitir menos de Statement_Length colunas em uma linha, o formatador será reiniciado com a coluna Wrap_Indentation como seu novo nível de recuo. Essa prática evita que as instruções profundamente aninhadas sejam impressas além da margem direita.
Especifica a coluna na qual o formatador inicia o próximo nível de recuo quando o nível atual de recuo não permitir Statement_Length. Essa prática evita que as instruções profundamente aninhadas sejam impressas além da margem direita.
Controla a formatação das listas do formulário (xxx:aaa; yyy:bbb), que aparecem nas partes formais do subprograma e como discriminantes nas declarações de tipo. Também controla a formatação das listas do formulário (xxx=>aaa, yyy=>bbb), que aparecem nas chamadas do subprograma e nos agregados. Como essa opção é diferente de (True), quando uma lista não se encaixar em uma linha, cada elemento da lista começará em uma nova linha.
Especifica o número de espaços em branco que o formatador pode inserir para alinhar as construções léxicas em instruções consecutivas, como dois pontos, designações e setas na notação nomeada. Se um número superior a esse de espaço for necessário para alinhar uma construção, a construção ficará desalinhada.
Observe que
para forçar um determinado layout, o programador pode inserir um fim-de-linha ou uma
quebra de linha que não será removida pelo formatador, digitando <space>
<space> <carriage-return>.
Utilizando essa
técnica e para aprimorar a legibilidade e a sustentabilidade, as linhas de elementos
Ada devem ser quebradas para conter apenas um elemento por linha,
quando a lista exceder 3 itens e quando eles não se encaixarem em uma linha. Em
específico, isso se aplica às seguintes construções
Ada (conforme definido no Apêndice E do Manual de Referência Ada [ISO87]):
associação de argumento
pragma Suppress (Range_Check, On => This_Type, On => That_Type, On => That_Other_Type);
lista de identificadores, lista de componentes
Next_Position, Previous_Position, Current_Position : Position; type Some_Record is record A_Component, B_Component, C_Component : Component_Type; end record;
definição do tipo de enumeração
type Navaid is (Vor, Vor_Dme, Dme, Tacan, VorTac, NDB);
restrição discriminante
subtype Constrained is Element (Name_Length => Name'Length, Valid => True, Operation => Skip);
seqüências de instruções (feitas pelo formatador)
parte formal, parte formal genérica, parte real do parâmetro, parte real genérica do parâmetro
procedure Just_Do_It (This : in Some_Type; For_That : in Some Other_Type; Status : out Status_Type); Just_Do_It (This => This_Value; For_That => That_Value; Status => The_Status);
Contrário a uma crença amplamente retida, os bons programas não são caracterizados pelo número de comentários, mas por suas qualidades.
Os comentários devem ser utilizados para complementar o código Ada, nunca o parafraseie. O Ada em si é uma linguagem de programação bem legível-ainda mais quando suportada por boas convenções de nomenclatura. Os comentários devem complementar o código Ada, explicando aquilo que não é óbvio; eles não devem duplicar a sintaxe Ada ou as semânticas. Os comentários devem ajudar o leitor a captar os conceitos de segundo plano, as dependências e as codificações de dados ou algoritmos especialmente complexos. Os comentários devem realçar os desvios dos padrões de codificação ou de design, utilizar os recursos restritos e "truques" especiais. Os quadros de comentários, ou formulários, que aparecem sistematicamente para cada principal construção Ada (como subprogramas e pacotes) terão o benefício da uniformidade e do restante do programador para documentar o código, mas geralmente conduzem a um estilo de paráfrase. Para cada comentário, o programador deve ser capaz de responder bem à pergunta: "Qual Valor é Agregado Por Esse Comentário?"
Um comentário mal interpretado ou incorreto é pior que nenhum comentário. Os comentários não são verificados pelo compilador (a menos que participem em algum ADL (Ada Design Language) formal ou PDL (Program Design Language), como com o Recurso de Design Rational) não são verificados pelo compilador. Portanto, de acordo com o princípio de ponto-único-de-manutenção, as decisões de design devem ser expressas no Ada em vez de serem expressas em comentários, mesmo nos gastos de algumas declarações a mais.
Como um exemplo (não tão bom), considere a seguinte declaração:
------------------------------------------------------------ -- procedure Create ------------------------------------------------------------ -- procedure Create (The_Subscriber: in out Subscriber.Handle; With_Name : in out Subscriber.Name); -- -- Propósito: Esse procedimento cria um assinante em um determinado -- nome. -- -- Parâmetros: - The_Subscriber :mode in out, type Subscriber.Handle - É o manuseio para o assinante criado - With_Name :mode in, type Subscriber.Name - O nome do assinante a ser criado. - A sintaxe do nome é -- <letter> { <letter> | <digit> } -- Exceções: -- Subscriber.Collection_Overflow em que não há mais -- espaço para criar um novo assinante -- Subscriber.Invalid_Name quando o nome estiver em branco ou -- mal formado -- -------------------------------------------- end Create ----
Diversos pontos podem ser estabelecidos sobre esse exemplo.
Nesse caso, são preferidas as seguintes versões úteis e mais concisas:
procedure Create (The_Subscriber : in out Subscriber.Handle; With_Name : in Subscriber.Name);-- --Levanta Subscriber.Collection_Overflow. --Levanta Subscriber.Invalid_Name quando o nome estiver --em branco ou mal formado (consulte a descrição da sintaxe --anexada à declaração do tipo Subscriber.Name).
Os comentários
devem ser colocados próximos do código aos quais estão associados, com o mesmo recuo
e serão anexados nesse código-ou seja, com linhas de comentário em branco
que se vinculam visualmente ao bloco de comentários para a construção Ada:
procedure First_One; -- -- Esse comentário está relacionado ao First_One. -- Mas esse comentário destina-se ao Second_One. -- procedure Second_One (Times : Natural);
Utilize as linhas em branco para separar os blocos relacionados de código fonte (comentários e código) em vez das pesadas linhas de comentário.
Utilize comentários vazios, em vez de linhas vazias, dentro de um único bloco de comentário para separar os parágrafos:
-- Alguma explicação aqui que precise ser continuada em um -- parágrafo subseqüente. -- -- A linha de comentário vazia acima deixa claro que estamos -- tratando de um único bloco de comentário.
Embora os
comentários possam ser colocados acima ou abaixo da(s) construção(ões)
Ada para as quais estão relacionadas, coloque os comentários como um título de seção
ou uma parte maior de informações que se aplica a diversas construções
Ada acima da(s) construção(ões). Coloque os comentários que sejam observações
ou informações adicionais abaixo da construção Ada à qual se aplicam.
Agrupe os comentários no início da construção Ada, utilizando a largura inteira da página. Evite comentários na mesma lista de uma construção Ada. Esses comentários geralmente ficam desalinhados. No entanto, tais comentários são tolerados nas descrições de cada elemento em longas declarações, como literais de tipo de enumeração.
Utilize uma pequena hierarquia de blocos padrão de comentários para os títulos de seção, mas apenas em unidades Ada muito grandes (>200 declarações ou instruções):
--=========================================================== -- -- TÍTULO PRINCIPAL AQUI -- --=========================================================== ------------------------------------------------------------- -- Título Secundário Aqui ------------------------------------------------------------- -- -------------------- -- Cabeçalho da Subseção -- --------------------
Coloque mais linhas em branco acima desses comentários de título do que abaixo-por exemplo, duas linhas antes e uma linha depois. Isso associa visualmente o título ao seguinte texto.
Evite o uso de cabeçalhos que contenham informações como o autor,
os números de telefone, as datas de criação e modificação e o local da unidade
(ou o nome do arquivo), porque essas informações ficam rapidamente obsoletas.
Coloque os avisos de direitos autorais à propriedade
no fim da unidade, especialmente ao utilizar o Ambiente Rational. Ao acessar a origem
de uma especificação de pacote (pressionando [Definição] no Ambiente Rational, por exemplo),
o usuário não deseja ter que se deslocar através de duas ou três páginas de texto que não
sejam úteis para o entendimento do programa e/ou o texto que não carrega qualquer informação
do programa, como um aviso de direitos autorais. Evite a utilização de barras verticais
ou caixas ou quadros fechados, que apenas adicionam ruído visual e são difíceis de se
manter consistentes. Utilize as notas do Rational CMVC (ou alguma outra forma de
arquivos de desenvolvimento de software) para manter o histórico da unidade.
Não replique as informações normalmente encontradas
em todos os lugares; forneça um ponteiro para as informações.
Utilize o Ada sempre que possível, em vez de um comentário. Para
isso, você pode utilizar nomes melhores, variáveis temporárias
extra, qualificação, renomeação, subtipos, expressões estáticas e atributos,
todos que não afetam o código gerado (pelo menos, com um bom compilador). Você também pode utilizar funções menores de predicados seqüenciais
e dividir o código em diversos procedimentos sem parâmetros, cujos nomes fornecem títulos para diversas seções discretas
do código.
Exemplos:
Substitua:
exit when Su.Locate (Ch, Str) /= 0; -- Sair do loop de procura ao localizá-lo.
Search_Loop : loop
Found_It := Su.Locate (Ch, Str) /= 0;
exit Search_Loop when Found_It
end Search_Loop;
Substitua:
if Value < 'A' or else Value > 'Z' then -- Se não for letras maiúsculas.
subtype Uppercase_Letters is Character range 'A' .. 'Z'; if Value not in Uppercase_Letters then ...
Substitua:
X := Green; -- Isso é Verde no -- Status, não na Cor. raise Fatal_Error; -- Do pacote Outer_Scope. delay 384.0; -- Igual a 6 minutos e 24 -- segundos.
The_Status := Green;
X := Status'(Green); raise Outer_Scope.Fatal_Error; delay 6.0 * Minute + 24.0 * Second;
Substitua:
if Is_Valid (The_Table (Index).Descriptor(Rank).all) then -- Esse é o valor atual para iteração; se for -- válido, anexamos à lista que contém. Append (Item, To_List => The_Table (Index).Descriptor(Rank).Ptr);|
declare Current_Rank : Lists.List renames The_Table (Index).Descriptor (Rank); begin if Is_Valid (Current_Rank.all) then Append (Item, To_List => Current_Rank.Ptr); end if; end;
Cuide do estilo, sintaxe e ortografia nos comentários. Não
utilize um estilo telegráfico criptografado. Utilize o corretor ortográfico. (No
Ambiente Rational chame Speller.Check_Image).
Não utilize letras acentuadas ou outros caracteres diferentes do inglês. Os
caracteres diferentes do inglês podem ser suportados em alguns sistemas de desenvolvimento
e em alguns compiladores Ada apenas nos comentários, de acordo com o Problema Ada AI-339. Mas
isso não é portátil e provavelmente falhará em outros sistemas.
Para os subprogramas, documente no mínimo:
Para os tipos e os objetos, documente qualquer constante ou restrições adicionais que não possam ser expressas no Ada.
Evite repetições nos comentários. Por exemplo, a seção de propósito deve ser uma breve resposta à pergunta "o que isso faz?" e não a "como isso é feito?" A visão geral deve ser uma breve apresentação do design. A
descrição não deve descrever os algoritmos utilizados, mas, em vez disso, deve
explicar como o pacote deve ser utilizado.
O Data_Structure e a seção de algoritmo devem conter informações suficientes para ajudar a entender a estratégia principal de implementação (para que o pacote possa ser utilizado corretamente), mas não tenha que fornecer os detalhes de implementação ou as informações que não sejam relevantes para o uso apropriado desse pacote.
A escolha de bons nomes para designar entidades Ada (unidades do programa, tipos, subtipos, objetos, literais, exceções) é um dos problemas mais delicados a serem endereçados em todos os aplicativos de software. Em aplicativos médios-para-grandes, surge outro problema: conflitos nos nomes ou então a dificuldade na localização de sinônimos suficientes para designar a distinção, mas noções semelhantes sobre o mesmo conceito de mundo real (ou para nomear um tipo, subtipo, objeto e parâmetro). Aqui, a regra para não utilizar as cláusulas "use" (ou apenas em condições altamente restritas) pode ser explorada. Em várias situações, isso permitirá a redução de um nome e a reutilização das mesmas palavras descritivas sem risco de confusão.
Escolha nomes limpos, legíveis e significativos.
Diferentes de várias outras linguagens de programação, o Ada não limita o comprimento dos identificadores para 6, 8 ou 15 caracteres. A velocidade da digitação não é uma justificativa aceitável para os nomes abreviados. Os identificadores de uma letra geralmente são uma indicação de uma opção fraca ou lentidão. Pode haver algumas exceções, como utilizar E para a base dos logaritmos naturais, Pi, ou uma porção de outros casos bem reconhecidos.
Separe diversas palavras de um nome por um sublinhado:
Is_Name_Valid
em vez de IsNameValid
Utilize nomes completos em vez de abreviações.
Utilize apenas abreviações aprovadas pelo projeto
Se forem utilizadas abreviações, elas devem ser bem comuns para o domínio de aplicativos (por exemplo, FFT para Fast Fourier Transform) ou elas devem ser extraídas de uma lista no nível do projeto de abreviações reconhecidas. Caso contrário, é muito provável que ocorram aqui abreviações semelhantes, mas não idênticas, introduzindo problemas e erros posteriormente (por exemplo, Track_Identification sendo abreviado Tr_Id, Trck_Id, Tr_Iden, Trid, Tid, Tr_Ident e assim por diante).
Utilize sufixos moderadores que indiquem a categoria da
construção Ada. Eles não aprimoraram a legibilidade.
Os sufixos por categoria de entidades Ada, como _Package para pacotes, _Error para exceções, _Type para o tipo, e _Param para parâmetros de subprograma geralmente não são muito eficazes para o processo de leitura e entendimento do código. Isso é ainda pior com sufixos como _Array, _Record e _Function. Tanto o compilador Ada como o leitor humano podem distinguir uma exceção de um subprograma pelo contexto: é óbvio que apenas um nome de exceção pode aparecer em uma instrução raise ou em uma rotina de tratamento de exceção. Esses sufixos são úteis nas seguintes situações limitadas:
Tipo formal genérico com sufixo _Constrained
Tipo de acesso com sufixo _Pointer ou outra forma de referência indireta: _Handle ou _Reference
Subprograma ocultando uma chamada de entrada que esteja potencialmente bloqueando _Or_Wait
Nomes expresso de modo que parecem bons do ponto de vista de uso.
Tente imaginar o contexto no qual uma entidade exportada será utilizada e escolha o nome desse ponto de vista. Uma entidade é declarada uma vez e utilizada várias vezes. Isso é especialmente verdadeiro para os nomes de subprogramas e seus parâmetros: as chamadas resultantes, que utilizam associações nomeadas, devem ser o mais próximo possível do idioma natural. Lembre-se de que a ausência das cláusulas use tornará compulsório o nome qualificado da maioria das entidades declaradas. Bons acordos devem ser localizados para os parâmetros genéricos formais, que podem ser utilizados mais na unidade genérica do que em seu lado cliente, mas definitivamente dão preferência a uma boa aparência no lado cliente para os parâmetros formais do subprograma.
Utilize palavras em inglês e as soletre corretamente.
A mistura de idiomas (por exemplo, francês e inglês) dificulta a leitura do código e, às vezes, apresenta ambigüidades no significado dos identificadores. Como as palavras-chave Ada já estão em inglês, são requeridas palavras em inglês. A forma americana é a preferida, para que seja possível utilizar o verificador ortográfico interno do Ambiente Rational.
Não redefina qualquer entidade do pacote Padrão. Isso
é absolutamente proibido.
Fazer isso conduz à confusão e a erros dramáticos. A regra poderia ser estendida a outras unidades de biblioteca predefinidas: Calendário, Sistema. E isso inclui o próprio identificador Padrão.
Evite a redefinição dos identificadores de outros pacotes
predefinidos, como Sistema ou Calendário.
Não utilize como identificadores: Wide_Character
e Wide_String que serão introduzidos no pacote Padrão no Ada 95. Não
introduza uma unidade de compilação denominada Ada.
Não utilize como identificadores
as palavras: abstract, aliased,protected, requeue,
tagged e until, que se tornarão palavras-chave no Ada 95.
Seguem algumas sugestões de nomenclatura de diversas entidades Ada. É assumido um estilo de design geralmente de "tipo de objeto". Consulte o Anexo A para futuras explicações.
Quando um pacote apresentar alguma classe de objeto,
forneça o nome da classe de objeto, geralmente um nome comum no formato singular,
com o sufixo _Generic, se necessário (ou seja, se uma classe de parâmetro for definida).
Utilize a forma plural apenas se os objetos sempre aparecerem em grupos. Por exemplo:
package Text is package Line is package Mailbox is package Message is package Attributes is package Subscriber is package List_Generic is
Quando um pacote especificar uma interface ou
algum agrupamento de funcionalidade e não se relacionar a um objeto, expresse isso no nome:
package Low_Layer_Interface is package Math_Definitions is
Quando o pacote "lógico" precisar ser expresso
como vários pacotes, utilizando uma decomposição simples, utilize os sufixos
extraídos de uma lista concordada no nível do projeto. Um pacote lógico de Caixa
Postal, por exemplo, poderia ser implementado com:
package Mailbox_Definitions is package Mailbox_Exceptions is package Mailbox_Io is package Mailbox_Utilities is package Mailbox_Implementation is package Mailbox_Main is
Os outros sufixos aceitos são:
_Test_Support _Test_Main _Log _Hidden_Definitions _Maintenance _Debug
Em um pacote que define uma classe de objeto, utilize:
type Object is ...
quando as semânticas de cópia estiverem implícitas-ou seja, quando o tipo for instanciável e alguma forma de designação for viável. Observe que o nome da classe não deve ser repetido no identificador, porque sempre será utilizado na sua forma completa:
Mailbox.Object Line.Object
Quando as semânticas compartilhadas
estiverem implícitas-ou seja, o tipo é implementado com os valores de acesso
(ou alguma outra forma de via indireta), e a designação, se disponível,
não copiar o objeto-indique esse fato utilizando:
type Handle is
para uma referência indiretatype Reference is
como uma alternativa possível
Os elementos são utilizados como sufixos quando seu uso sozinho, com o prefixo do nome do pacote, não for claro ou for ambíguo.
Quando vários objetos estiverem implícitos, utilize um dos seguintes:
type Set
type List
type Collection
type Iterator
Para alguma designação de cadeia do projeto, utilize:
type Name
O nome qualificado do tipo também deve ser utilizado
em todo o pacote de definição, para melhor legibilidade. No Ambiente Rational,
isso também conduz ao melhor comportamento ao utilizar a função
[Complete] em uma chamada de subprograma.
Por exemplo, observe o nome completo Subscriber.Object abaixo
package Subscriber is type Object is private; type Handle is access Subscriber.Object; subtype Name is String; package List is new List_Generic (Subscriber.Handle); Master_List : Subscriber.List.Handle; procedure Create (The_Handle : out Subscriber.Handle; With_Name : in Subscriber.Name); procedure Append (The_Subscriber : in Subscriber.Handle; To_List : in out Subscriber.List.Handle); function Name_Of (The_Subscriber : Subscriber.Handle) return Subscriber.Name; ... private type Object is record The_Name : Subscriber.Name (1..20); ... end Subscriber;
Em outras circunstâncias, utilize os nomes
ou qualificadores+nome para o nome de um tipo. Você pode utilizar a forma plural
para o tipo, deixando o singular para os objetos (variáveis):
type Point is record ... type Hidden_Attributes is ( ... type Boxes is array ...
Para os tipos de enumeração, utilize Mode, Kind, Code, e assim por diante, sozinho ou como um sufixo.
Para o tipos de matrizes, o sufixo _Table pode ser utilizado quando o nome simples já for utilizado para o tipo de componente. Utilize nomes ou sufixos como _Set e _List apenas quando a matriz for mantida com semânticas implícitas. Reserve _Vector e _Matrix para os conceitos matemáticos correspondentes.
Como os objetos de tarefa singulares serão evitados
(por motivos explicados posteriormente), um tipo de tarefa deve ser introduzido
mesmo quando houver apenas um objeto desse tipo.
Isso ocorre quando uma estratégia de sufixo simples como _Type for satisfatória:
task type Listener_Type is ... for Listener_Type'Storage_Size use ... Listener : Listener_Type;
De forma semelhante, quando existirem conflitos
entre o uso de um substantivo (ou frase substantiva) para o nome do tipo e em vários locais
para o nome do objeto ou parâmetro, coloque o sufixo nesse substantivo com _Kind para o
tipo e mantenha um substantivo simples para o objeto:
type Status_Kind is (None, Normal, Urgent, Red); Status : Status_Kind := None;
Ou, para itens que sempre aparecem em múltiplos, utilize a forma plural para o tipo.
Como os tipos de acesso possuem perigos inerentes,
o usuário deve ficar ciente disso. Em geral, eles são chamados Ponteiro. Utilize o
sufixo _pointer, se o nome sozinho for ambíguo. Como é possível um _Access alternado. ;
Às vezes, utilizar um subpacote aninhado para
apresentar uma abstração secundária simplifica a nomenclatura:
package Subscriber is ... package Status is type Kind is (Ok, Deleted, Incomplete, Suspended, Privileged); function Set (The_Status : Subscriber.Status.Kind; To_Subscriber : Subscriber.Handle); end Status; ...
Como as exceções devem ser utilizadas apenas para manipular
as situações de erro, utilize um substantivo ou uma frase substantiva
que transmita claramente uma idéia negativa:
Overflow, Threshold_Exceeded, Bad_Initial_Value
Quando definido em um pacote de classe, é irrelevante para o identificador conter o nome da classe-por exemplo, Bad_Initial_Subscriber_Value-como a exceção será sempre utilizada como Subscriber.Bad_Initial_Value.
Utilize uma dessas palavras:
Bad, Incomplete, Invalid, Wrong, Missing ou Illegal como parte do nome em vez de
utilizar sistematicamente Error, que não transmite as informações específicas:
Illegal_Data, Incomplete_Data
Utilize os verbos para os procedimentos (e entradas de tarefas).
Utilize os substantivos com os atributos ou as características da classe de objeto
para as funções. Utilize os adjetivos (ou particípios passados) para as funções que
retornam um Booleano (predicados). s
Subscriber.Create Subscriber.Destroy Subscriber.List.Append Subscriber.First_Name -- Retorna uma cadeia. Subscriber.Creation_Date -- Retorna uma data. Subscriber.List.Next Subscriber.Deleted -- Retorna um Booleano. Subscriber.Unavailable -- Retorna um Booleano. Subscriber.Remote
Para os predicados, em alguns casos, pode ser
útil incluir o prefixo Is_ ou Has_ antes de um substantivo; seja preciso e
consistente em relação ao tempo gramatical:
function Has_First_Name ... function Is_Administrator ... function Is_First... function Was_Deleted ...
É útil quando o nome simples já for utilizado como um nome de tipo ou um literal de enumeração.
Utilize os predicados na forma afirmativa, ou seja, eles não podem conter "Not_".
Para as operações comuns, utilize consistentemente os verbos retirados de uma lista de opções do projeto (lista a ser expandida conforme adquirimos conhecimento do sistema):
Create Delete Destroy Initialize Append Revert Commit Show, Display
Utilize nomes
positivos para as funções de predicado e parâmetros
Boolean. Utiliza nomes negativos pode criar negações duplas
(por exemplo, Not Is_Not_Found) e pode dificultar mais a leitura do código.
function Is_Not_Valid (...) return Boolean procedure Find_Client (With_The_Name : in Name; Not_Found : out Boolean)
deve ser definido como:
function Is_Valid (...) return Boolean; procedure Find_Client (With_The_Name: in Name; Found: out Boolean)
que permite ao cliente negar suas expressões conforme necessário (não há penalidade de tempo de execução para isso)
if not Is_Valid (...) then ....
Em alguns casos, um predicado negativo também pode se tornar positivo sem alterar sua semântica, utilizando um antônimo, como "Is_Invalid" em vez de "Is_Not_Valid." No entanto, nomes positivos são mais legíveis: "Is_Valid" é mais fácil de ser entendido do que "not Is_Invalid."
Utilize a mesma palavra quando o mesmo significado geral
for implícito, em vez de tentar localizar sinônimo ou variações. Portanto, a
sobrecarga é encorajada para aprimorar a uniformidade, para manter o princípio da
mínima surpresa.
Se os subprogramas forem utilizados como
"skins" ou "wrappers" para chamadas de entrada, pode ser útil que
o nome reflita esse fato, colocando o sufixo _Or_Wait no verbo ou fazendo com que uma
frase como Wait_For_ seja seguida por um nome:
Subscriber.Get_Reply_Or_Wait Subscriber.Wait_For_Reply
Algumas operações sempre devem ser consistentemente definidas utilizando os mesmos nomes:
Para as convenções de tipo de e para as cadeias, as funções simétricas:
function Image and function Value
Para as convenções de tipo de e para
alguma representação de baixo nível (como Byte_String para Data Interchange):
procedure Read and Write
Para os dados alocados:
function Allocate (rather than Create) function Destroy (or Release, to express that the object will disappear)
Quando isso é feito sistematicamente, utilizando nomenclatura consistente, a composição do tipo fica muito mais fácil.
Para iteradores ativos, os seguintes primitivos sempre devem ser definidos:
Initialize Next Is_Done Value_Of Reset. Se vários tipos de iteradores forem introduzidos no mesmo escopo, esses primitivos devem ser sobrecarregados em vez de introduzir um conjunto distinto de identificadores para cada iterador. Cf. [BOO87].
Ao utilizar os atributos predefinidos Ada
como nomes de função, certifique-se de que eles sejam utilizados com as mesmas
semânticas gerais: 'First, 'Last, 'Length, 'Image, 'Value e assim por diante.
Observe que vários atributos (por exemplo, 'Range e 'Delta) não podem ser utilizados
como nomes de função porque são palavras reservadas.
Para indicar exclusividade ou para mostrar que essa entidade
é o principal foco da ação, como o prefixo The_ ou This_ no objeto ou no nome do parâmetro.
Para indicar um lado, temporário, objeto auxiliar, como o prefixo A_ ou Current_ nele:
procedure Change_Name (The_Subscriber : in Subscriber.Handle; The_Name : in Subscriber.Name ); declare A_Subscriber : Subscriber.Handle := Subscriber.First; begin ... A_Subscriber := Subscriber.Next (The_Subscriber); end;
Para os objetos Booleanos,
utilize uma cláusula de predicado, com o formato positivo:
Found_It Is_Available
Is_Not_Available
deve ser evitado.
Para os objetos de tarefa, utilize um substantivo ou
uma frase substantiva que implique uma entidade ativa:
Listener Resource_Manager Terminal_Driver
Para os parâmetros, colocar o prefixo do nome da classe
ou algum substantivo característico com uma pré-posição também inclui a
legibilidade, especialmente no lado do responsável pela chamada quando a associação
nomeada for utilizada. Outros prefixos úteis para os parâmetros auxiliares
possuem o formato Using_ ou, no caso de um parâmetro in out que é afetado como
algum efeito secundário, Modifying_:
procedure Update (The_List : in out Subscriber.List.Handle; With_Id : in Subscriber.Identification; On_Structure : in out Structure; For_Value : in Value); procedure Change (The_Object : in out Object; Using_Object : in Object);
A ordem na qual os parâmetros são definidos
também são muito importantes do ponto de vista do responsável pela chamada:
Isso permite tirar proveito dos padrões sem ter que utilizar a associação nomeada para o(s) principal(is) parâmetro(s).
O modo "in" deve ser explicitamente indicado, mesmo em funções.
Escolha o melhor nome que você utilizaria para uma versão não genérica:
nome da classe para um pacote ou verbo transitivo (ou frase verbal) para um procedimento
(consulte acima) e coloque nele o sufixo _Generic.
Para tipos formais genéricos, quando o pacote genérico definir
alguma estrutura de dados abstrata, utilize
Item
ou Element
para
formal genérico e Structure
, ou algum outro substantivo mais apropriado, para
a abstração exportada.
Para iteradores passivos,
utilize um verbo como
Apply
, Scan
, Traverse
,
Process
ou Iterate
no identificador:
generic with procedure Act (Upon : in out Element); procedure Iterate_Generic (Upon : in out Structure);
Os nomes de parâmetros formais genéricos não podem ser homógrafos.
generic type Foo is private; type Bar is private; with function Image (X : Foo) return String; with function Image (X : Bar) return String; package Some_Generic is ...
deve ser substituído por:
generic type Foo is private; type Bar is private; with function Foo_Image (X : Foo) return String; with function Bar_Image (X : Bar) return String; package Some_Generic is ...
Se necessário, os parâmetros formais genéricos podem ser renomeados na unidade genérica:
function Image (Item : Foo) return String Renames Foo_Image; function Image (Item : Bar) return String Renames Bar_Image;
Quando um grande sistema for particionado nos subsistemas Rational (ou outra forma de bibliotecas de programas interconectados), é útil definir uma estratégia de nomenclatura que permita:
Em um sistema que consiste em várias centenas de objetos e subobjetos, é provável que ocorram alguns conflitos no nível de unidade de biblioteca e os programadores serão abreviações de sinônimos para nomes muito úteis como Utilities, Support, Definitions e assim por diante.
Utilizando os recursos de navegação no host Rational, localizar onde uma entidade é definida se torna uma tarefa fácil, mas quando o código é portado em um destino e utiliza as ferramentas de destino (depuradores, ferramentas de teste e assim por diante), o local de um procedimento Utilities.Get entre 2.000 unidade em 100 subsitema pode ser um desafio considerável para os novatos no projeto.
Os nomes de unidade no nível de biblioteca de prefixo
com a abreviação de quatro letras do subsistema no qual está contido.
A lista de subsistemas pode ser localizada no SAD (Software Architecture Document). Exclua dela, as bibliotecas de regra de componentes altamente reutilizáveis que provavelmente serão reutilizados entre inúmeros projetos, produtos COTS e unidades padrão.
Exemplo:
Comm Comunicação
Dbms Gerenciamento de banco de dados
Disp Exibições
Math Pacotes matemáticos
Drivers de Unidade
Por exemplo, todas as unidades exportadas do subsistema Disp receberão o prefixo Disp_, permitindo que a equipe ou a empresa encarregada do Disp tenha então liberdade completa de nomenclatura Se DBMS e Disp precisarem apresentar uma classe de objetos nomeada Subscriber, isso resultará em pacotes como:
Disp_Subscriber Disp_Subscriber_Utilities Disp_Subscriber_Defs Dbms_Subscriber Dbms_Subscriber_Interface Dbms_Subscriber_Defs
O recurso de forte digitação Ada
será utilizado para evitar a mistura de diferentes tipos. De forma conceitual,
diferentes tipos devem ser realizados como diferentes tipos definidos pelo usuário. Os
subtipos devem ser utilizados para aprimorarem a leitura do programa e para
aprimorarem a efetividade das verificações de tempo de execução geradas pelo compilador.
Sempre que possível, introduza na enumeração
algum valor literal extra que represente o valor não inicializado, inválido ou
nenhum valor:
type Mode is (Undefined, Circular, Sector, Impulse); type Error is (None, Overflow, Invalid_Input_Value,Ill-formed_Name);
Isso suportará as regras para inicializar sistematicamente os objetos. Coloque esse literal no início, em vez de colocá-lo ao final da lista, para facilitar a manutenção e permitir subfaixas contíguas de valores válidos:
subtype Actual_Error is Error range Overflow .. Error'Last;
Evite o uso de tipos numéricos predefinidos.
Quando um alto grau de portabilidade e nova utilidade for o objetivo, ou quando o controle for necessário sobre o espaço de memória ocupado por objetos numéricos, os tipos numéricos predefinidos (do pacote Padrão) não devem ser utilizados. A razão para esse requisito é que as características dos tipos predefinidos Integer e Float são (deliberadamente) não-especificados no Manual de Referência para a Linguagem de Programação Ada [ISO87].
Uma primeira estratégia sistemática é introduzir
tipos numéricos específicos de projeto-em um pacote System_Types, por exemplo-com nomes
que transportam uma indicação da exatidão ou do tamanho da memória:
package System_Types is type Byte is range -128 .. 127; type Integer16 is range -32568 .. 32567; type Integer32 is range ... type Float6 is digits 6; type Float13 is digits 13; ... end System_Types;
Não redefina os tipos padrão (tipos do pacote Padrão).
Não especifique o tipo base do qual ele deve ser derivado;
deixe o compilador escolher. O exemplo a seguir é incorreto:
type Byte is new Integer range -128 .. 127;
Float6 é um nome melhor do que Float32,
mesmo se na maioria das máquinas as flutuações de 32 bits alcançarem 6 dígitos de
exatidão.
Nas diversas partes do objeto do projeto,
derive os tipos com nomes mais significativos do que os existentes em
Baty_System_Types. Alguns dos tipos mais precisos poderiam se tornar privados
para suportarem uma porta eventual para um destino com suporte de precisão limitada.
Essa estratégia deve ser utilizada quando:
Se esse não for o caso, outra estratégia mais simples é definir sempre novos tipos, especificando o intervalo selecionado e a exatidão, mas nunca especificando o tipo base dos quais eles devem ser derivados. Por exemplo, declare:
type Counter is range 0 .. 100; type Length is digits 5;
Isso é preferível ao seguinte:
type Counter is new Integer range 1..100; -- poderia ser 64 bits type Length is new Float digits 5; -- poderia ser 13
Essa segunda estratégia força o programador a considerar os limites precisos e precisão que cada tipo requer, em vez de selecionar arbitrariamente um determinado número de bits. No entanto, certifique-se de que se o intervalo não for idêntico ao de um tipo base, o intervalo sistemático será aplicado pelo compilador-por exemplo, para o tipo Counter acima, se o tipo base for um inteiro de 32 bits.
Se as verificações de intervalo estiverem se
tornando um problema, uma forma de evitá-las é declarar:
type Counter_Min_Range is range 0 .. 10_000; type Counter is range Counter_Min_Range'Base'First .. Counter_Min_Range'Base'Last;
Evite os tipos padrão que correm para o código através de
construções como loops, intervalos de índice e assim por diante.
Os subtipos de tipos numéricos predefinidos são utilizados apenas nas seguintes circunstâncias:
Exemplo:
for I in 1 .. 100 loop ... -- I é do tipo Standard.Integer type A is array (0 .. 15) of Boolean; -- index é Standard.Integer.
Em vez disso, utilize o formato: Some_Integer range L .. H
for I in Counter range 1 .. 100 loop ... type A is array (Byte range 0 .. 15) of Boolean;
Não tente implementar tipos não designados.
Os tipos inteiro com aritmética não designada não existem no Ada. Na definição de idiomas, todos os tipos de inteiros são derivados inteiramente ou não dos tipos predefinidos e, por sua vez, eles devem ser simétricos aproximando-se do zero.
Para a portabilidade, conte apenas com tipos reais que tenham valores nos intervalos:
[-F'Large .. -F'Small] [0.0] [F'Small .. F'Large]
Certifique-se de que F'Last e F'First não podem ser números de modelos e até podem não estar em qualquer intervalo de modelo. O local relativo de F'Last e F'Large depende da definição de tipo e do hardware subjacente. Um exemplo especialmente ruim é o caso em que 'Last do tipo de ponto fixo não pertença ao tipo, como em:
type FF is delta 1.0 range -8.0 .. 8.0;
em que, de acordo com uma leitura rigorosa do Manual de Referência Ada 3.5.9(6), FF'Last = 8.0 não possa pertencer ao tipo.
Para representar números reais grandes ou pequenos, utilize atributos 'Large ou 'Small (e suas contrapartes negativas), não 'First e 'Last, como seria feito para os tipos inteiros.
Para os tipos de ponto flutuante, utilize
apenas <= e >=, nunca =, <, >, /=.
As semânticas da comparação absoluta são uma representação mal-definida (igualdade da representação e não igualdade dentro do grau requerido de exatidão). Por exemplo, X < Y pode não produzir o mesmo resultado de: não (X >= Y). Os testes para a igualdade, A = B, devem ser expressas como:
abs (A - B) <= abs(A)*F'Epsilon
Para aprimorar a legibilidade e capacidade de manutenção, considere fornecer um operador Equal que envolva a expressão acima.
Observe também que a expressão mais simples:
abs (A - B) <= F'Small
é válida apenas para os valores menores de A e B, e portanto não é geralmente recomendada.
Evite qualquer referência à exceção predefinida Numeric_Error. Foi
feita uma interpretação de ligação do Quadro Ada em todos os casos que
costumavam acionar Numeric_Error e agora acionam Constraint_Error. A exceção
Numeric_Error é obsoleta em Ada 95.
Se Numeric_Error ainda for acionado pela
implementação (esse será o caso do compilador nativo Rational), sempre verifique
o Constraint_Error junto com o Numeric_Error na mesma alternativa em uma rotina
de tratamento de exceção:
when Numeric_Error | Constraint_Error => ...
Seja cuidadoso com o fluxo baixo.
O fluxo baixo não é detectado no Ada. O resultado é 0.0 e nenhuma exceção é levantada. Observe que uma verificação para o fluxo baixo pode ser explicitamente alcançada testando o resultado de uma multiplicação ou divisão em 0.0, em que nenhum dos operandos será 0.0. Observe também que você pode implementar nossos próprios operadores para executar automaticamente essa verificação, embora com algum custo na eficiência.
O uso dos tipos de ponto fixo é restrito.
Utilize os tipos de ponto flutuante sempre que possível. A implementação irregular dos tipos de pontos fixos em uma implementação Ada provoca problemas de portabilidade.
Para os tipos de pontos fixos, 'Small deve ser igual a 'Delta.
É isso que o código deve especificar. O fato de que a opção padrão para 'Small seja um domínio 2 conduz a todos os tipos de problemas. Uma maneira de esclarecer a opção é gravar:
Fx_Delta : constant := 0.01; type FX is delta Fx_Delta range L .. H; for FX'Small use Fx_Delta;
Se as cláusulas de comprimento para os tipos de ponto fixo não forem suportadas, a única maneira de obedecer a essa regra é especificar explicitamente um 'Delta que é um poder de 2. Os subtipos podem ter um 'Small diferente de 'Delta (a regra se aplica apenas à definição de tipo ou ao "primeiro subtipo nomeado" na terminologia do Manual de Referência Ada).
Sempre que possível,
forneça valores iniciais simples estáticos para os componentes de um tipo de registro
(geralmente, podem ser utilizados valores como 'First ou 'Last).
Mas não aplique isso aos discriminantes. As regras do idioma são que os discriminantes sempre possuem valores. Os registros mutáveis (ou seja, os registros com valores padrão para os discriminantes) devem ser introduzidos apenas quando a mutabilidade for uma característica desejada. Caso contrário, os registros mutáveis introduzirão código extra no espaço de memória (geralmente será alocada a maior variante) e no tempo (as verificações de variantes são mais complexas de serem alcançadas).
Evite as chamadas de função nos valores iniciais padrão
de qualquer componente, uma vez que isso pode conduzir a um erro de
"acesso antes da elaboração" (consulte "Estrutura de Programa
e Problemas de Compilação").
Para registros mutáveis (registros cujos
discriminantes possuam valores padrão), se um discriminante for utilizado no
dimensionamento de algum outro componente, especifique-o para que seja de
um pequeno intervalo razoável.
Exemplo:
type Record_Type (D : Integer := 0) is record S : String (1 .. D); end record; A_Record : Record_Type;
está propenso a levantar um Storage_Error na maioria das implementações. Especifique um intervalo mais razoável para o subtipo do discriminante D.
Não assuma nada sobre o layout físico dos registros.
Especialmente, e diferente de outras linguagens de programação, os componentes não precisam estar dispostos na ordem fornecida na definição.
Restrinja o uso dos tipos de acesso.
Isso é especialmente válido para aplicativos destinados a serem executados permanentemente em pequenas máquinas sem memória virtual. Os tipos de acesso são perigosos, porque os erros de programação podem conduzir ao esgotamento de armazenamento e, mesmo com boa programação, podem fragmentar a memória. Os tipos de acesso também são mais lentos. O uso dos tipos de acesso deve fazer parte de uma ampla estratégia de projeto e coletas, seus tamanhos e pontos de alocação e de desalocação devem ser rastreados. Para que os clientes de uma abstração fiquem cientes de que os valores de acesso sejam manipulados, o nome escolhido deve indicar: o ponteiro ou um nome com sufixo _Pointer.
Aloque as coletas durante a elaboração do programa
e especifique sistematicamente o tamanho de cada coleta.
O valor fornecido (em unidades de armazenamento) pode ser estático ou dinamicamente computado (lido de um arquivo, por exemplo). O racional para essa regra é que o programa deve falhar imediatamente na inicialização, em vez de acabar misteriosamente N dias depois. Os pacotes genéricos podem fornecer um formal genérico adicional especificando o tamanho.
Observe que geralmente há um código extra para cada objeto alocado: pode ser que os tempos de execução no sistema de destino aloquem algumas informações adicionais com cada parte da memória para procedimentos operacionais internos. Portanto, para armazenar N objetos de unidades de armazenamento de tamanho M, pode ser necessário alocar mais de N * M unidades de armazenamento para a coleta-por exemplo, N * (M + K). Obtenha o valor desse código extra K a partir do Apêndice F [ISO87] ou conduzindo os experimentos.
Encapsule o uso dos alocadores (new primitivo Ada)
e release. Se for viável, gerencie uma lista interna livre, em vez de contar com
Unchecked_Deallocation.
Se um tipo de acesso for utilizado para implementar alguma estrutura de dados, é muito provável acessar um tipo de registro que possua (como um componente) o mesmo tipo de acesso. Isso permitirá reciclar as células livres, as encadeando em uma lista livre sem código extra de espaço adicional (diferente do ponteiro para a cabeça da lista).
Manipule as exceções Storage_Error explicitamente levantadas por new e exporte uma exceção mais significativa novamente, indicando o esgotamento do tamanho de armazenamento máximo da coleta.
Ter um único ponto de alocação e desalocação também torna mais fácil o rastreio e a depuração no caso de um problema.
Utilize a desalocação apenas em células alocadas
do mesmo tamanho (portanto, os mesmos discriminantes).
Isso é importante para evitar a fragmentação da memória. A Unchecked_Deallocation é muito improvável para fornecer um serviço de compactação de memória. Você pode desejar verificar se o sistema de tempo de execução fornece aglutinação dos blocos adjacentes liberados.
Fornece sistematicamente um Destroy
(ou Free, ou Release) primitivo com tipos de acesso.
Isso é especialmente importante para os tipos de dados abstratos implementados com os tipos de acesso e deve ser feito sistematicamente para alcançar a capacidade de composição de vários desses tipos.
Libere os objetos sistematicamente.
Tente mapear as chamadas para a alocalização e desalocação para assegurar que todos os dados alocados serão desalocados. Tente desalocar os dados no mesmo escopo no qual foi alocado. Lembre-se de desalocar também quando ocorrerem as exceções. Observe que esse é um caso para utilizar uma alternativa when others, terminada com uma instrução raise.
A estratégia preferida é aplicar o padrão: Get-Use-Release. O programador obtém (Gets) os objetos (que cria alguma estrutura de dados dinâmicos) e, em seguida, os utiliza (Uses) e depois deve liberá-lo (Release). Certifique-se de que as três operações estejam claramente identificadas no código e que o release é feito em todas as saídas possíveis do quadro, incluindo a exceção.
Seja cuidadoso ao desalocar as estruturas de dados
compostas temporárias que podem estar contidas em registros.
Exemplo:
type Object is record Field1: Some_Numeric; Field2: Some_String; Field3: Some_Unbounded_List; end record;
em que 'Some_Unbounded_List' é uma estrutura ligada composta, ou seja, é composta por inúmeros objetos ligados entre si. Agora considere uma típica função de atributo, escrita como:
function Some_Attribute_Of(The_Object: Object_Handle) return Boolean is Temp_Object: The_Object; begin Temp_Object := Read(The_Object); return Temp_Object.Field1 < Some_Value; end Some_Attribute_Of;
A estrutura composta criada implicitamente no heap quando o objeto for lido no Temp_Object nunca é desalocada, mas agora é inatingível. Essa é uma fuga de memória. A solução apropriada é implementar um paradigma Get-Use-Release para essas onerosas estruturas. Em outras palavras, seu cliente deve obter (Get) o objeto primeiro, em seguida, utilizá-lo (Use) conforme necessário e, em seguida, liberá-lo (Release):
procedure Get (The_Object : out Object; With_Handle : in Object_Handle); function Some_Attribute_Of(The_Object : Object) return Some_Value; function Other_Attribute_Of(The_Object : Object) return Some_Value; ... procedure Release(The_Object: in out Object);
O código do cliente pode ficar semelhante ao seguinte:
declare My_Object: Object; begin Get (My_Object, With_Handle => My_Handle); ... Do_Something (The_Value => Some_Attribute_Of(My_Object)); ... Release(My_Object); end;
Declare os tipos como privados sempre que
for necessário ocultar os detalhes da implementação.
Os detalhes da implementação precisam ser ocultos com um tipo privado quando:
No Ambiente Rational, os tipos de privilégio, em conjunto com as partes privadas e os subsistemas, reduzem muito o impacto de uma alteração de design de interface eventual.
Em contradição à conhecida
programação orientada ao objeto "puro", não utilize os tipos privados
quando o tipo completo correspondente for a melhor abstração possível. Seja
pragmático; pergunte se o tipo privado inclui algo.
Por exemplo, um vetor matemático é melhor representado como uma matriz, ou um ponto em um plano como um registro, do que como um tipo privado:
type Vector is array (Positive range <>) of Float; Type Point is record X, Y : Float := Float'Large; end record;
A indexação da matriz, seleção de componente de registro e notação agregada será muito mais legível (e eventualmente mais eficiente) do que uma série de chamadas de subprograma, como seria requerido se o tipo fosse desnecessariamente privado.
Declare os tipos privados como limitados quando a
designação padrão ou a comparação dos objetos reais e os valores fossem sem sentido,
não intuitivo ou impossível.
Esse é o caso quando:
Um tipo privado limitado deve se auto-inicializar.
Uma declaração de objeto desse tipo deve receber um valor inicial razoável, uma vez que geralmente não será viável para designar um posteriormente, sem riscos de levantar alguma exceção durante a chamada do subprograma.
Sempre que viável ou significativo, forneça
aos tipos limitados um procedimento Copy (ou Assign) e um procedimento Destroy.
Ao designar os tipos formais de um genérico,
especifique os tipos privados limitados contanto que a igualdade ou a
designação não seja requerida internamente, para maior utilidade da unidade genérica
correspondente.
Em linha com a regra anterior, você pode então importar um procedimento formal genérico Copy e Destroy e um predicado Are_Equal, se significativo.
Para os tipos privados formais genéricos,
indique na especificação se o real correspondente deve ser restrito, ou não.
Isso pode ser alcançado por uma convenção de nomenclatura e/ou comentário:
generic --Deve ser restrito. type Constrained_Element is limited private; package ...
Como alternativa, você pode utiliza o pragma definido pelo Rational Must_Be_Constrained
:
generic type Element is limited private; pragma Must_Be_Constrained (Element); package ...
Lembre-se de que derivar um tipo também deriva todos
os subprogramas que são declarados na mesma parte declarativa como o tipo pai: os
subprogramas deriváveis. Portanto, é inútil redefinir todos como skins na parte declarativa
do tipo derivado. Mas os subprogramas genéricos não são deriváveis e pode ser
necessário redefini-los como skins.
Exemplo:
package Base is type Foo is record ... end record; procedure Put(Item: Foo); function Value(Of_The_Image: String) return Foo; end Base; with Base; package Client is type Bar is new Foo; -- Nesse ponto, as seguintes declarações são -- implicitamente criadas: -- -- function "="(L,R: Bar) return Boolean; -- -- procedure Put(Item: bar); -- function Value(Of_The_Image: String) return Bar; -- end Client;
Portanto, não é necessário redefinir essas operações como skins. No entanto, observe que os subprogramas genéricos (como iteradores passivos) não são derivados juntamente com outras operações e, portanto, devem ser reexportados como skins. Os subprogramas definidos em outro lugar que não seja o da especificação que contém a declaração de tipo base também não são deriváreis e também devem ser reexportados como skins.
Especifique os valores iniciais nas declarações do objeto,
a menos que o objeto inicialize sozinho ou exista um valor inicial padrão
implícito (por exemplo, tipos de acesso, tipos de tarefa, registros com valores
padrão para campos não discriminantes).
O valor designado deve ser real e significativo, não apenas qualquer valor do tipo. Se o valor inicial real estiver disponível, como, por exemplo, um dos parâmetros de entrada, designe-o. Se não for possível computar um valor significativo, considere declarar o objeto posteriormente ou designe qualquer valor "nil", se disponível.
O nome "Nil" significa
"Uninitialized" e é utilizado para declarar as constantes que podem
ser utilizadas como "valor não utilizável, mas conhecido" que pode ser
rejeitado de uma maneira controlada pelos algoritmos.
Sempre que viável, o valor Nil não deve ser utilizado para qualquer outro propósito que não seja a inicialização, de modo que sua aparência sempre possa indicar um erro de variável não inicializada.
Observe que nem sempre é possível declarar um valor Nil para todos os tipos, especialmente os tipos modulares, como um ângulo. Nesse caso, escolha o valor menos provável.
Observe que o código para inicializar grandes
registros pode ser caro, especialmente se o registro tiver
variantes e se algum valor inicial for não estático
(ou, mais precisamente, se o valor não puder ser computado no momento da compilação). Às
vezes, é mais eficiente elaborar uma vez e para todos um valor inicial
(talvez, no pacote que define o tipo) e designá-lo explicitamente:
R : Some_Record := Initial_Value_For_Some_Record;
Nota:
A experiência mostra que as variáveis não inicializadas são uma das principais origens dos problemas no código de portabilidade e uma das principais origens de erros na programação. Isso é agravado quando o host de desenvolvimento tenta ser "agradável" ao programador fornecendo valores padrão, para ao menos alguns dos objetos (por exemplo, digite Integer no computador nativo Rational) ou quando o sistema de destino zerar a memória antes do carregamento do programa (por exemplo, em um DEC VAX). Para alcançar a portabilidade, sempre assuma o pior.
A designação de um valor inicial na
declaração pode ser omitida quando for onerosa e quando for óbvio que o objeto
recebeu um valor antes de ser utilizado.
Exemplo:
procedure Schmoldu is Temp : Some_Very_Complex_Record_Type; -- initialized later begin loop Temp := Some_Expression ... ...
Evite o uso de valores literais no código.
Utilize as constantes (com um tipo) quando o valor definido for vinculado a um tipo. Caso contrário, utilize os números nomeados, especialmente para todos os valores sem dimensão (valores puros):
Earth_Radius : constant Meter := 6366190.7; -- In meters. Pi : constant := 3.141592653; -- No units.
Defina as constantes relacionadas com expressões
universais estáticas:
Bytes_Per_Page : constant := 512; Pages_Per_Buffer : constant := 10; Buffer_Size : constant := Bytes_Per_Page * Pages_Per_Buffer; Pi_Over_2 : constant := Pi / 2.0;
Isso tira proveito do fato de que essas expressões devem ser computadas exatamente no momento da compilação.
Não declare objetos com tipos anônimos. Para obter
informações adicionais, consulte o Manual de Referência Ada 3.3.1.
A capacidade de manutenção é reduzida, os objetos não podem ser transmitidos como parâmetros e geralmente conduz aos erros de conflito de tipo.
Os subprogramas podem ser
declarados como procedimentos ou funções; seguem alguns critérios que podem ser
utilizados para escolher qual forma declarar.
Declare uma função quando:
Declare um procedimento quando:
Evite fornecer os valores padrão aos parâmetros formais genéricos utilizados para as estruturas de dimensionamento (tabelas, coletas, etc.)
Grave os procedimentos locais
com alguns efeitos colaterais, conforme a possibilidade, e as funções
sem qualquer efeito colateral. Documente o efeito colateral.
Os efeitos colaterais geralmente são modificações das variáveis globais e só podem ser observados ao ler o corpo do subprograma. O programa pode não estar ciente dos efeitos colaterais no site de chamada.
Transmitir os objetos requeridos como parâmetros torna o código mais robusto, mais fácil de ser lido e menos dependente de seu conteúdo.
Essa regra se aplica principalmente aos subprogramas locais: os subprogramas exportados geralmente requerem acesso legítimo às variáveis globais no corpo do pacote.
Utilize os
parênteses redundantes para esclarecer as expressões compostas.
O nível de aninhamento de uma expressão é definido como o número de conjuntos aninhados de parênteses requeridos para avaliar uma expressão da esquerda para a direita, se as regras de precedência do operador forem ignoradas.
Limite o nível de aninhamento das expressão para quatro.
Os agregados de
registro devem utilizar associações nomeadas e devem ser qualificados:
Subscriber.Descriptor'(Name => Subscriber.Null_Name, Mailbox => Mailbox.Nil, Status => Subscriber.Unknown, ...);
O uso de when others é proibido
para os agregados de registro.
Isso ocorre porque, diferente das matrizes, os registros são estruturas naturalmente heterogêneas e, portanto, a designação uniforme não é razoável.
Utilize expressões Booleanas simples em substituição
às instruções "if...then...else" para predicados simples:
function Is_In_Range(The_Value: Value; The_Range: Range) return Boolean is begin if The_Value >= The_Range.Min and The_Value <= The_Range.Max then return True; end if; end Is_In_Range;
Seria melhor utilizar o seguinte:
function Is_In_Range(The_Value: Value; The_Range: Range) return Boolean is begin return The_Value >= The_Range.Min and The_Value <= The_Range.Max; end Is_In_Range;
As expressões complexas que contêm duas ou mais instruções if não devem ser alteradas dessa maneira, se afetar a legibilidade.
As instruções de loop devem ter nomes:
Forever: loop ... end loop Forever;
Quando um loop tiver um nome, qualquer instrução exit
que ele contiver deve especificá-lo.
Loops que requerem um teste de conclusão no início devem
utilizar o formulário de loop "while".
Loops que requerem um teste de conclusão em algum lugar devem utilizar o formulário
geral e uma instrução exit.
Minimize o número de instruções exit em um loop.
Em um loop "for" que itera sobre uma matriz,
utilize o atributo 'Range aplicado no objeto de matriz, em vez de um intervalo explícito
ou algum outro subtipo.
Mova qualquer código independente de loop para
fora do loop. Embora "code hoisting" seja uma otimização do compilador
comum, ele não pode ser feito quando o código invariante fizer chamadas a outras
unidades de compilação.
Exemplo:
World_Search: while not World.Is_At_End(World_Iterator) loop ... Country_Search: while not Nation.Is_At_End(Country_Iterator) loop declare City_Map: constant City.Map := City.Map_Of (The_City => Nation.City_Of(Country_Iterator), In_Atlas => World.Country_Of(World_Iterator).Atlas); begin ...
No código acima, a chamada "World.Country_Of" é independente de loop (ou seja, o país permanece inalterado no loop interno). No entanto, na maioria dos casos, o compilador está proibido de mover a chamada para fora do loop, porque a chamada pode ter efeitos colaterais que possam afetar a execução do programa. Portanto, o código será executado desnecessariamente em cada vez através do loop.
O loop será mais eficiente e mais fácil de ser entendido e mantido se reescrito como:
Country_Search: while not World.Is_At_End(World_Iterator) loop declare This_Country_Atlas: constant Nation.Atlas := World.Country_Of (World_Iterator).Atlas; begin ... City_Search: while not Nation.Is_At_End (The_City_Iterator) loop declare City.Map_Of ( The_City => Nation.City_Of (Country_Iterator), In_Atlas => This_Country_Atlas ); begin ...
O subprograma e as chamadas de entrada devem utilizar associações nomeadas.
No entanto, se ficar claro que o primeiro parâmetro (ou único) é o foco principal da operação (por exemplo, um objeto direto de um verbo transitivo), o nome poderá ser omitido apenas para esse parâmetro:
Subscriber.Delete (The_Subscriber => Old_Subscriber);
em que Subscriber.Delete é o verbo transitivo e Old_Subscriber é o objeto direto. As seguintes expressões sem a associação nomeada The_Subscriber => Old_Subscriber são aceitas:
Subscriber.Delete (Old_Subscriber); Subscriber.Delete (Old_Subscriber, Update_Database => True, Expunge_Name_Set => False); if Is_Administrator (Old_Subscriber) then ...
Também há casos em que o significado dos parâmetros é tão óbvio que a associação nomeada apenas degradaria a legibilidade. Isso é válido, por exemplo, quando todos os parâmetros forem do mesmo tipo e modo e não tiverem valores padrão:
if Is_Equal (X, Y) then ... Swap (U, B);
Um when others
não deve ser utilizado em instruções case ou nas definições de tipo de registro
(para variantes).
Não utilizar um when others ajudará durante a fase de manutenção tornando essas construções inválidas sempre que a definição de tipo distinto for modificada, forçando o programador a considerar o que deveria ser feito para tratar da modificação. No entanto, isso é tolerado quando o seletor for um grande intervalo de inteiro.
Utilize uma
instrução case em vez de uma série de "elsif" quando a condição da
ramificação for um valor distinto.
Os subprogramas devem ter um único ponto de retorno.
Tente sair dos subprogramas ao final da parte da instrução. As funções devem ter uma instrução única de retorno. As instruções de retorno dispersas livremente sobre um corpo da função são comparáveis às instruções goto, dificultando a leitura e a manutenção do código.
Os procedimentos não devem ter nenhuma instrução de retorno.
Vários returns
podem ser tolerados apenas em funções muito pequenas, quando todos os returns
puderem ser vistos simultaneamente e quando o código tiver uma estrutura muito regular:
function Get_Some_Attribute return Some_Type is begin if Some_Condition then return This_Value; else return That_Other_Value; end if; end Get_Some_Attribute;
O uso das instruções goto é restrito.
Na defesa da instrução "goto"; deve ser observado que a sintaxe das etiquetas goto e as condições restritas do uso de goto no Ada torne essa instrução não tão prejudicial como se imagina e, em vários casos, isso é preferível e mais legível e significativo do que algumas construções equivalentes (uma construção goto falha com uma exceção, por exemplo).
Ao manipular as matrizes, não assuma que seus índices iniciem com 1. Utilize os atributos 'Last, 'First, 'Range.
Defina o subtipo restrito mais comum
dos tipos não restritos-na maioria registros-e utilize esses subtipos para os
parâmetros e os valores de retorno para aumentar a auto-verificação no código do cliente.
type Style is (Regular, Bold, Italic, Condensed); type Font (Variety: Style) is ... subtype Regular_Font is Font (Variety => Regular); subtype Bold_Font is Font (Variety => Bold); function Plain_Version (Of_The_Font: Font) return Regular_Font; procedure Oblique (The_Text : in out Text; Using_Font : in Italic_Font); ...
As seguintes diretrizes são recomendadas:
Subprogramas de Sobrecarga.
No entanto, certifique-se de que ao utilizar o mesmo identificador, isso realmente esteja implicando o mesmo tipo de operação.
Evite o ocultamento dos identificadores homógrafos
nos escopos aninhados.
Isso conduz à confusão para o leitor e os riscos em potencial na manutenção. Além disso, certifique-se da existência e do escopo das variáveis de controle de loop "for".
Não sobrecarregue as operações sobre os subtipos,
sempre no tipo.
Ao contrário daquilo que o leitor ingênuo pode acabar acreditando, a sobrecarga será aplicada ao tipo base e a seus subtipos.
Exemplo:
subtype Table_Page is Syst.Natural16 range 0..10; function "+"(Left, Right: Table_Page) return Table_Page;
O compilador procura pelo tipo base e não pelo subtipo de um parâmetro ao combinar os subprogramas. Portanto, no exemplo acima, "+" é redefinido realmente para todos os valores Natural16 no pacote atual, não apenas Table_Page. Portanto, qualquer expressão "Natural16 + Natural16" agora seria mapeado para uma chamada para "+"(Table_Page, Table_Page), que provavelmente retornaria o resultado incorreto ou produziria uma exceção.
Minimize o número de dependências introduzidas pelas cláusulas "with".
Onde a visibilidade for estendida pelo uso de uma cláusula "with", a cláusula deve abranger a menor região de código possível. Utilize uma cláusula "with" apenas quando necessário, idealmente apenas em um corpo ou mesmo em um grande stub de corpo.
Utilize os pacotes de interface para exportar novamente as entidades de baixo nível, evitando assim a visibilidade do "with" de um grande número de pacotes de baixo nível. Para isso, utilize os tipos derivados, a nova nomenclatura, os subprogramas de skin e, talvez, os tipos predefinidos como cadeias (assim como é feito nos pacotes de comando do Ambiente).
Utilize o acoplamento soft (fraco) entre as unidades utilizando os parâmetros formais genéricos, em vez do acoplamento hard (forte), utilizando as cláusulas "with".
Exemplo: Para exportar um procedimento Put em um tipo composto, importe como formais genéricos algum procedimento Put para seus componentes, em vez de with diretamente no Text_Io.
As cláusulas "Use" não devem ser utilizadas.
Evitar as cláusulas "use" o máximo possível aumenta a leitura e a legibilidade, contanto que essa regra seja suportada adequadamente pelas convenções de nomenclatura que fazem uso efetivo do contexto e da nomenclatura apropriada. (Consulte "Convenções de Nomenclatura", acima). Isso também ajuda a evitar algumas surpresas na visibilidade, especialmente durante a fase da manutenção.
Para um pacote que define um tipo de caractere, uma cláusula "use" é necessária em qualquer unidade compilação que precise definir os literais de cadeia com base nesse tipo de caractere:
package Internationalization is type Latin_1_Char is (..., 'A', 'B', 'C', ..., U_Umlaut, ...); type Latin_1_String is array (Positive range <>) of Latin_1_Char; end Internationalization ; use Internationalization; Hello : constant Latin_1_String := "Baba"
A ausência de uma cláusula "use" evita o uso de operadores no formato infix. Eles podem ser renomeados na unidade cliente:
function "=" (X, Y : Subscriber.Id) return Boolean renames Subscriber."="; function "+" (X, Y :Base_Types.Angle) return Base_Types.Angle renames Base_Types."+";
Como a ausência de uma cláusula
"use" geralmente conduz a incluir o mesmo conjunto de renomeações em
inúmeras unidades clientes, todas essas renomeações podem ser fatoradas no próprio
pacote de definição, através de um pacote Operations aninhado no pacote de
definição. Uma cláusula "use" no pacote Operations é então recomendado na
unidade cliente:
package Pack is type Foo is range 1 .. 10; type Bar is private; ... package Operations is function "+" (X, Y : Pack.Foo) return Pack.Foo renames Pack."+"; function "=" (X, Y : Pack.Foo) return Boolean renames Pack."="; function "=" (X, Y : Pack.Bar) return Boolean renames Pack."="; ... end Operations; private ... end Pack; with Pack; package body Client is use Pack.Operations; -- Torna APENAS as Operações diretamente visíveis. ... A, B : Pack.Foo; -- Ainda precisa do prefixo Pack. ... A := A + B ; -- Observe que "+" é diretamente -- visível.
As Operações de Pacote sempre devem ter esse nome e sempre devem ser colocados na parte inferior da parte visível do pacote de definição. A cláusula "use" deve ser colocada apenas onde necessário-ou seja, deve ser colocada apenas no corpo de Client se nenhuma operação for utilizada na especificação, o que geralmente é o caso.
with Defs; package Client is ... package Inner is use Defs; ... end Inner; -- O escopo da cláusula use é encerrado aqui. ... end Client; declare use Special_Utilities; begin ... end; -- O escopo da cláusula use é encerrado aqui.
Utilize as declarações de renomeação.
A renomeação é recomendada em conjunto com a restrição nas cláusulas "use" para facilitar a leitura do código. Quando uma unidade com um nome muito longo for referida várias vezes, fornecer um nome muito abreviado para ele aprimorará a legibilidade:
with Directory_Tools; with String_Utilities; with Text_Io; package Example is package Dt renames Directory_Tools; package Su renames String_Utilities; package Tio renames Text_Io; package Dtn renames Directory_Tools.Naming; package Dto renames Directory_Tools.Object; ...
A opção dos nomes abreviados deve ser consistente
em todo o projeto, ao manter o princípio da mínima surpresa. A forma de alcançar
isso é fornecer o nome abreviado no próprio pacote:
package With_A_Very_Long_Name is package Vln renames With_A_Very_Long_Name; ... end with With_A_Very_Long_Name; package Example is package Vln renames With_A_Very_Long_Name; -- A partir daqui on Vln é uma abreviação.
Certifique-se de que uma renomeação do pacote fornece a visibilidade apenas na parte visível do pacote renomeado.
As renomeações de pacote importadas devem ser agrupadas
no início da parte declarativa e ser alfabeticamente classificadas.
A renomeação pode ser utilizada localmente sempre
que aprimorar a legibilidade (não há penalidade de tempo de execução para isso). Os
tipos podem ser renomeados como subtipos sem restrição.
Conforme mostrado na seção dos comentários, renomear geralmente fornece uma forma elegante e passível de manutenção para documentar o código-por exemplo, para fornecer um nome simples para algum objeto complexo ou para refinar localmente o significado de um tipo. O escopo do identificador de renomeação deve ser escolhido para evitar o princípio da confusão.
As exceções de renomeação
permitem que as exceções sejam fatoradas entre diversas unidades-por exemplo, dentre
todas as instanciações de um pacote genérico. Observe que, em um pacote derivado de
um tipo, as exceções potencialmente levantadas pelos subprogramas derivados devem
ser exportadas novamente, juntamente com o tipo derivado, para evitar que os clientes tenham
"with" do pacote original:
with Inner_Defs; package Exporter is ... procedure May_Raise_Exception; -- Raises exception Inner_Defs.Bad_Schmoldu when ... ... Bad_Schmoldu : exception renames Inner_Defs.Bad_Schmoldu; ...
Os subprogramas de renomeação com diferentes
valores padrão para os parâmetros "in" podem permitir a fatoração de
código simples e aprimorar a legibilidade:
procedure Alert (Message : String; Beeps : Natural); procedure Bip (Message : String := ""; Beeps : Natural := 1) renames Alert; procedure Bip_Bip (Message : String := ""; Beeps : Natural := 2) renames Alert; procedure Message (Message : String; Beeps : Natural := 0) renames Alert; procedure Warning (Message : String; Beeps : Natural := 1) renames Alert;
Evite utilizar o nome da entidade renomeada
(o antigo nome) dentro do escopo imediato da declaração de renomeação;
utilize apenas o identificador ou o símbolo do operador introduzido pela declaração
de renomeação (o novo nome).
Por vários anos, havia uma controvérsia de cláusula "use" na comunidade Ada, que, às vezes, se aproximava de uma guerra religiosa. Ambas as partes utilizaram diversos argumentos que geralmente não são escalados bem em projetos grandes ou exemplos que estejam muito fora da realidade-ou deliberadamente desiguais.
Os defensores da cláusula "use" alegam que ela aumenta a legibilidade e fornecem exemplos de nomes especialmente ilegíveis, longos e redundantes, que se beneficiariam de serem renomeados, se utilizados várias vezes. Eles também alegam que um compilador Ada possa resolver a sobrecarga, o que é verdade, mas um humano sendo submetido a um grande programa Ada não pode executar uma resolução de sobrecarga como um compilador e certamente não tão rápido. Eles alegam que os APSEs sofisticados, como o Ambiente Rational, inutilizam os nomes explícitos completos; mas isso não é verdadeiro-o usuário não precisa pressionar [Definition] para cada identificador em dúvida. O usuário não tem que adivinhar, mas deve conseguir ver imediatamente quais objetos e quais abstrações são utilizados. Os defensores de Rosen da cláusula "use" negam seus perigos em potencial na manutenção do programa e sugerem a atribuição de um grau F ao programador que cria tais riscos; acreditamos que os nomes completos eliminam esse risco.
Se os métodos sugeridos acima para aliviar o impacto da restrição nas cláusulas "use" aparentemente precisarem de muita digitação, considere a conclusão de Norman H. Cohen: " Todo o tempo economizado durante a escrita de um programa será perdido inúmeras vezes quando o programa for revisado, depurado e mantido."
Finalmente, foi mostrado que em grandes sistemas a ausência das cláusulas "use" aprimora o tempo de compilação, reduzindo o código extra de consulta nas tabelas de símbolo.
O leitor interessado em aprender mais sobre a controvérsia da cláusula use pode consultar as seguintes origens:
D. Bryan, "Dear Ada," Ada Letters, 7, 1, January-February 1987, pp. 25-28.
J. P. Rosen, "In Defense of the Use Clause," Ada Letters, 7, 7, November-December 1987, pp. 77-81.
G. O. Mendal, "Three Reasons to Avoid the Use Clause," Ada Letters, 8, 1, January-February 1988, pp. 52-57.
R. Racine, "Why the Use Clause Is Beneficial," Ada Letters, 8, 3, May-June 1988, pp. 123-127.
N. H. Cohen, Ada as a Second Language, McGraw-Hill (1986), pp. 361-362.
M. Gauthier, Ada-Un Apprentissage, Dunod-Informatique, Paris (1989), pp. 368-370.]
Existem duas maneiras fundamentais de se decompor um grande pacote "lógico", resultante de uma fase de design inicial em várias unidades menores da biblioteca Ada que são mais fáceis de gerenciar, compilar, manter e entender:
a) A decomposição aninhada
Essa abordagem enfatiza o uso de subunidades e/ou subpacotes Ada. Os principais subprogramas, corpos de tarefas e corpos internos de pacote são sistematicamente separados. O processo é recursivamente repetido dentro dessas subunidades/subpacotes.
b) A decomposição flat
O pacote lógico é decomposto em uma rede de pacotes menores que são interconectados por cláusulas "with" e o pacote lógico original é em grande parte um skin de nova exportação (ou um artefato de design que não existe mais).
Cada abordagem possui suas vantagens e desvantagens. A decomposição aninhada requer menos código para ser escrito e conduz à nomenclatura mais simples (vários identificadores não precisam de prefixação); e, no Ambiente Rational mínimo, a estrutura é muito visível na imagem de biblioteca e a estrutura é mais fácil de ser transformada (comandos Ada.Make_Separate, Ada.Make_Inline). A decomposição simples geralmente conduz a menos recompilação e estrutura melhor ou mais limpa (especialmente nos limites do subsistema); também estimula a reutilização. Isso também torna mais fácil gerenciar as ferramentas de recompilação automática e gerenciamento de configuração. No entanto, com a estrutura simples, há um risco maior de violar o design original executando "with" de alguns dos pacotes de nível inferior que foram criados na decomposição.
O nível de aninhamento deve ser limitado para três para os
subprogramas e para dois nos pacotes; não aninhe os pacotes dentro dos subprogramas.
package Level_1 is package Level_2 is package body Level_1 is procedure Level_2 is procedure Level_3 is
Utilize os stubs do corpo para as unidades aninhadas
("corpos separados") quando ocorrer o seguinte:
A parte declarativa de uma especificação do pacote
contém declarações que devem ser organizadas na seguinte seqüência:
1) Declaração de renomeação para o pacote em si
2) Declaração de renomeação para as entidades importadas
3) Cláusulas "Use"
4) Números nomeados
5) Declarações de tipo e subtipo
6) Restrições
7) Declarações de exceção
8) Especificações exportadas do subprograma
9) Os pacotes aninhados, se houver
10) Parte privada.
Para obter um pacote que apresenta diversos tipos
principais, pode ser melhor ter vários conjuntos de declarações relacionadas:
5) Declarações de tipo e subtipo para A
6) Restrições
7) Declarações de exceção
8) Especificações exportadas do subprograma para as operações em A
5) Declarações de tipo e subtipo para B
6) Restrições
7) Declarações de exceção
8) Especificações exportadas do subprograma para as operações em B
Etc.
Quando a parte declarativa for grande
(>100 linhas), utilize os blocos de comentários para delimitar as diversas seções.
A parte declarativa das declarações do corpo do pacote
contém declarações que devem ser organizadas na seguinte seqüência:
1) Declarações de renomeação (para as entidades importadas)
2) Cláusulas "Use"
3) Números nomeados
4) Declarações de tipo e subtipo
5) Restrições
6) Declarações de exceção
7) Especificações locais do subprograma
8) Corpos locais do subprograma
9) Corpos exportados do subprograma
10) Corpos aninhados do pacote, se houver.
Outras partes declarativas,
como nos corpos do subprograma, corpos de tarefa e instruções de bloco seguem o
mesmo padrão geral.
Utilize uma cláusula "with" por unidade de
biblioteca importada. Classifique as cláusulas with em ordem alfabética. Se uma
cláusula "use" em uma unidade com "with" for apropriada, ela
deve seguir imediatamente a cláusula "with" correspondente. Consulte
abaixo para o pragma Elaborar.
Não conte com o pedido da elaboração das unidades da biblioteca
para alcançar qualquer efeito específico.
Cada implementação Ada está livre para escolher uma estratégia para calcular o pedido de elaboração, contanto que satisfaça as regras bem simples indicadas no Manual de Referência Ada [ISO87]. Algumas implementações utilizam estratégias mais inteligentes que outras (como elaboração dos corpos assim que viáveis após a especificação correspondente) e algumas implementações não se incomodam em serem inteligentes (especialmente para instanciações genéricas), conduzindo a problemas de portabilidade muito severas.
Há três origens principais para o erro infame "access before elaboration" durante a elaboração do programa (que geralmente provoca a exceção Program_Error):
task type T; type T_Ptr is access T; SomeT : T_Ptr := new T; -- Acesso antes da elaboração.
Para evitar problemas nos aplicativos de portabilidade de
um compilador Ada a outro, o programador deve eliminar os problemas reestruturando o
código (que nem sempre é possível) ou tomar controle do pedido de elaboração através
de um pragma Elaborate, utilizando a seguinte estratégia:
Na cláusula context de uma unidade Q, um pragma Elaborate deve ser aplicado a cada unidade P que aparece em uma cláusula "with":
Além disso, se P exportar um tipo T, de modo que a elaboração dos objetos do tipo T chame uma função no pacote R, a cláusula context de Q deve conter:
with R; pragma Elaborate (R);
mesmo se não houver referências diretas para R em Q!
Praticamente, pode ser mais fácil (mas nem sempre possível) indicar a regra que o pacote P deve incluir:
with R; pragma Elaborate (R);
O pacote Q deve simplesmente carregar:
with P; pragma Elaborate (P);
fornecendo, portanto, o pedido de elaboração correto pela transitividade.
Restrinja o uso de tarefas.
As tarefas são um recurso muito poderoso, mas são delicados de serem utilizados. O código extra grande em tempo e espaço pode estar associado ao uso indiscreto das tarefas. As pequenas alterações em alguma parte do sistema podem arriscar complemente a vitalidade de um conjunto de tarefas, conduzindo à fraqueza e/ou conflitos. É difícil testar e depurar os programas de criação de tarefas. Portanto, o uso de tarefas, seus posicionamentos e suas interações é uma decisão de nível do projeto. As tarefas não podem ser utilizadas de uma maneira oculta ou escritas por programadores inexperientes. O modelo de criação de tarefas de um programa Ada precisa ficar visível e inteligível.
A menos que haja suporte efetivo do hardware paralelo, as tarefas devem ser introduzidas apenas quando a simultaneidade for realmente necessária. Esse será o caso quando ao expressar ações que dependam do tempo: atividades periódicas ou introdução de tempos limites ou ações que dependam de um evento externo como uma interrupção ou chegada de uma mensagem externa. As tarefas também precisam ser introduzidas para desacoplar outras atividades, como: armazenamento em buffer, enfileiramento, dispatch e acesso de sincronização aos recursos comuns.
Especifique o tamanho da pilha da tarefa com uma cláusula
de comprimento 'Storage_Size.
Para as mesmas razões e sob as mesmas circunstâncias que conduziram ao requisito que as coletas possuem nas cláusulas de comprimento (seção "Access Types", acima), o tamanho de uma tarefa deve ser especificado nos casos em que a memória é um recurso precioso. Para isso, sempre declare as tarefas de um tipo explicitamente declarado (uma vez que a cláusula de comprimento pode ser aplicada apenas a um tipo). Uma chamada de função pode ser utilizada para dimensionar dinamicamente a pilha.
Nota: Pode ser muito difícil adivinhar a quantidade de pilha requerida por tarefa. Para facilitar, o sistema do tempo de execução pode ser instrumentado com um mecanismo "high-water mark".
Utilize uma rotina de tratamento de exceção
no corpo de uma tarefa para evitar ou, pelo menos, relatar a morte inexplicada de
uma tarefa.
As tarefas que não manipulam as exceções são encerradas-geralmente silenciosamente. Caso seja totalmente viável, tente relatar a natureza do encerramento, especialmente Storage_Error. Isso permitirá o ajuste fino do tamanho da pilha. Observe que isso requer que a alocação (primitiva nova) seja encapsulada em um subprograma que exporte novamente uma exceção diferente de Storage_Error.
Crie tarefas durante a elaboração do programa.
Para as mesmas razões e sob as mesmas circunstâncias que conduziram ao requisito no qual as coletas serão alocadas durante a elaboração do programa (seção "Access Types", acima), a criação de tarefas inteira deve ser criada bem cedo na inicialização do programa. É melhor fazer com que o programa não seja iniciado devido ao esgotamento de memória do que encerrar alguns dias mais tarde.
Nas regras subseqüentes, a distinção é feita entre as tarefas service e as tarefas application. As tarefas de serviço são tarefas pequenas e algoritmicamente simples que são utilizadas para fornecer a "cola" entre as tarefas relacionadas ao aplicativo. Os exemplos das tarefas de serviço (ou tarefas intermediárias) incluem buffers, transportadores, relays, agentes, monitores e assim por diante, que geralmente fornecem sincronização, desconexão, buffer e serviços de espera. As tarefas do aplicativo, como o nome indica, são relacionadas mais diretamente às funções primárias do aplicativo.
Evite as tarefas híbridas:
as tarefas de aplicativos devem se tornar puros responsáveis pela chamada;
as tarefas de serviços devem se tornar puros aceitadores da chamada.
Um puro aceitador da chamada inclui uma tarefa que contém apenas as instruções aceitas ou esperas seletivas e sem chamadas de entrada.
Evite circularidades no gráfico de chamadas de entrada.
Isso reduzirá consideravelmente o risco de conflitos. Evite circularidades, pelo menos, no estado fixo do sistema, se elas não puderem ser completamente evitadas. Essas duas regras também facilitam o entendimento da estrutura.
Restrinja o uso das variáveis compartilhadas.
Fique ciente especialmente das variáveis compartilhadas ocultas-ou seja, variáveis que ficam ocultas nos corpos do pacote, por exemplo, e acessadas por primitivos visíveis a diversas tarefas. As variáveis compartilhadas podem ser utilizadas em casos extremos para sincronização de acesso a estruturas de dados comuns, quando o custo dos encontros for muito alto. Verifique se o pragma Shared é suportado efetivamente.
Restrinja o uso de instruções de interrupção.
A instrução de interrupção é reconhecida universalmente como um dos primitivos mais perigosos e prejudiciais do idioma. Seu uso para terminar as tarefas incondicionalmente (e quase de for assíncrona) faz com que seja quase impossível pensar sobre o comportamento de uma determinada estrutura de criação de tarefas. No entanto, há circunstâncias muito limitadas nas quais é necessária uma instrução de interrupção.
Exemplo: Alguns serviços de baixo nível não recebem nenhum recurso para o tempo limite. A única forma de introduzir um tempo limite é fazer com que o serviço seja fornecido por alguma tarefa de agente auxiliar, aguardar (com um tempo limite) por uma resposta do agente e, em seguida, eliminar o agente com uma interrupção, se o serviço não for fornecido dentro do tempo de espera.
Uma interrupção é tolerável quando puder ser demonstrado que apenas outro anulador e anulado podem ser afetados-por exemplo, quando nenhuma outra tarefa puder possivelmente chamar a tarefa abortada.
Restrinja o uso das instruções de retardo.
A suspensão arbitrária de uma tarefa pode conduzir a graves problemas no planejamento, que são difíceis de serem rastreados e corrigidos.
Restrinja o uso dos atributos 'Count, 'Terminated e 'Callable.
O atributo 'Count deve ser utilizado apenas como uma indicação bruta e as decisões de planejamento não devem ser baseadas em seus valores como sendo zero ou não, uma vez que o número real de tarefas em espera pode mudar entre o tempo em que o atributo é avaliado e o tempo em que seu valor é utilizado.
Utilize as chamadas de entrada condicionais (ou construção equivalente com aceitação) para verificar confiavelmente a ausência das tarefas de espera.
select The_Task.Some_Entry; else -- do something else end select;
É preferível fazer o seguinte:
if The_Task.Some_Entry'Count > 0 then The_Task.Some_Entry; else -- do something else end if;
O atributo 'Terminated é significativo apenas quando produzir True, e 'Callable quando produzir False; portanto, limitando consideravelmente sua utilidade. Eles não devem ser utilizados para fornecer sincronização entre as tarefas durante o encerramento do sistema.
Restrinja o uso das prioridades.
As prioridades no Ada possuem um impacto limitado no planejamento. Em específico, as prioridades das tarefas em espera nas entradas não são consideradas para solicitar as filas de entrada ou para selecionar a entrada que atenderá em uma espera seletiva. Isso pode conduzir à inversão de prioridades (consulte [GOO88]). As prioridades são utilizadas pelo planejador apenas para selecionar a próxima tarefa a ser executada entre as tarefas prontas para execução. Devido ao risco da inversão de prioridade, não conte com as prioridades para exclusão mútua.
Ao utilizar as famílias de entradas, é possível dividir a fila de entrada em várias subfilas e com isso geralmente é possível introduzir um conceito explícito de urgência.
Se as prioridades não forem necessárias, não designe qualquer prioridade a qualquer tarefa.
Assim que uma
prioridade for designada a uma tarefa, designe uma prioridade a todas as tarefas
no aplicativo.
Essa regra é necessária porque as prioridades das tarefas sem um pragma Priority são indefinidas.
Para portabilidade,
mantenha pequeno o número de níveis de prioridade.
O intervalo do subtipo System.Priority é definido pela implementação e a experiência mostra que o intervalo real disponível varia de forma enorme de sistema para sistema. Além disso, é uma boa idéia definir centralmente as prioridades, fornecendo a elas nomes e definições, em vez de utilizar os literais de inteiros em todas as tarefas. Ter esse pacote System_Priorities central facilita a portabilidade e, juntamente com a regra anterior, permite a fácil localização das especificações de todas as tarefas.
Para evitar a tendência em tarefas cíclicas,
programe a instrução de atraso a ser considerada no tempo de processamento, código
extra e preempção de tarefa:
Next_Time := Calendar.Clock; loop -- Execute a tarefa. Next_Time := Next_Time + Period; delay Next_Time - Clock; end loop;
Observe que Next_Time - Clock pode ser negativo, indicando que a tarefa cíclica está sendo executada com atraso. Pode ser possível eliminar um ciclo.
Para garantir
a capacidade de planejamento, designe as prioridades para as tarefas
cíclicas de acordo com o Algoritmo de Planejamento Monotônico de Taxa-ou seja
a mais alta prioridade para a tarefa mais freqüente. (Consulte
[SHA90] para obter detalhes adicionais.)
Designe uma prioridade mais alta para servidores intermediários muito rápidos: monitores, buffers.
Mas, em seguida, certifique-se de que esses servidores não se comprometem outras tarefas. Documente essa prioridade no código de modo que possa ser respeitada durante a manutenção do programa.
Para minimizar o efeito de
"jitter", conte com as amostras de entrada de marca de hora ou dados de
saída, e não com o próprio período.
Evite a espera ocupada (polling).
Certifique-se de que as tarefas esperam com chamadas de seleção ou de entrada, ou que sejam atrasadas, em vez de procurar furiosamente por algo a ser feito.
Para cada encontro,
certifique-se de que, pelo menos, um lado está aguardando e que apenas um lado
possua uma chamada de entrada em condicional ou chamada de entrada
marcada ou esperas.
Caso contrário, de forma notável em loops, há o risco da execução do código em uma condição de disputa, altamente semelhante em resultado de uma espera ocupada. Isso pode ser agravado pelo uso insuficiente das prioridades.
Ao encapsular as tarefas, certifique-se de deixar
algumas de suas características especiais altamente visíveis.
Se as chamadas da entrada forem ocultas nos subprogramas, certifique-se de que o leitor da especificação desses subprogramas estejam cientes de que a chamada a esse subprograma pode ser bloqueada. Além disso, especifique se a espera é limitada; se estiver, forneça alguma estimativa do limite superior. Utilize uma convenção de nomenclatura para indicar a espera em potencial (seção "Subprograms", acima).
Se a elaboração de um pacote, a chamada de um subprograma ou a instanciação de uma unidade genérica ativarem uma tarefa, torne esse fato visível ao cliente:
package Mailbox_Io is -- Esse pacote elabora uma tarefa interna Control -- que sincroniza todo o acesso à -- caixa postal externa procedure Read_Or_Wait (Name: Mailbox.Name; Mbox: in out Mailbox.Object); -- -- Bloqueando (espera não limitada).
Não dependa de qualquer pedido específico para a seleção
de entrada em uma espera seletiva.
Se forem necessárias algumas justificativas na seleção das tarefas enfileiradas nas entradas, faça isso verificando explicitamente as filas sem espera no pedido desejado e, em seguida, aguarde em todas as entradas. Não utilize 'Count.
Não dependa de qualquer pedido de ativação específica
para as tarefas elaboradas na mesma parte declarativa.
Se for procurado um pedido de inicialização específico, isso deve ser feito através de um encontro com as entradas especiais de inicialização.
Implemente as tarefas a serem terminadas de forma normal.
A menos que a natureza do aplicativo necessite que as tarefas, depois de ativadas, sejam executadas eternamente, as tarefas devem ser terminadas, através da conclusão normal ou através de uma alternativa de terminação. Isso pode ser impossível para tarefas cujo master é um pacote no nível de biblioteca, uma vez que o Manual de Referência Ada não especifica sob qual condição elas devem ser terminadas.
Se a estrutura dependente de master não permitir a terminação limpa, as tarefas devem fornecer e esperar por entradas especiais de encerramento, que são chamadas durante o encerramento do sistema.
A filosofia geral é utilizar as exceções apenas para os erros: erros lógicos e de programação, erros de configuração, dados corrompidos, esgotamento de recursos, etc. A regra geral é que os sistemas em condição normal e na ausência de sobrecarga ou falha de hardware não levante qualquer exceção.
Utilize as exceções para
manipular os erros lógico e de programação, erros de configuração,
dados corrompidos, esgotamento de recursos. Relate as exceções pelo mecanismo de log
apropriado assim que possível, incluindo no ponto de elevação.
Minimize o número de exceções exportadas de uma determinada abstração.
Em grandes sistemas, ter que manipular um grande número de exceções em cada nível dificulta a leitura e a manutenção do código. Às vezes, o processamento da exceção atrapalha o processamento normal.
Há várias formas de minimizar o número de exceções:
Não propague as exceções não especificadas no design.
Evite uma alternativa when
others nas rotinas de tratamento de exceção, a menos que a exceção capturada
seja levantada novamente.
Isso permite que haja alguma organização interna local sem interferir nas exceções que não podem ser manipuladas nesse nível:
exception when others => if Io.Is_Open (Local_File) then Io.Close (Local_File); end if; raise; end;
Outro local em que uma alternativa when others pode ser utilizada é na parte inferior de um corpo de tarefa.
Não utilize as exceções para eventos
freqüentes e antecipados.
Há várias inconveniências na utilização de exceções para representar condições que não são claramente erros:
Por exemplo, não utilize uma exceção como alguma forma de valor extra retornado por uma função (como Value_Not_Found em uma procura); utilize um procedimento com um parâmetro "out" nem o introduza algum valor especial indicando Not_Found, nem compacte o tipo retornado em um registro com um Not_Found discriminante.
Não utilize as exceções para implementar as estruturas de
controle.
Esse é um caso especial da regra anterior: as exceções não devem ser utilizadas como uma forma de instrução "goto".
Ao capturar as exceções predefinidas, coloque
a rotina de tratamento em um quadro bem pequeno que cerca a construção que a levanta.
As exceções predefinidas como Constraint_Error, Storage_Error e assim por diante podem ocorrer em vários locais. Se uma exceção desse tipo precisar ser capturada por alguma razão específica, a rotina de tratamento deve ser limitada no espaço, assim que possível:
begin Ptr := new Subscriber.Object; exception when Storage_Error => raise Subscriber.Collection_Overflow; end;
Termine as rotinas de tratamento de exceção
nas funções com uma instrução "return" ou uma instrução "raise". Caso
contrário, a exceção Program_Error será levantada no responsável pela chamada;
Restrinja a supressão de verificações.
Com os compiladores Ada atuais, as reduções potenciais no tamanho do código e os aumentos no desempenho são obtidas pela supressão das verificações se tornaram marginais. Portanto, a supressão das verificações deve ser restrita a partes bem limitadas do código que deve ser identificada (por medições) como gargalos de desempenho; ela nunca deve ser aplicada amplamente em um sistema inteiro.
Como resultado, não inclua um intervalo extra explícito e a verificação discriminante apenas para o caso improvável que alguém decidirá posteriormente para suprimir as verificações. Confie nos recursos de verificação de restrição internos Ad's.
Não propague exceções fora do escopo de suas declarações.
Isso tornará impossível para o código do cliente manipular explicitamente a exceção, diferente de uma alternativa when others, que pode não ser específica o suficiente.
Um resultado para essa regra é: ao exportar novamente um tipo por derivação, pense em exportar novamente as exceções que os subprogramas derivados possam levantar-renomeando, por exemplo. Caso contrário, os clientes terão que executar "with" do pacote de definição original.
Sempre manipule Numeric_Error e Constraint_Error em conjunto.
O Quadro Ada decidiu que todas as circunstâncias teriam levantado Numeric_Error se Constraint_Error for levantado, em substituição.
Certifique-se de que os códigos de status possuam um valor apropriado.
Ao utilizar o código de status retornado pelos subprogramas como um parâmetro "out", sempre certifique-se de que um valor seja atribuído ao parâmetro "out", fazendo com que essa seja a primeira instrução executável no corpo do subprograma. Sistematicamente, torne todos os status um êxito por padrão ou uma falha por padrão. Considere todas as saídas possíveis do subprograma, incluindo as rotinas de tratamento de exceção.
Execute as
verificações de segurança localmente; não espere que seu cliente faça isso.
Ou seja, se um subprograma produzir saída errônea, a menos que seja fornecida a entrada apropriada, instale o código no subprograma para detectar e relatar a entrada inválida de uma maneira controlada. Não confie em um comentário que informe ao cliente para transmitir os valores apropriados. É virtualmente garantido que, mais cedo ou mais tarde, esse comentário será ignorado, resultando em erros de difícil depuração, se os parâmetros inválidos não forem detectados.
Para obter informações adicionais, consulte [KR90b].
Essa seção trata dos recursos Ada que são uma priori não portáteis. Elas são definidas no capítulo 13 do Manual de Referência para a Linguagem de Programação Ada [ISO87] e os recursos específicos do compilador são descritos no "Apêndice F" fornecido pelos fornecedores do compilador Ada.
Estude cuidadosamente
o Apêndice F do Manual de Referência Ada (e conduza os experimentos pequenos para
garantir que sejam bem entendidos).
Restrinja o uso das cláusulas de representação.
As cláusulas de representação não são suportadas uniformemente de implementação para implementação. Seus usos contêm várias interrupções. Portanto, não devem ser utilizadas livremente em um sistema.
As cláusulas de representação devem fazer o seguinte:
As cláusulas de representação podem ser evitadas nos seguintes tipos de situações:
Exemplo:
Substitua:
type Foo is (Bla, Bli, Blu, Blo); for Foo use (Bla => 1, Bli =>3, Blu => 4, Blo => 5);
type Foo is (Invalid_0, Bla, Invalid_2, Bli, Blu, Blo);
Agrupe os tipos que possuem cláusulas de representação
em pacotes claramente identificados como contendo o código dependente de
implementação.
Nunca assuma um pedido específico no layout de registro.
Em uma cláusula de representação de registro, sempre especifique
o posicionamento de todos os discriminantes e faça isso especificando todos os
componentes nas variantes.
Evite as cláusulas de alinhamento.
Confie no compilador para fazer um bom trabalho; ele conhece as restrições de alinhamento de destino. O uso do programador das cláusulas de alinhamento provavelmente conduzirá posteriormente a conflitos no alinhamento.
Certifique-se da existência de campos gerados
pelo compilador nos tipos compostos não restritos:
em registros: deslocamento de campos dinâmicos, índice de cláusula variante, bit restrito e assim por diante
nas matrizes: vetores dope.
Consulte o Apêndice F para o compilador para obter detalhes. Não se apoie naquilo que está escrito no capítulo 13 do Manual de Referência Ada [ISO87].
Restrinja o uso de Unchecked_Conversion.
A extensão do suporte para Unchecked_Conversion varia muito de um compilador Ada para outro, e seu comportamento preciso pode ser ligeiramente diferente, especialmente quando aplicado aos tipos compostos e tipos de acesso.
Em uma instância Unchecked_Conversion, certifique-se de que
os tipos de origem e de destino são restritos e possuem o mesmo tamanho.
Essa é a única maneira de alcançar alguma portabilidade limitada e evitar problemas na implementação-informações incluídas como vetores dope. Uma maneira de garantir que ambos os tipos possuem o mesmo tamanho é "agrupá-los" em um tipo de registro com uma cláusula de representação de registro.
Uma maneira de restringir o tipo é executar a instância em uma função "skin", em que a restrição é computada antecipadamente.
Não aplique Unchecked_Conversion
para acessar valores ou tarefas.
Não apenas isso não é suportado por todos os sistemas (por exemplo, o compilador Rational nativo), mas também não deve ser assumido que:
Recapitulamos aqui os fatos mais importantes a serem observados:
Esse documento é derivado diretamente de Diretrizes Ada: Recomendações para Designer e Programadores, Nota do Aplicativo Nº15, Rev. 1.1, Rational, Santa Clara, Ca., 1990. [KR90a]. No entanto, várias origens diferentes contribuíram para sua elaboração.
BAR88 B. Bardin & Ch. Thompson, "Composable Ada Software Components and the Re-export Paradigm", Ada Letters, VIII, 1, Jan.-Feb. 1988, p.58-79.
BOO87 E. G. Booch, Software Components with Ada, Benjamin/Cummings (1987)
BOO91 Grady Booch: Object-Oriented Design with Applications, Benjamin-Cummings Pub. Co., Redwood City, California, 1991, 580p.
BRY87 D. Bryan, "Dear Ada," Ada Letters, 7, 1, January-February 1987, pp. 25-28.
COH86 N. H. Cohen, Ada as a Second Language, McGraw-Hill (1986), pp. 361-362.
EHR89 D. H. Ehrenfried, Tips for the Use of the Ada Language, Application Note #1, Rational, Santa Clara, Ca., 1987.
GAU89 M. Gauthier, Ada-Un Apprentissage, Dunod-Informatique, Paris (1989), pp. 368-370.
GOO88John B. Goodenough and Lui Sha: "The Priority Ceiling Protocol," special issue of Ada Letters, Vol., Fall 1988, pp. 20-31.
HIR92 M. Hirasuna, "Using Inheritance and Polymorphism with Ada in Government Sponsored Contracts", Ada Letters, XII, 2, March/April 1992, p.43-56.
ISO87 Reference Manual for the Ada Programming Language, International Standard ISO 8652:1987.
KR90a Ph. Kruchten, Ada Guidelines: Recommendations for Designer and Programmers, Application Note #15, Rev. 1.1, Rational, Santa Clara, Ca., 1990.
KR90b Ph. Kruchten, "Error-Handling in Large, Object-Based Ada Systems," Ada Letters, Vol. X, No. 7, (Sept. 1990), pp. 91-103.
MCO93 Steve McConnell, Code Complete-A Practical Handbook of Software Construction, Microsoft® Press, Redmond, WA, 1993, 857p.
MEN88 G. O. Mendal, "Three Reasons to Avoid the Use Clause," Ada Letters, 8, 1, January-February 1988, pp. 52-57.
PER88 E. Perez, "Simulating Inheritance with Ada", Ada letters, VIII, 5, Sept.-Oct. 1988, p. 37-46.
PLO92 E. Ploedereder, "How to program in Ada 9X, Using Ada 83", Ada Letters, XII, 6, November 1992, pp. 50-58.
RAC88 R. Racine, "Why the Use Clause Is Beneficial," Ada Letters, 8, 3, May-June 1988, pp. 123-127.
RAD85 T. P. Bowen, G. B. Wigle & J. T. Tsai, Specification of Software Quality Attributes, Boeing Aerospace Company, Rome Air Development Center, Technical Report RADC-TR-85-37 (3 volumes).
ROS87 J. P. Rosen, "In Defense of the Use Clause," Ada Letters, 7, 7, November-December 1987, pp. 77-81.
SEI72 E. Seidewitz, "Object-Oriented Programming with Mixins in Ada", Ada Letters, XII, 2, March/April 1992, p.57-61.
SHA90 Lui Sha and John B. Goodenough: "Real-Time Scheduling Theory and Ada," Computer, Vol. 23, #4 (April 1990), pp. 53-62.)
SPC89 Software Productivity Consortium: Ada Quality and Style-Guidelines for the Professional Programmer, Van Nostrand Reinhold (1989)
TAY92 W. Taylor, Ada 9X Compatibility Guide, Version 0.4, Transition Technology Ltd., Cwmbran, Gwent, U.K., Nov. 1992.
WIC89 B. Wichman: Insecurities in the Ada Programming Language, Report DITC137/89, National Physical Laboratory (UK), January 1989.
A maioria dos termos utilizados nesse documento são definidos no Apêndice D do Manual de Referência da Linguagem de Programação Ada, [ISO87]. Os termos adicionais são definidos aqui:
ADL: Ada como uma Linguagem de Design; refere-se à maneira como o Ada é utilizado para expressar os aspectos de um design; também conhecido como PDL ou Program Design Language.
Ambiente: o ambiente de desenvolvimento de software Ada em uso.
Comutador de biblioteca: No Ambiente Rational, uma opção de compilação que se aplica a uma biblioteca inteira de programa.
Mundo de modelo: No Ambiente Rational, uma biblioteca especial que é utilizada para capturar o projeto uniforme-configurações de comutação de biblioteca ampla.
Mutável: Propriedade de um registro cujos discriminantes possuem valores padrão; um objeto de um tipo mutável pode ser designado a qualquer valor do tipo, mesmo os valores que fazem com que sejam alterados seus discriminantes, daí sua estrutura.
Skin: Um subprograma cujo corpo atua unicamente como um relay. O ideal é que contenha apenas uma instrução: uma chamada para outro subprograma, com um conjunto idêntico de parâmetros ou parâmetros que possam ser convertidos de e para o parâmetro.
PDL: Program Design Language.