quinta-feira, 18 de março de 2010

No Silver Bullet (Sem Bala de Prata): Essência e Acidentes da Engenharia de Software

O artigo "No Silver Bullet" ("Sem Bala de Prata" ou "Não há Bala de Prata"), escrito por Frederick P. Brooks, Jr, em 1986, discute sob diversos pontos de vista os problemas e dificuldades encontradas nos processos de engenharia de software. É incrível como, apesar de ter sido escrito na década de 1980, este texto ainda seja tão atual!

O autor começa seu texto comparando o software ao lobisomem, dizendo que, de todos os monstros que preenchem os pesadelos de nosso folclore, nenhum nos aterroriza mais que lobisomens, porque eles inesperadamente se transformam do familiar ao horrível. Assim, procuram-se balas de prata que possam magicamente fazê-los descansar. O projeto de software familiar, pelo menos do ponto de vista do gerente não técnico, tem algo desta personagem; ele é normalmente inocente e honesto, mas capaz de se tornar um monstro de prazos deficientes, orçamentos estourados e produtos falhos. Surgem então súplicas desesperadas por uma bala de prata – algo que faça os custos de software cair tão rapidamente quanto os custos de hardware de computador.


Isto Precisa Ser Ouvido?—Dificuldades Essenciais

Não há balas de prata em vista, e a própria natureza do software torna improvável de que haverá alguma. Ou seja, nenhuma invenção irá fazer pela produtividade, confiança e simplicidade do software o que eletrônicos, transistores e integração em alta escala fizeram pelo hardware de computador.

Primeiro, deve-se observar que a anomalia não é que o progresso do software seja tão lento, mas que o progresso do hardware de computador seja tão rápido. Em nenhuma outra tecnologia alguém pode escolher ganhos tanto em melhoria de desempenho quanto em custos reduzidos.

Segundo, para ver qual taxa de progresso se pode esperar na tecnologia de software, devemos examinar as dificuldades daquela tecnologia. Seguindo Aristóteles, o autor as divide em:

Essência: as dificuldades inerentes na natureza do software.
Acidentes: dificuldades que afetam a produção do software, mas não são inerentes à sua natureza.

A essência de uma entidade do software é a construção de conceitos engrenados: conjuntos de dados, relacionamentos entre itens de dados, algoritmos e chamadas de funções. Esta essência é tão abstrata que uma construção conceitual é a mesma sob muitas representações diferentes. Portanto, é altamente precisa e rica em detalhes. O autor acredita que a parte mais difícil na construção do software seja a especificação, o projeto e o teste desta construção conceitual, e não o trabalho de representá-lo e testar a fidelidade da representação.

A construção de um software será sempre difícil. Não há nenhuma bala de prata inerente. Seguem as propriedades inerentes desta essência irredutível dos sistemas de software modernos:

Complexidade: as entidades do software talvez sejam mais complexas para seu tamanho que qualquer outra construção humana porque duas partes não são similares (pelo menos acima do nível de especificação). Se elas forem similares, criamos as duas partes em uma sub-rotina – aberta ou fechada. Neste aspecto, sistemas de software diferem profundamente de computadores, prédios ou automóveis, onde elementos repetidos abundam.

O escalonamento de uma entidade de software não é meramente a repetição dos mesmos elementos em tamanhos maiores, mas necessariamente um aumento no número de elementos diferentes. Na maioria dos casos, os elementos interagem entre si de forma não linear, e a complexidade do todo cresce muito mais que linearmente.

A complexidade do software é uma propriedade essencial, não acidental. Por isso, descrições de uma entidade de software que abstraem sua complexidade sempre abstraem sua essência.

