Arquivo da categoria ‘Engenharia de Software’

A biblioteca glibc, na versão 2.18, foi liberada no dia 12/08/2013. Mas o que é a glibc? Pretendo escrever outro post para descrever com mais detalhes essa resposta, mas em resumo…

Toda aplicação escrita em linguagem C, para ser executada num ambiente com ou sem sistema operacional, faz uso da biblioteca padrão C. Portanto, todo sistema Unix-like precisa de uma implementação dessa biblioteca, sendo que a mais utilizada em sistemas Linux é a glibc (GNU C Library). Existem outras implementações, dependendo da aplicação final, tais como uClibc, dietlibc, musl, Newlib, EGLIBC, etc.

Mesmo uma aplicação simples, contendo somente a instrução return 0, precisa da biblioteca padrão C? Sim! A não ser que você implemente todas as funções de bootstrap, tal como _start, as quais são chamadas pelo sistema operacional ou código de start-up ao executar o binário da aplicação em memória. Sem maiores detalhes, vamos às grandes mudanças nessa nova versão da glibc:

– Melhorado o suporte ao padrão C++11, referente ao uso de objetos do tipo thread_local;

– Melhorado o pior caso de performance das funções da biblioteca matemática libm;

– Adicionado suporte à herança de prioridade em mutex usado em pthread condition variables em arquiteturas não-x86;

– Adicionado suporte às arquiteturas Xilinx MicroBlaze e POWER8.

Todas as mudanças podem ser conferidas aqui.

Até mais,

Henrique

Como ocorre anualmente, o evento TDC2013 São Paulo (The Developer’s Conference) contou com diversas trilhas sobre assuntos diversos, tal como agile, cloud, games, testes, etc. Além dessas, foram realizadas trilhas de C/C++, Embedded e Segurança, das quais eu tive o prazer de participar.
 
Tanto a trilha de segurança quanto a trilha de embarcados foram estreantes, graças à força de vontade e entusiasmo de pessoas como Alberto Fabiano, Alan Silva e Vinicius Senger. Muito obrigado pessoal!!
 
Fiquei muito mais agradecido pois pude contribuir um pouco para a trilha de segurança, apresentando uma palestra sobre codificação segura em C para sistemas embarcados. A mesma teve o seguinte foco:
 
– Por que usar linguagem C?
– Como evitar alguns erros simples
– Padrões de codificação
– Ferramentas de análise estática e dinâmica de código
– Ataques comuns (code injection e arc injection)
– Uso de alocação dinâmica de código
 
O download da apresentação pode ser feito aqui, mas também a disponibilizei no meu slideshare.
 
Espero que este conteúdo ajude a comunidade de sistemas embarcados! Obrigado novamente aos organizadores e ao público fera desse evento, e podem contar comigo para o próximo ano!
 
Abraços,
Henrique

Objetivo

O objetivo deste artigo é destacar a importância da arquitetura de software de um projeto, embarcado ou não, para o desenvolvimento do mesmo, até porque esse assunto é muito extenso. O que é arquitetura de software? Para que serve? Por que requisitos não-funcionais são importantes? Responderemos a essas questões a seguir!


O que é arquitetura de software?

Pelo fato de arquitetura de software ser uma disciplina em crescimento e ainda muito jovem, não existe uma definição única para a mesma. A discussão sobre essa definição é muito extensa, já que existem diferentes pontos de vista envolvidos. As definições com as quais concordo são as seguintes:

The software architecture of a program or computing system is the structure or structures of the system, which comprise software elements, the externally visible properties of those elements, and the relationships among them.” [1]

Architecture is defined by the recommended practice as the fundamental organization of a system, embodied in its components, their relationships to each other and the environment, and the principles governing its design and evolution.” [2]

Nas definições acima são mencionados elementos, estruturas, propriedades e relacionamentos. Cada elemento possui uma implementação privada, que contém suas propriedades, e uma interface pública, que, por sua vez, propicia relacionamentos com outros elementos. E o conjunto desses elementos forma uma estrutura. Desse modo, pode-se entender a arquitetura de software como uma abstração do sistema que omite detalhes dos seus elementos, ou seja, suas partes privadas, e uma representação da relação que esses possuem entre si.

As partes privadas dos elementos não afetam como esses usam, são usados por, relacionados a ou interagem com os outros elementos do sistema. Portanto, a definição de qual estruturas de dados devem ser utilizadas e encapsuladas não é uma decisão arquitetural, ao passo que as interfaces para tais estruturas de dados faz parte da arquitetura do projeto. E pode-se dizer, também, que o comportamento de um elemento é considerado parte da arquitetura somente caso o mesmo possa ser observado ou reconhecido por outro elemento do sistema.

