Engenharia de Software: Conceitos e organização de domínios

A Engenharia de Software é uma disciplina que impõe disciplina e organização a uma atividade que pode ser caótica. Trabalhar na área de programação exige dos profissionais mais do que apenas habilidades em linguagens e IDEs. É necessário também modelar todo o processo de construção do produto.

Neste artigo apresentaremos uma estrutura teórica para uma disciplina moderna de Engenharia de Software, ajustando levemente sua taxonomia para refletir como a indústria e a academia (através do Software Engineering Body of Knowledge – SWEBOK) organizam esses domínios. O mapa mental hierárquico que estamos descrevendo está representado na Figura 1.

Figura 1 – Mapa mental adotado para o conteúdo teórico de Engenharia de Software.

O que é Engenharia de Software?

Diferentemente da Ciência da Computação, que foca nos fundamentos teóricos, matemáticos e algorítmicos, a Engenharia de Software (ES) foca na construção de sistemas práticos dentro de restrições de custo, tempo e confiabilidade. Segundo Somerville (2011), a ES lida com processos, ferramentas e técnicas para a produção de software de alta qualidade.

Engenharia de Requisitos

Esta é a base de todo projeto. Nesta fase do desenvolvimento, o objetivo é descobrir o que o sistema deve fazer e quais são suas restrições. Para isso, elaboramos a lista de Requisitos Funcionais e Não Funcionais, que são basicamente a distinção entre o comportamento do sistema e suas qualidades (performance, segurança, usabilidade).

Os engenheiros de software nesta etapa buscam utilizar técnicas para extrair informações sobre as necessidades dos stakeholders (pessoas envolvidas com o sistema) e assim fazer análises mais profundas sobre o contexto da aplicação.

Projeto de Software (Design)

Definidos os requisitos do sistema, passamos para a fase de design, que é onde transformamos o “quê” (requisitos) no “como” (solução técnica). Podemos dividir esta etapa em três níveis de abstração:

Design Orientado a Objetos

O paradigma de programação orientada a objetos (OO) sustenta a maioria dos sistemas modernos, e pode ser definida como um modelo em que o software é composto de objetos interativos que mantêm seu próprio estado local e oferecem operações nesse estado. A construção de software a partir da OO foca em conceitos como encapsulamento, herança, polimorfismo e abstração.

A abstração constitui uma das maneiras fundamentais de lidar com a complexidade, permitindo simplificar elementos sistêmicos ao descrevê-los em níveis de linguagem superiores ao código propriamente dito (Pressman, 2011). Esse princípio elimina a necessidade de comunicar detalhes técnicos exaustivos, concentrando-se na essência do comportamento que o software deve exibir (Sommerville, 2011). Complementarmente, o encapsulamento estabelece um limite em torno de um conjunto coeso de dados e funções, ocultando a representação interna de um objeto e garantindo que seu estado local seja protegido de acessos externos diretos (Martin, 2019). Essa prática assegura a independência funcional dos componentes, pois permite que detalhes algorítmicos e de estruturas de dados sejam modificados sem impactar as partes do sistema que utilizam o objeto por meio de sua interface pública (Pádua 2001; Silveira et al. 2011).

O princípio da herança atua como um mecanismo importante para a extensão da funcionalidade e para o reúso de código, permitindo que novas classes derivem atributos e comportamentos de superclasses existentes (Wazlawick, 2013). De acordo com as normas clássicas de design, é necessário distinguir entre a herança de classe, focada no compartilhamento de código e representação física, e a herança de interface, que descreve a subtipificação ou quando um objeto pode ser utilizado no lugar de outro (Gamma et al., 2007). Ao aplicar esse conceito, o projetista deve assegurar que as subclasses sejam substitutas de suas classes-base, respeitando o Princípio de Substituição de Liskov, o que exige que qualquer classe derivada honre o contrato implícito estabelecido pela sua superclasse (Pressman, 2011; Gamma et al., 2007).

O polimorfismo representa a capacidade de substituir objetos com interfaces coincidentes por outros em tempo de execução por meio da ligação dinâmica, o que simplifica as definições de clientes e desacopla os objetos entre si (Gamma et al., 2007). Do ponto de vista da arquitetura de sistemas, esse conceito é crucial porque oferece ao engenheiro controle absoluto sobre a direção de cada dependência de código-fonte, permitindo a inversão de dependências (Martin, 2019). Assim, o uso disciplinado de interfaces possibilita que módulos que contêm regras de negócio e políticas de alto nível tornem-se independentes de módulos que contêm detalhes de baixo nível, como bancos de dados ou interfaces de usuário, facilitando a criação de arquiteturas plug-in (Martin, 2019; Sommerville, 2011)

Design de Software