Muitos dos problemas clássicos no desenvolvimento de produtos de software derivam desta complexidade essencial e seu aumento não linear de tamanho. Da complexidade surge dificuldade de comunicação entre os membros da equipe, o que leva a falhas no produto, custos exacerbados e atrasos nos prazos estabelecidos. Da complexidade surge dificuldade de enumerar, muito menos entender todos os estados do programa e a origem da deficiência. Da complexidade da função vem dificuldade de chamar a função, o que torna os programas difíceis de usar. Da complexidade de estrutura surge dificuldade de estender os programas com novas funções sem criar efeitos colaterais. Da complexidade de estrutura surgem estados invisíveis que constituem furos de segurança.

Não somente problemas técnicos, mas também de gerência surgem da complexidade. Isto dificulta a visão global, além de impedir a integridade conceitual. Torna difícil encontrar e controlar todos os pontos deficientes. Cria uma carga tremenda de aprendizagem e entendimento, o que torna a rotatividade de pessoal um desastre.

Conformidade: Einstein argumentava que deve haver explicações simplificadas da natureza, porque Deus não é excêntrico ou arbitrário. Porém, nenhuma fé conforta o engenheiro de software. Grande parte da complexidade que ele tem que dominar é de caráter arbitrário, forçada sem uma razão exata por muitas instituições humanas e sistemas para os quais suas interfaces devem se adaptar. Diferem de interface para interface e de tempo para tempo, não devido à necessidade, mas somente porque foram projetados por diferentes pessoas, em vez de Deus. Em muitos casos, o software deve se adaptar porque é novo e entrou em cena recentemente. Em outros, porque é percebido como o mais adaptável. Mas, em todos os casos, grande parte da complexidade surge da conformidade com outras interfaces; esta complexidade não pode ser simplificada por qualquer re-projeto isolado do software.

Mutabilidade: a entidade do software está constantemente sujeita às pressões por mudanças.
O software, dentro de um sistema, pode ser modificado mais facilmente – é puramente objeto intelectual, infinitamente maleável. Todo software de sucesso sofre mudança. Como o produto de software é feito para ser útil, as pessoas o testam em seu limite ou além do domínio original. As pressões por função estendida vêm principalmente dos usuários que apreciam a função básica e inventam novos usos para a mesma.

Um software de sucesso sobrevive além da vida normal do veículo de máquina para o qual é primeiramente escrito. Novos computadores, discos, displays, impressoras surgem e o software deve ser adaptável a estes dispositivos.

Em suma, o produto de software é incorporado a uma matriz cultural de aplicações, usuários, leis e veículos de máquina, que estão em constante mudança e suas mudanças, inexoravelmente, forçam mudanças no produto de software.

Invisibilidade: o autor defende que o software é invisível e invisualizável. Abstrações geométricas são ferramentas poderosas, mas a realidade do software não está incorporada inerentemente ao espaço. Por isso, não possui uma representação geométrica como, por exemplo, uma região possui mapas. Assim que tentamos diagramar a estrutura do software, descobrimos que ela não possui um, mas muitos gráficos superpostos. Os diversos gráficos podem representar o relacionamento entre fluxo de controle, fluxo de dados, camadas de dependência, seqüência de tempo e name-space.

Estes gráficos normalmente não são planos, nem hierárquicos. De fato, uma das formas de estabelecer um controle conceitual sobre tal estrutura é forçar o corte de vínculo até que um ou mais gráficos se tornem hierárquicos.

Apesar do progresso em restringir e simplificar as estruturas do software, elas permanecem inerentemente invisualizáveis e, por isso, não permitem à mente usar algumas de suas ferramentas mais poderosas. Esta carência não somente impede o processo de projeto dentro de uma mente, mas também dificulta bastante a comunicação entre mentes.


Progressos do Passado Resolveram Dificuldades Acidentais

O autor defende que, ao examinarmos as três etapas no desenvolvimento da tecnologia de software que foram mais produtivas no passado, descobrimos que cada uma atacou uma dificuldade diferente na construção do software, mas que aquelas dificuldades foram acidentais, não essenciais.