Dado o conceito, é interessante utilizar um exemplo de documentação de arquitetura de software. A Figura 1 auxilia nesse sentido.

Figura 1 – Exemplo de arquitetura de software

O exemplo está correto, respeitando inclusive a definição de arquitetura de software referenciada nesse artigo. No entanto, o que deve ser analisado é o que ele representa e o que não é possível concluir a partir do mesmo, tal como: a origem de cada elemento; significado da separação entre os elementos; ambiente de execução de cada elemento, podendo ser em um único ou múltiplos processadores; processamento distribuído; responsabilidades de cada elemento no sistema; significado das relações entre os elementos, podendo haver relações de uso, controle, sincronização e acesso a dados entre si; conteúdo e formato das informações que trafegam entre os elementos; etc.

Muito bem! Agora sabemos, resumidamente, o que é arquitetura. Como ela é criada, para que ela serve e como definimos suas estruturas?

Um projeto de software deve atender requisitos, os quais são divididos em dois grandes tipos: funcionais e não-funcionais ou qualidades. De forma resumida, os requisitos funcionais definem o que o sistema deve fazer, ao passo que as qualidades ditam como o sistema deve ser. Esses últimos são a base da arquitetura.

Qualidades

Qualidades, também conhecidas como atributos de qualidade, requisitos não-funcionais, requisitos não-comportamentais, etc…, são requisitos que traduzem como o sistema deve ser e não o que esse deve fazer, auxiliando na implementação das funcionalidades do sistema. Dentre as qualidades existentes, pode-se citar: usabilidade, desempenho, modificabilidade, segurança, testabilidade, etc.

Embora algumas qualidades e funcionalidades sejam intimamente ligadas, as últimas ganham muito mais foco que as primeiras na etapa de desenvolvimento, o que prejudica a visão de futuro do sistema. Por esse motivo que, dada uma modificação pedida pelo cliente, geralmente os sistemas são redesenhados, visto que os mesmos são difíceis de manter, portar para outras plataformas ou escalar, tanto horizontalmente quanto verticalmente.

Antes de serem estabelecidas tais qualidades, devem ser analisadas as restrições que são impostas à solução, as quais podem possuir origem econômica, técnica, sistêmica, política, ambiental e de planejamento e recursos, como mencionado em [3]. Tais restrições, após estudadas, podem ou não gerar requisitos para o sistema em questão.

E como são listadas as qualidades que o sistema deve possuir? Existem algumas técnicas: entrevista com o cliente; análise de negócio; análise do problema, encontrando causas raízes e soluções para o mesmo; estudo se soluções similares; etc.

A seguir seguem exemplos de declarações de requisitos funcionais, qualidades e restrições:

Requisito funcional

– O sistema deve receber/enviar requisições via comunicação de rede (Ethernet ou Wi-Fi) de/para um terminal servidor.

Restrições do sistema

Técnica: O código da solução deve ser implementado nas linguagens C ou C++ pois são as linguagens dominadas pelos desenvolvedores da equipe;

Sistêmica: A solução deve manter compatibilidade com a solução existente, que faz uso do protocolo XYZ para comunicação entre um terminal cliente e um terminal servidor;

Econômica: Não é permitido gastar acima de R$5.000,00 em licenças de software.

Requisitos não-funcionais ou qualidades

Desempenho: O sistema deve ser capaz de tratar, no mínimo, 10 requisições originadas pelo terminal servidor dentro de um segundo;

Modificabilidade: O sistema deve ser fácil de ser modificado a fim de atender mudanças futuras;

Disponibilidade: O sistema deve responder 100% das requisições efetuadas pelo servidor.

Muito bom pessoal! Até agora sabemos que é importante definir as qualidades do sistema. Só que como essas são testadas? Para isso são usados cenários de qualidade, que são compostos por seis partes:

1) Fonte do estímulo: é uma entidade, seja um humano ou um sistema computacional, que gera um estímulo;

2) Estímulo: é um evento que deve ser processado pelo sistema;

3) Ambiente: é o conjunto de condições sob o qual o estímulo ocorreu, como, por exemplo, estado normal de operação, estado de erro ativo, etc;

4) Artefato: o que é afetado pelo estímulo, podendo ser todo o sistema ou parte do mesmo;

5) Resposta: é a resposta ao estímulo;

6) Medida da resposta: é a maneira pela qual a resposta é medida de modo que a qualidade possa ser testada.

A partir do momento que é possível listar cenários de qualidade reais para um sistema, esses podem ser usados como requisitos dos mesmos. Muito bom! Vamos criar, então, um cenário de modificabilidade, já que trata-se de uma qualidade difícil de ser testada? Mãos à obra!