Neste nível, focamos na manutenibilidade e legibilidade do código. Em outras palavras, buscamos formas de organização do código que permitam facilidade para os desenvolvedores corrigirem erros ou adicionarem recursos (features) futuramente.

Existem várias estratégias para organizar seu código. Citamos brevemente algumas delas abaixo (outros artigos neste site tratarão de forma mais completa):

  • Clean Code (Código Limpo): são práticas de escrita que priorizam a leitura humana e que também foram pensadas por Robert C. Martin (conhecido como “Uncle Bob“). Elas focam em discutir o nome de variáveis, construção de funções pequenas, formatação e comentários úteis.
  • SOLID: constitui um conjunto de diretrizes para a organização de funções e estruturas de dados em classes e módulos, visando a criação de sistemas que tolerem mudanças, sejam fáceis de compreender e sirvam de base para componentes reutilizáveis. Discutidas inicialmente por Robert C. Martin, elas objetivam tornar o design de software mais compreensível, flexível e sustentável (Martin, 2011). Em resumo, SOLID é uma parte prática e arquitetural do ecossistema de Clean Code.
  • Design Patterns (Padrões de Projeto): constituem descrições abstratas de objetos e classes comunicantes, as quais são personalizadas para solucionar problemas recorrentes de design em contextos particulares da engenharia de software. Baseiam-se no trabalho clássico da “Gang of Four” (Gamma et al., 2007).
  • Modelagem de Domínio (Entities, Value Objects, DTOs): fundamenta-se na identificação de abstrações estáveis que refletem a lógica de negócio e as prioridades dos stakeholders, permitindo que a equipe se concentre na essência do problema antes da implementação técnica (Sommerville, 2011; Pressman, 2011).

Arquitetura de Software

Enquanto o design foca nos componentes internos, a arquitetura foca na estrutura global e nas decisões difíceis de mudar (Fowler, 2002). Alguns exemplos de arquitetura são:

  • Arquitetura Monolítica: o ponto de partida clássico para organização de código. Caracteriza-se por ser implementada em um único arquivo executável no qual todos os componentes compartilham o mesmo espaço de endereçamento, o que simplifica o desenvolvimento inicial e a comunicação interna, mas pode dificultar a escalabilidade e a manutenção em longo prazo (Martin, 2019; Sommerville, 2011).
  • Arquitetura em Camadas: propõe a organização da funcionalidade em níveis lógicos independentes, garantindo que cada camada utilize apenas os serviços fornecidos pela camada imediatamente inferior (Sommerville, 2011; Silveira et al., 2011).
  • Arquitetura Hexagonal: idealizada por Alistair Cockburn sob o conceito de “portas e adaptadores”, estabelece as bases para o isolamento do código de domínio em relação aos detalhes de infraestrutura, segregando o sistema em um núcleo interno e interações externas que dependem obrigatoriamente desse centro. Tem como objetivo primordial viabilizar o desenvolvimento de sistemas independentes de ferramentas.
  • Clean Architecture (Arquitetura Limpa): Martin (2019) sistematiza essas ideias na Arquitetura Limpa, integrando modelos precedentes em uma estrutura de círculos concêntricos governada pela Regra da Dependência, na qual as referências de código devem apontar apenas para as políticas de nível mais alto situadas nas camadas internas. Segue o mesmo objetivo da arquitetura hexagonal.
  • Microsserviços: estilo arquitetural para sistemas distribuídos e altamente escalável. Divide uma grande aplicação em serviços menores, focados em um domínio de negócio, implantáveis de forma independente. Eles podem se comunicar de forma síncrona (como REST/HTTP) ou assíncrona.
  • Event-driven (Orientado a Eventos): igualmente escalável com o microsserviço, é um padrão de design onde os serviços reagem a “eventos” (ex: “pedido criado”) emitidos por outros componentes. Proporciona alto desacoplamento e reatividade. É frequentemente usada em conjunto com microsserviços para criar sistemas escaláveis e fracamente acoplados.

Implementação do código

Passada a etapa de planejamento e definição do sistema, começamos a implementar o código conforme determinado pela equipe/empresa. Não se trata apenas de digitar, mas também da gestão do controle de versão dos arquivos (usando Git ou Apache Subversion – SVN) e da prática de CI/CD (Integração Contínua/Entrega ou Implantação Contínua), que é uma prática DevOps que automatiza o desenvolvimento, testes e lançamento de software.

Testes e Qualidade de Software

A gestão da qualidade de software ultrapassa a mera detecção de falhas, consolidando-se como um paradigma preventivo que integra processos, métodos e ferramentas para garantir a entrega de produtos que agreguem valor real aos usuários (Pressman, 2011).