Linguagens de alto nível: a linha mais poderosa para a produtividade, confiança, simplicidade e inteligibilidade do software foi o uso das linguagens de alto nível na programação. A linguagem de alto nível livra um programa de muitas das suas complexidades acidentais. Seu maior benefício é fornecer todas as construções que o programador imagina no programa abstrato. E o desenvolvimento da linguagem se aproxima cada vez mais da sofisticação dos usuários.

Divisão do tempo (time-sharing): a divisão do tempo trouxe melhorias principalmente na produtividade dos programadores e na qualidade de seu produto, mas não tanto quanto as trazidas pelas linguagens de alto nível. A divisão de tempo preserva o imediatismo e, por isso, permite manter uma visão geral da complexidade.

Ambientes de programação unificada: Unix e Interlisp, os primeiros ambientes de programação integrada que chegaram a um uso abrangente, melhoraram a produtividade pois atacaram as dificuldades acidentais que resultam do uso de programas individuais em conjunto, através do fornecimento de bibliotecas integradas, formatos de arquivo unificados, filtros, entre outros. Como resultado, estruturas conceituais que, em princípio, poderiam chamar, alimentar e usar uma outra, podem fazê-lo facilmente na prática.


Esperanças para a Prata

O autor chama o leitor para refletir sobre os desenvolvimentos técnicos que são avançados como potenciais balas de prata. Quais problemas eles visam – problemas de essência, ou as dificuldades acidentais permanentes? Eles oferecem avanços revolucionários, ou incrementais?

Programação orientada a objeto: o autor mantém mais esperança em programação orientada a objeto do que em qualquer outra novidade técnica atual. Mesmo assim, defende que tais avanços não fazem mais do que remover todas as dificuldades acidentais de expressão do projeto. A complexidade do projeto por si só é essencial e estes ataques não trazem nenhuma mudança na mesma.
Inteligência artificial: muitas pessoas esperam avanços na inteligência artificial para prover o progresso revolucionário que proporcionará ganhos na ordem de magnitude em produtividade e qualidade de software. Mas o autor não. Para verificar o porquê, ele dissecar o que se entende por “Inteligência Artificial”.

D.L. Parnas esclareceu o caos terminológico: [4]

“Duas definições diferentes de IA estão em uso comum atualmente. IA-1: o uso de computadores para resolver problemas que anteriormente puderam ser resolvidos somente pela aplicação da inteligência humana. IA-2: o uso de um conjunto específico de técnicas de programação conhecidas como heurística ou programação baseada em regras. Nesta abordagem, experts humanos são estudados para determinar o que a heurística ou regras eles utilizam para resolver problemas... O programa é projetado para resolver um problema da forma que os humanos o resolveriam.”

“A primeira definição possui um significado deslizante... Algo pode se encaixar na definição da IA-1 atualmente, mas uma vez que vemos como o programa trabalha e entende o problema, não pensaremos mais nele como IA... Infelizmente não posso identificar um corpo de tecnologia que seja único para este campo... A maioria do trabalho é específica do problema e é necessária alguma abstração ou criatividade para verificar como transferi-la.”

O autor concorda com esta crítica. Por exemplo, as técnicas usadas para reconhecimento de fala parecem ter pouco em comum com aquelas usadas para reconhecimento de imagem e ambas são diferentes daquelas usadas em sistemas experts. O difícil na construção do software é decidir o que alguém quer dizer, não dizendo. Nenhuma facilidade de expressão pode trazer mais que ganhos marginais.

Sistemas expert: de acordo com o autor, a parte mais avançada da arte da inteligência artificial, e a mais aplicada, é a tecnologia para construir sistemas expert. Um sistema expert é um programa que contém uma máquina de inferência generalizada e uma base de regra, tem dados de entrada e suposições, explora as inferências deriváveis da base de regra, fornece conclusões e avisos, além de oferecer explicações sobre os resultados re-traçando o raciocínio para o usuário.