Na listagem de qualidades feita anteriormente foi dada a descrição de modificabilidade para o sistema exemplo. Mas dizer que deve ser fácil alterar um sistema é relativo. O que é ser “fácil”?

Modificabilidade é determinada por dois fatores: arquiteturais (como as funcionalidades são decompostas) e não-arquiteturais (técnicas de design e implementação utilizadas). Tendo em vista tais fatores, deve-se atentar à necessidade de fazer com que mudanças afetem o menor número possível de elementos do sistema, e, principalmente, deve ser levado em consideração o tempo necessário para que as mesmas sejam implementadas. Portanto, podem ser considerados níveis de complexidade para mudanças e, para cada nível especificado, um tempo de implementação deve ser estipulado.

Desse modo, pode-se definir diversos cenários de modificabilidade reais, os quais comprovam que um sistema é ou não modificável e impõe um fim à implementação da qualidade no caso de sucesso ser obtido em todos os cenários de teste. Segue um exemplo:

1) Fonte do estímulo: desenvolvedor de software.

2) Estímulo: mudança de complexidade média na API do módulo Display.

3) Ambiente: em tempo de desenvolvimento.

4) Artefato: código fonte.

5) Resposta: implementações realizadas sem causar efeitos colaterais nos outros módulos ou na arquitetura.

6) Medida da resposta: modificação finalizada em até 8 horas.

São considerados os seguintes níveis de complexidade:

– baixa: mudanças realizadas em até 4 horas;

– média: mudanças realizadas em até 8 horas;

– alta: mudanças realizadas em até 21 horas.

Agora sabemos que dada uma qualidade, sabemos como essa pode ser testada. Mas como ela é atingida? Por meio de táticas e padrões arquiteturais!

Táticas e padrões arquiteturais

Táticas são decisões arquiteturais que influenciam a resposta dada por um sistema com relação à uma ou mais qualidades. Como exemplos de táticas para disponibilidade pode-se citar detecção de falhas (ping/echo, heartbeat e tratamento de exceções), já para modificabilidade pode-se usar modificações localizadas (coerência semântica, antecipação de mudanças esperadas e generalização de módulos) e redução de efeitos colaterais (ocultação de informação e comprometimento com as interfaces existentes).

Além de táticas, podem ser utilizados padrões arquiteturais, os quais implementam um pacote de táticas já conhecidas. Esses são, em sua essência, frameworks arquiteturais, descritos por meio de um conjunto de componentes computacionais, ou simplesmente componentes, e as interações que esses realizam entre si, denominadas conectores. De modo visual, pode-se entender a arquitetura de software de um sistema como um grafo no qual seus vértices representam os componentes e suas arestas interpretam os conectores. Tais conectores podem representar chamadas à funções, eventos, queries de banco de dados, pipes, etc.

Desse modo, um padrão arquitetural define:

– um conjunto de elementos (componentes);

– uma topologia estrutural de seus elementos;

– um conjunto de mecanismos de interações (conectores) e;

– um conjunto de restrições semânticas para seu uso.

Seguem alguns dos padrões arquiteturais existentes:

pipes e filtros;

– abstração de dados e organização orientada a objetos;

– invocação orientada a eventos;

– orientação a componentes;

– funcional;

– declarativo;

– sistemas em camadas;

– repositórios e blackboards;

– interpretadores;

– sistemas distribuídos;

– arquiteturas de software para domínio específico;

– MVC.

Um dos padrões arquiteturais que é muito utilizado em sistemas embarcados é o de sistema em camadas. Um sistema desse tipo é organizado hierarquicamente, de modo que cada camada ofereça serviços para a camada acima e atue como um cliente para a camada logo abaixo. Geralmente as interações entre as camadas são realizadas por meio de chamadas de funções, como mostrado na Figura 2.

Figura 2 – Padrão arquitetural de sistemas em camadas

Portanto, uma das atividades iniciais de um arquiteto de software ao iniciar um projeto é, após entender as qualidades que o sistema deve apresentar, escolher o padrão arquitetural que melhor se adapta ao sistema em questão. Um dos aspectos mais importantes de padrões arquiteturais é que esses já apresentam soluções para requisitos não-funcionais já conhecidos.

Os relacionamentos entre os elementos de uma arquitetura podem ser reproduzidos por meio de estruturas.

Estruturas arquiteturais