Nesse cenário, a atividade de teste desempenha um papel crítico como mecanismo de verificação e validação, operando sob o princípio fundamental de que sua execução visa descobrir a presença de erros latentes, embora não possa comprovar a ausência total de defeitos (Sommerville, 2011). É neste momento que o papel do(a) QA (Quality Assurance) vira protagonista, garantindo que o software atenda aos padrões exigidos.

É válido ressaltar que a qualidade superior é intrínseca ao rigor da prática de engenharia, onde o emprego de revisões técnicas e estratégias de teste multicamadas assegura que o software não apenas atenda às especificações técnicas, mas também cumpra as expectativas funcionais e de desempenho dos stakeholders (Pádua, 2000; Wazlawick, 2013).

Manutenção e Evolução

Segundo Lehman (1980), um sistema que é usado frequentemente sofrerá mudanças inevitáveis ou então acabará se tornando progressivamente menos útil. Esta fase do ciclo de vida do software envolve correção de bugs, adaptação a novos ambientes e refinamento de funcionalidades.

Benefícios e Trade-offs

A adoção de uma estrutura rigorosa traz benefícios claros tais como previsibilidade, redução de dívida técnica e facilidade de escala. No entanto, o “custo de abstração” é o principal trade-off.

Projetar um sistema seguindo rigorosamente a Clean Architecture com todos os Design Patterns, por exemplo, pode introduzir uma complexidade acidental desnecessária se o problema a ser resolvido for simples. O desenvolvedor sênior (categoria mais experiente na classe dev) sabe que a engenharia é a arte de escolher qual problema você prefere ter.

Engenheiro de Software vs. Desenvolvedor

A diferença fundamental entre o papel do engenheiro de software e o do desenvolvedor reside na natureza de suas atividades: enquanto o desenvolvedor é um executor do processo de construção, o engenheiro de software possui um papel voltado para a criação e otimização dos próprios processos de trabalho. Descrevendo de outra forma, enquanto o engenheiro garante que a infraestrutura, o processo e as ferramentas sejam os mais eficazes para atingir a qualidade e a previsibilidade esperadas, o desenvolvedor aplica essa disciplina para criar o produto.

Wazlawick (2013) sugere que o engenheiro de software “não coloca a mão na massa”, ou seja, não mexe diretamente com o código. Entretanto, autores como Martin (2011; 2019), enfatizam que um arquiteto ou engenheiro sênior deve continuar sendo um programador. Abandonar o código para tratar apenas de questões de alto nível é considerado um erro, pois o engenheiro não conseguiria vivenciar os problemas que os processos por ele criados impõem aos demais desenvolvedores.

Na prática, o que acontece é que as pessoas que conseguem evoluir na carreira tendem a dividir seu tempo produtivo mais com obrigações de gestão e menos com código, apesar de não ser uma regra determinística. Se haverá uma redução completa ou não do tempo de programação deste profissional, depende muito da organização da empresa e da equipe de tecnologia.

Conclusão

A Engenharia de Software não é uma matéria para falar apenas sobre “como escrever código”, mas sobre a aplicação de uma abordagem sistemática, disciplinada e quantificável ao desenvolvimento, operação e manutenção de software (IEEE, 1990). Para um estudante de graduação, compreender a hierarquia entre os conceitos é o que separa um programador amador de um profissional que projeta sistemas resilientes.

Obrigado pela leitura e bons estudos!

Referências Bibliográficas

FOWLER, Martin. Patterns of Enterprise Application Architecture. Boston: Addison-Wesley Professional, 2002.

GAMMA, Erich et al. Padrões de Projetos: Soluções Reutilizáveis de Software Orientados a Objetos. Porto Alegre: Bookman, 2007.

IEEE Computer Society. Software Engineering Body of Knowledge (SWEBOK). 1990.

LEHMAN, Meir M. Programs, life cycles, and laws of software evolution. Proceedings of the IEEE, v. 68, n. 9, p. 1060-1076, 1980.

MARTIN, Robert C. Clean Code: A Handbook of Agile Software Craftsmanship. Upper Saddle River: Prentice Hall, 2011.

MARTIN, Robert C. Arquitetura Limpa: O Guia do Artesão para Estrutura e Design de Software. Rio de Janeiro: Alta Books, 2019.

PÁDUA, Wilson de. Engenharia de Software: Fundamentos, Métodos e Padrões. Rio de Janeiro: LTC, 2001.

PRESSMAN, Roger S. Engenharia de Software: Uma Abordagem Profissional. 7. ed. Porto Alegre: AMGH, 2011.

SILVEIRA, Paulo et al. Introdução à Arquitetura e Design de Software. São Paulo: Casa do Código, 2011.

SOMMERVILLE, Ian. Engenharia de Software. 9. ed. São Paulo: Pearson Prentice Hall, 2011.

WAZLAWICK, Raul Sidnei. Engenharia de Software: Conceitos e Práticas. Rio de Janeiro: Elsevier, 2013.