O poder destes sistemas vem das bases de conhecimento cada vez mais ricas, que refletem o mundo real com mais precisão. O autor crê que o avanço mais importante oferecido pela tecnologia seja a separação entre a complexidade da aplicação e o programa. Esta tecnologia pode ser aplicada à engenharia de software de diversas formas, como: sugestões sobre diferentes regras, avisos sobre estratégias de teste, alertar sobre as freqüências de tipos de bug e oferecer dicas de otimização.

A maior contribuição dos sistemas experts será colocar a serviço do programador inexperiente a experiência e a sabedoria acumulada dos melhores programadores. E esta contribuição não é pequena. A lacuna entre a melhor prática de engenharia de software e a prática média é muito grande – talvez maior que em qualquer outra disciplina de engenharia. Uma ferramenta que dissemine boas práticas é importante.

Programação “Automática”: o autor cita Parnas: “Em suma, a programação automática tem sempre sido mais um eufemismo para a programação com uma linguagem de alto nível do que os recursos atualmente disponíveis ao programador.” E conclui que é difícil ver como esta técnica generaliza para o mundo mais extenso do sistema de software ordinário, onde casos com propriedades nítidas são exceções. E também é difícil imaginar como este progresso na generalização pode ocorrer.

Programação gráfica: o autor diz que, às vezes, o teórico justifica a abordagem por considerar fluxogramas o meio de projeto de programa ideal por fornecerem facilidades poderosas para construí-lo. Mas defende que nada convincente ainda surgiu destes esforços e crê que não surgirá, pois vê o fluxograma como uma abstração muito pobre da estrutura do software e as telas da época eram muito pequenas, em pixels, para mostrar tanto o escopo quanto a resolução de qualquer diagrama de software detalhado. E conclui que, mais fundamentalmente, é muito difícil visualizar o software.

Verificação do programa: o autor não acredita em grandes mágicas em produtividade com este recurso. A verificação do programa é um conceito poderoso, mas não promete poupar tanto trabalho. Embora verificação possa reduzir a carga de teste de programa, não pode eliminá-la. Indo além, mesmo uma verificação perfeita do programa pode somente confirmar que o programa atende às suas especificações. A parte mais difícil da tarefa de software é chegar a uma especificação completa e muito da essência de um programa é, na verdade, debugar a especificação.

Ambientes e ferramentas: o autor diz que editores inteligentes específicos de linguagem são desenvolvimentos ainda não utilizados amplamente na prática, mas prometem principalmente resolver problemas de sintaxe e erros simples de semântica. Talvez o maior ganho dos ambientes de programação ainda seja o uso de sistemas de banco de dados integrados para acompanhamento dos inúmeros detalhes que devem ser recuperados apropriadamente pelo programador individual e mantidos atualizados para um grupo de colaboradores em um sistema único. O autor conclui que este trabalho é válido e trará frutos em produtividade e confiança. Mas, pela sua própria natureza, o retorno deve ser marginal.

Estações de Trabalho: o autor diz que as estações de trabalho cada vez mais poderosas são bem vindas, mas não podemos esperar progressos mágicos a partir delas.


Ataques Promissores na Essência Conceitual

De acordo com o autor, embora nenhum progresso tecnológico prometa trazer resultados mágicos com os quais somos tão familiares na área de hardware, há a abundância de bons trabalhos e a promessa de um progresso, se não espetacular, estável. Todos os ataques tecnológicos sobre os acidentes do processo de software são fundamentalmente limitados pela equação de produtividade:

tempo_de_tarefa = ∑ (freqüência) x (tempo)

Se os componentes conceituais da tarefa agora estão tomando a maioria do tempo, nenhuma atividade sobre os componentes da tarefa que são meramente a expressão dos conceitos pode trazer altos ganhos de produtividade. Por isso o autor defende que devemos considerar aqueles ataques que indicam a essência do problema de software, a formulação destas estruturas conceituais complexas. Felizmente, alguns desses ataques são muito promissores.

Comprar versus construir: a solução mais radical possível para a construção do software é não construí-lo completamente.