Os sistemas de software atuais são muito complexos para serem entendidos de uma única vez. Para isso, é necessário entender a função de cada estrutura ou conjunto de estruturas do software. Uma comparação pode ser feita ao corpo humano. Existem médicos para cada especialização, tal como cardiologia, neurologia, obstetrícia, etc…, no entanto o corpo humano é único. Cada um desses especialistas utiliza uma visão em especial do corpo humano para realizar o seu trabalho, ao invés de inspecionar o corpo como um todo.

Mas o que são essas estruturas? São conjuntos de elementos, sejam de software, sejam de hardware, que, em conjunto, comunicam a organização arquitetural do projeto de software. Basicamente, é possível dividir tais estruturas em três grandes grupos:

1) Estruturas de módulos: É uma forma de representar os elementos da arquitetura, que neste caso são especificados como módulos, de forma estática. A esses módulos são atribuídas responsabilidades funcionais, o que permite distinguir a relação de uso entre cada elemento do sistema e, portanto, o acoplamento dos módulos entre si. Exemplos: Estruturas de decomposição, uso, camada e classe.

2) Estruturas de componente e conector: É um modo de representação dinâmica do sistema, onde cada componente é um elemento computacional e um conector é uma via de comunicação entre componentes. Ajuda a encontrar o caminho percorrido pelos dados, processos concorrentes e paralelos, etc. Exemplos: Estruturas de comunicação entre processos, concorrência, repositório e cliente-servidor.

3) Estruturas de alocação: É a representação da relação entre os elementos de software com os elementos do ambiente externo no qual o software está inserido. Auxilia na alocação de processos por processador do computador, na distribuição das tarefas de cada elemento de software entre a equipe de desenvolvimento, etc. Exemplos: Estruturas de implantação, implementação e distribuição de trabalho.

Agora você me pergunta…qual estrutura utilizar? Não existe uma resposta igual para todos os projetos, mas sim aquela que melhor se encaixa num projeto em específico. Um artigo sobre o assunto pode ser encontrado em [4]. Não tem sentido documentar uma estrutura de cliente-servidor (do tipo componente e conector) quando o padrão de repositório for utilizado no projeto, já que a troca de informações entre seus módulos é realizada por meio de um repositório de dados centralizado. O importante é notar que uma estrutura não é a arquitetura em si, mas sim um modo de visualizar como os seus elementos interagem entre si.

Por exemplo, o diagrama de uso é de grande necessidade quando o padrão arquitetural de repositório é utilizado. Esse padrão impõe que sejam especificados módulos com responsabilidades bem definidas e que esses produzam e/ou consumam dados de um repositório de dados centralizado. A estrutura de decomposição, como mostrado na Figura 3, especifica somente os módulos do sistema e indica que os mesmos produzem ou consomem dados do repositório. No entanto, falta um detalhe: como é verificado o fluxo de dados no sistema, desde a produção até o consumo? A estrutura de uso auxilia nessa função! Na Figura 4 é demonstrado o uso dos módulos entre si. Como exemplo, o módulo Compras consome os dados produzidos pelo módulo Produtos (o sentido da seta indica o sentido de uso), ao passo que o módulo Log armazena no repositório de dados informações de log geradas por todos os módulos do sistema, tal como erros ocorridos ao longo de um processo de compra ou cadastro de usuário.

Figura 3 – Estrutura de decomposição de um sistema que utiliza o padrão de repositório

Concluindo, por que arquitetura de software é importante? Pode-se listar algumas razões técnicas:

– criação de uma abstração do sistema que proporciona um meio de comunicação entre os interessados no projeto ou stakeholders;

– definição das decisões iniciais do projeto, as quais contribuem para o seu desenvolvimento e manutenção futura;

– promoção do reuso de arquitetura, já que a mesma, por trata-se de uma abstração de um sistema, pode ser reutilizada em outros sistemas que necessitem exibir os mesmos atributos de qualidade e comportamentos funcionais.

Figura 4 – Estrutura de uso de um sistema que utiliza o padrão de repositório

Desse modo concluímos esse artigo e teremos novos sobre esse assunto em breve!

Bibliografia

[1] BASS, L.; CLEMENTS, P.; KAZMAN, R. Software Architecture in Practice. 2nd ed. [S.l]: Addison-Wesley Professional, 2003.

[2] ANSI/IEEE Std 1471-2000, Recommended Practice for Architectural Description of Software-Intensive Systems

[3] LEFFINGWELL, D.; WIDRIG, D. Managing Software Requirements: A Unified Approach. [S.l]: Addison-Wesley Professional, 1999.

[4] Kruchten P. (1995), Architectural Blueprints—The “4+1” View Model of Software Architecture, [Online], Disponível em: http://www.cs.ubc.ca/~gregor/teaching/papers/4+1view-architecture.pdf [14 de Maio de 2012]