Todos os dias isto se torna mais fácil, já que cada vez mais vendedores oferecem mais e melhores produtos de software para uma variedade vertiginosa de aplicações. Enquanto os engenheiros de software têm trabalhado na metodologia de produção, a revolução do computador pessoal criou não um, mas muitos mercados de massa para o software. Fontes mais especializadas oferecem produtos de muito poderosos, e ferramentas de software e ambientes podem ser comprados de prateleira.

Qualquer um destes produtos é mais barato que construir um novo. Mesmo a um custo de cem mil dólares, o preço de compra do software é equivalente ao salário anual de somente um programador. A entrega é imediata e tais produtos tendem a ser muito melhor documentados e mantidos que o software feito em casa.

O autor defende que o desenvolvimento do mercado de massa seja a tendência na engenharia de software. O custo do software tem sempre sido de desenvolvimento, não de replicação. Dividir este custo mesmo entre alguns usuários corta radicalmente o custo por usuário. Outra forma de olhar para isto é que o uso de n cópias de um sistema de software multiplica efetivamente a produtividade de seus desenvolvedores por n.

Muitos usuários agora operam seus próprios computadores diariamente, utilizando diversas aplicações sem nunca ter escrito um programa. De fato, muitos desses usuários não podem escrever novos programas para as suas máquinas, mas são adeptos da solução de novos problemas com os mesmos.

O autor ainda defende que a estratégia de produtividade de software mais poderosa para muitas organizações seja equipar os trabalhadores intelectuais sem conhecimentos de computador com computadores pessoais e programas de escrita, desenho, arquivo e planilha. A mesma estratégia, executada com pacotes matemáticos e estatísticos e alguns recursos simples de programação, funcionará para centenas de cientistas de laboratório.

Refinamento de requisitos e prototipagem rápida: a parte mais difícil na construção de um sistema de software é decidir precisamente o que construir. Nenhuma outra parte do trabalho conceitual é tão difícil quanto estabelecer requisitos técnicos detalhados, incluindo todas as interfaces para pessoas, máquinas e outros sistemas de software. Nenhuma outra parte do trabalho prejudica o sistema resultante caso seja feita incorretamente. Nenhuma outra parte é tão difícil de corrigir mais tarde.

Portanto, as funções mais importantes que o construtor de software desenvolve para o cliente são a extração e o refinamento iterativos dos requisitos do produto. A verdade é que o cliente não sabe o que quer. O cliente normalmente não sabe quais questões precisam ser respondidas, e ele nunca pensou no problema com o detalhamento necessário para a especificação. Nem mesmo a simples resposta “Faça o novo sistema de software funcionar como o nosso velho sistema de processamento de informação manual” é na verdade tão simples. Ele nunca quer exatamente aquilo. Além disso, sistemas de software complexos são coisas que agem, movem e trabalham. É difícil de imaginar a dinâmica esta ação. Desta forma, no planejamento de qualquer atividade de projeto de software, é necessário permitir uma iteração extensiva entre o cliente e o projetista como parte da definição do sistema.

O autor ainda afirma que é realmente impossível para um cliente, mesmo trabalhando com um engenheiro de software, especificar completa, precisa e corretamente os requisitos exatos de um produto de software moderno antes de testar algumas versões do produto.

Portanto, um dos esforços tecnológicos atuais mais promissores e que ataca a essência do problema de software, e não os acidentes, é o desenvolvimento de abordagens e ferramentas para prototipagem rápida de sistemas, uma vez que a prototipagem é parte da especificação iterativa de requisitos. Um sistema de protótipo de software simula as interfaces importantes e desempenha as principais funções do sistema pretendido. Os protótipos normalmente desempenham as principais tarefas da aplicação, mas não tentam tratar as tarefas excepcionais, responder corretamente a entradas inválidas ou abortar de forma limpa. O propósito do protótipo é tornar real a estrutura conceitual especificada, para que o cliente possa testá-la em termos de consistência e usabilidade.

O autor diz que muito do procedimento atual de aquisição de software repousa sobre a suposição de que se pode especificar um sistema satisfatório antecipadamente, receber propostas para sua construção, construí-lo e instalá-lo. E defende que esta suposição é essencialmente incorreta, e que muitos problemas na aquisição de software surgem desta ilusão. Por isso, não podem ser corrigidos sem uma revisão fundamental – que prevê o desenvolvimento iterativo e a especificação de protótipos e produtos.

O desenvolvimento incremental evolui o software, não constrói. E o autor nos convida a transformar a natureza e a complexidade do estudo em matérias vivas, em vez de trabalhos mortos do homem. E diz que o cérebro sozinho é intrincado além de mapeamento, poderoso além da imitação, rico em diversidade, auto-protetor e auto-renovador. O segredo é que o cérebro é evoluído, não construído, e da mesma forma deve ocorrer com sistemas de software.

O autor ainda descreve a experiência de desenvolvimento incremental obtida no Software Engineering Laboratory, onde ministrava aulas, e defende que os mesmos benefícios obtidos em seus pequenos projetos podem ser obtidos em grandes projetos.

Grandes projetistas: o autor defende que a questão central de como melhorar os centros de software está nas pessoas. Podemos ter bons projetos seguindo boas práticas, que podem ser pensadas. Ressalta que os programadores fazem parte da camada mais inteligente da população, desta forma podem aprender boas práticas. Por isso, o grande impulso é propagar boas práticas modernas. Novos currículos, nova literatura, novas organizações a fim de elevar o nível de nossa prática de ruim para bom. Excelentes projetos vêm de excelentes projetistas. A construção do software é um processo criativo. Uma metodologia sólida pode capacitar e libertar a mente criativa. Muitos estudos mostram que projetistas muito melhores produzem estruturas que são mais velozes, menores, simples, limpas e com menos esforço.

Segundo o autor, um pequeno retrospecto mostra que, embora muitos sistemas de software bons e úteis tenham sido projetados por comitês, e construídos como parte de projetos multi-partes, os sistemas de software que realmente empolgaram e incitaram fãs apaixonados são produtos de uma ou poucas grandes mentes projetistas. Por isso, embora apóie fortemente os esforços de transferência de tecnologia e desenvolvimento dos programas de estudos em andamento, o autor pensa que o esforço mais importante a se realizar seja desenvolvermos maneiras de formar grandes projetistas.

Nenhuma organização de software pode ignorar este desafio, Bons gerentes, ainda que sejam escassos, não são tão escassos como bons projetistas. Grandes projetistas e grandes gerentes também são muito raros. A maioria das organizações faz esforços consideráveis para encontrar e cultivar grandes gerentes, mas o autor não conhece, ao menos na época, nenhuma que faça um esforço igual para encontrar e desenvolver grandes projetistas, de quem a excelência técnica dos produtos irá ultimamente depender.

A proposta do autor é que cada organização de software deve determinar e proclamar que grandes projetistas são tão importantes para o seu sucesso quanto grandes gerentes e, similarmente, nutri-los e recompensá-los. Não somente salário, mas gratificações de reconhecimento – tamanho do escritório, mobiliários, equipamento técnico pessoal, fundos para viagens e pessoal de apoio. E sugere alguns passos para que as organizações desenvolvam grandes projetistas:

 Identificar os melhores projetistas o mais cedo possível. Os melhores nem sempre são os mais experientes.
 Atribuir um mentor de carreira para ser responsável pelo desenvolvimento de cada profissional identificado e montar cuidadosamente um plano de carreira.
 Criar e manter um plano de desenvolvimento de carreira para o profissional, incluindo cuidadosamente aprendizagens selecionadas com os melhores projetistas do mercado, educação formal avançada e pequenos cursos, todos intercalados com projetos individuais e exercícios de liderança técnica.
 Dar oportunidades para os projetistas em crescimento interagirem e criarem estímulos.