Categoria: Assuntos Randômicos (Página 4 de 6)

Noções de Engenharia de Software

Este artigo é o Capítulo I da minha monografia de Especialização em Engenharia de Software. Embora seja um referencial teórico e não acrescente nada de novo, espero que o possa ser útil de alguma forma para o leitor.

Introdução

Neste capítulo são apresentadas definições de termos utilizados neste estudo com o intuito de nivelar o entendimento sobre os mesmos. Embora tais conceitos possam ser considerados de senso comum, é certo que existem visões distintas e conflitantes.

Os próximos tópicos revisarão alguns conceitos da Engenharia de Software pertinentes ao tema.

Definição de aplicativo de software

Aplicativos de software são “programas isolados que resolvem uma necessidade específica do negócio” (Pressman, 2006, p. 4). Exemplos disso são softwares que processam dados comerciais ou técnicos que facilitam as operações e a gestão de um negócio. Além do código-fonte, o software inclui toda a documentação e os dados necessários para que o programa funcione corretamente (Sommerville, 2003, p. 5).

Definição de Engenharia de Software

A Engenharia de Software é a disciplina que trata de todos os aspectos do desenvolvimento de um software, incluindo as atividades de engenharia de requisitos, os modelos de processos e os modelos e técnicas de estimação (Sommerville, 2003, p. 6-7).

Um aplicativo de software é desenvolvido através de um processo. Não é algo que se fabrica a partir de matéria prima, nem é montado a partir de partes menores. Segundo Pressman (2006, p. 4), o software apresenta esta característica especial em relação a outros tipos de produtos, ou seja: ele não é fabricado no sentido clássico, mas desenvolvido, passando por um processo de engenharia.

A Engenharia de Software fornece abordagens sólidas para aumentar as chances de que os objetivos de negócio sejam atingidos em termos de prazo, qualidade e funcionalidades. Segundo Campos (2009, p. 2), as organizações enfrentam hoje o desafio de desempenhar suas atividades de forma produtiva, com qualidade e cumprindo o planejamento estratégico. Portanto, o uso de uma abordagem adequada no desenvolvimento de um software para elicitação de requisitos, estimação, desenvolvimento e controle é fundamental para as organizações.

Definição de projeto de software

Um projeto consiste num empreendimento temporário, cujo objetivo é a criação de um produto ou prestação de um serviço. Um projeto de software consiste no desenvolvimento de um software, incluindo os artefatos relacionados.

Independente do modelo de processo adotado, o empreendimento de construção de um software envolve atividades de diversas áreas de conhecimento utilizadas em maior ou menor grau durante as fases do projeto, nos âmbitos de gerenciamento e desenvolvimento.

Figura 1 - Visão geral de um modelo iterativo (Rational, 2001)

Figura 1 – Visão geral de um modelo iterativo (Rational, 2001)

O Processo Unificado (Rational, 2001), por exemplo, classifica as atividades da Engenharia de Software em nove disciplinas, dentre as quais cinco são relacionadas diretamente ao produto de software e três ao controle e gerenciamento, isto é, atividades de suporte ao desenvolvimento (Figura 1). No decorrer do projeto, as disciplinas demonstram um maior ou menor grau de atividade e nota-se que o gerenciamento do projeto é a única disciplina utilizada com certa regularidade no decorrer do tempo.

As estimativas de software são o fundamento para o planejamento do projeto ao permitirem uma visão geral do esforço necessário para o desenvolvimento e de variáveis que influenciam o projeto positivamente ou negativamente, tais como a produtividade da equipe e a complexidade do domínio.

Noções sobre arquitetura de software

Antes do desenvolvimento de um software é necessário definir sua arquitetura. O projeto da arquitetura é realizado através da decomposição do software em componentes. A arquitetura descreve o papel dos componentes que compõem o software e o relacionamento eles (Sommerville, 2003, p. 182).

A arquitetura do software fornece um framework estrutural básico para o desenvolvimento do software. Os diversos componentes do sistema agrupam elementos similares, tal como objetos com comportamento semelhante, e facilitam a estruturação do software. Dessa forma, é possível determinar com facilidade quais partes do sistema são afetadas pela implementação ou alteração de uma funcionalidade, pois os tipos de componentes e objetos estarão previamente definidos.

A decomposição arquitetural fornece um ponto de partida para técnicas de estimação baseadas em objetos ou elementos do software. A estimação é possível ao contar os elementos de um componente e estimar cada componente dessa forma. A estimativa resultante tende a ter mais qualidade devido ao conhecimento do tipo de elemento do componente em relação a estimativas geradas sem uma arquitetura definida.

Além disso, a arquitetura influencia diretamente na complexidade do software (Pressman, 2006, p. 223). Quanto mais dependências compartilhadas de recursos, tal como banco de dados ou arquivos, e dependências entre os componentes que compõem o software, maior será a sua complexidade. Um grande número de interdependências faz com que qualquer alteração cause maiores impactos em outros componentes.

Através da arquitetura é possível analisar o impacto de modificações no software. Isso é feito considerando as dependências entre os componentes. Além disso, ao desenvolver uma nova funcionalidade, é mais fácil identificar com antecedência os elementos dos componentes que serão necessários desenvolver, modificar ou reusar.

A análise de impacto de alterações é importante para o ajuste das estimativas dos componentes afetados quando se deseja obter uma posição atualizada do projeto.

Barreiras do desenvolvimento e estimação de software

A atividade de estimação consiste em tentar antecipar o tamanho ou esforço de desenvolvimento de um produto abstrato. Existe uma série de desafios que fazem com que o software seja difícil de estimar e, consequentemente, o projeto difícil de planejar.

Por um lado, isso se deve a características intrínsecas ao software. Brooks (1987) afirma que, por definição, o software é complexo e irredutível, ou seja, não é possível simplificá-lo sem que haja perda de informação. Ele apresenta quatro características importantes que contribuem para isso, tornando o software essencialmente difícil de construir:

  • Complexidade: entidades de software são extremamente complexas para seu tamanho e não existem duas partes iguais no nível de algoritmo. Isso faz parte da natureza do software, não ocorrendo por acaso. A escalabilidade de um software, por exemplo, não consiste simplesmente em fazer suas partes maiores ou menores, diferentemente de construções físicas realizadas pelo homem.
  • Conformidade: apesar de existirem áreas onde pessoas lidam com alta complexidade, o software possui algumas complicações adicionais. Por exemplo, um físico lida com a complexidade do átomo, mas este possui uma interface “bem definida” pela natureza, ou seja, os átomos possuem uma conformidade. O software tem diversas interfaces diferentes definidas por diversas pessoas e geralmente há pouca ou nenhuma conformidade nestas definições.
  • Mutabilidade: o software parece estar sempre pressionado a mudar. Produtos fabricados como pontes, carros e máquinas também estão, mas geralmente eles são substituídos por novos modelos, não sendo modificados com a mesma facilidade de um software. Eles são produtos tangíveis, com escopo bem definido e o custo de modificações maiores é impeditivo. Um software, por ser abstrato e com baixos custos diretos para se mexer, muitas vezes sem aparentes consequências imediatas, sofre constantes mudanças. Além disso, a maioria das mudanças tem como objetivo fazer com que o software vá além de seus limites iniciais.
  • Invisibilidade ou intangibilidade: apesar de existirem interfaces e linguagens amigáveis, o software não pode ser visualizado através de imagens. Em geral, desenvolvedor e usuário não conseguem enxergar o software como um todo. Isso ocorre porque os usuários geralmente não compreendem as questões tecnológicas e os desenvolvedores não entendem todas as regras do negócio, além do que um software pode ser desenvolvido por diversos indivíduos especialistas em diferentes áreas. Portanto, como cliente e desenvolvedor não conseguem ter em mente o software por completo, as chances de erros aumentam.

A estimação é realizada com base numa abstração em forma de requisitos. Por definição, ela não pode ser exata ao considerar as características apresentadas acima, pois a estimativa é, na verdade, baseada numa ideia pré-concebida do software.

Mesmo medidas do produto final de software são subjetivas. Não existem métricas objetivas de tamanho, qualidade, eficiência, robustez, usabilidade e tantos outros aspectos. Mesmo o número de linhas de código ou caracteres do código fonte não possuem uma relação direta com as funcionalidades e características do software, embora muito esforço tenha sido empreendido em conseguir uma aproximação razoável. Isso implica em que qualquer comparação entre softwares, processos de desenvolvimento, técnicas estimação e de engenharia de requisitos somente podem ser realizadas através de critérios específicos e objetivos. Portanto, não é possível afirmar categoricamente que uma técnica, método ou modelo seja superior a qualquer outro.

Por outro lado, o fator humano acrescenta ainda mais dificuldades no desenvolvimento e na estimação de um software. Enquanto desenvolvedores e arquitetos pensam em nível técnico e funcional, os gerentes observam o cronograma e os custos. Não é tarefa fácil conciliar ambas as perspectivas, sendo um desafio alinhar os objetivos estratégicos de uma organização com os aspectos técnicos e abstratos de um produto de software.

Do ponto de vista de quem está gerenciando o projeto, prazo e cronograma são fundamentais para que decisões sejam tomadas e estratégias de negócio estabelecidas, portanto boas estimativas são necessárias. Isso pode ser um problema do ponto de vista da equipe de desenvolvimento, por exemplo, quando prazos arbitrários são definidos ou tempo e recursos disponíveis são tecnicamente insuficientes.

Portanto, tanto a natureza do software quanto os fatores humanos devem ser considerados na estimação. Da mesma forma, quando há alterações no contexto do projeto de software, tal como mudança nos requisitos, no domínio ou na equipe, as estimativas são afetadas e devem ser ajustadas.

Conclusão

Este capítulo definiu o tipo e a natureza da entidade de software, além de descrever brevemente alguns aspectos relacionados ao processo de desenvolvimento.

Sistemas de software são entidades complexas e difíceis de desenvolver. Por isso, a Engenharia de Software busca mitigar os riscos e diminuir as falhas em projetos de desenvolvimento software através de modelos de processos, técnicas e boas práticas.

Entretanto, a correta estimação de um software inclui compreender o que exatamente deve ser construído e como isso será realizado. Assim, os próximos capítulos apresentam abordagens da Engenharia de Software para a engenharia de requisitos e para o desenvolvimento e gerenciamento do projeto.

Curso de Orientação a Objetos com Java na Uniesp de São Roque

DSCN3428

Na terceira semana de Julho de 2014, ministrei um curso de férias sobre Orientação a Objetos com Java na Uniesp de São Roque.

Foi um tempo muito gostoso de interação com os alunos. Todos gostaríamos que tivesse durado mais. De qualquer forma, aproveitamos ao máximo e foi possível lançar as bases da linguagem Java, Lógica de Programação e Orientação a Objetos.

Agradecimentos

Antes de mais nada, parabenizo aos alunos que compareceram em plenas férias para aprimorar seus conhecimentos em programação, em especial ao professor Daniel Vieira, que organizou o evento e teve a disposição que vai além de suas obrigações como docente.

Agradeço ainda à GFT, onde trabalho, por ter apoiado essa iniciativa e, inclusive, disponibilizado alguns brindes para os alunos. Esses brindes foram entregues aos alunos que conseguiram fazer o maior número de exercícios, que eu havia preparado propositalmente em grande número, com grau crescente de dificuldade, para testar até onde eles conseguiriam chegar.

O professor Daniel entregou também um certificado de agradecimento oficial para mim e para a GFT:

DSCN3427

Material do Curso

Preparei um material introdutório, mas completo, consistindo em apostilas e uma apresentação de slides conceituais. Dessa forma, espero que os alunos possam consultar posteriormente e caminhar com as próprias pernas.

Também deixei algumas referências de estudo, tais como livros, sites e apostilas.

Tudo isso você também pode conferir na pasta Java e Orientação a Objetos, compartilhada no meu Google Drive.

Estatísticas

Além disso, também preparei um questionário de avaliação para os alunos, dos quais a maioria respondeu. Veja abaixo o resultado de duas das principais questões (além da minha inabilidade em criar as opções mais adequadas).

O quanto você considera ter aprendido?

aprendizado

Infelizmente na pergunta acima faltou um nível intermediário entre Muito e Pouco, o que talvez tenha distorcido o gráfico.

No geral, o que você achou do curso?

interesse

Reflexões

Ensinar pode ser uma grande experiência. Para mim certamente é.

Alguns dizem que você aprende muito mais ensinando. Concordo plenamente.

Se você é um aluno ou está começando na carreira, procure absorver ao máximo as experiências e conhecimento de quem já é mais maduro, sem esquecer de reconhecer o esforço daqueles que dispõem de tempo para lhe dar algum tipo de instrução.

Se, por outro lado, você é um profissional experiente, olhe para os que estão começando e procure incentivá-los e instruí-los. Isso é recompensador de várias formas. Compartilhar conhecimento nunca faz você perder nada. No mínimo, ajudará a que você reforce e organize seus conceitos.

Análise e Design Orientados a Objetos – uma visão prática

UML_logo Estudantes e praticantes de Engenharia de Software geralmente tem dúvidas sobre como modelar um sistema de forma efetiva.

Atualmente, a abordagem mais recomendada é a Orientada a Objetos, preferencialmente usando UML, já que essas técnicas são amplamente reconhecidas e propagadas.

O problema é que muita confusão ocorre por falta de conhecimento sobre os objetivos da modelagem de sistemas, as fases de um projeto de desenvolvimento de software e os diferentes níveis de modelagem possíveis. Por exemplo: quais classes devem ser incluídas num Diagrama de Classes? Devemos colocar as classes do framework? Como identificar as classes necessárias em um sistema?

O ponto de partida é não misturar a Análise do problema com o Design (projeto) da solução ou com a Implementação tecnológica.

Em resumo, vamos analisar nos tópicos abaixo como aplicar a modelagem orientada a objetos em diferentes fases de um projeto, tendo em mente alcançar objetivos concretos com a modelagem, além das diferentes aplicações dos modelos utilizados.

Análise

O analista é o profissional responsável por identificar um problema a ser resolvido ou necessidade a ser atendida e elicitar os requisitos para a criação de uma solução.

O conjunto de requisitos define o que o sistema deve fazer para atender às necessidades identificadas.

Baseando-se nos requisitos, o analista continua o processo de Análise identificando em alto nível de quais funcionalidades o sistema deverá possuir para atender aos requisitos.

Uma solução comum para mapear cada funcionalidade é através de Casos de Uso (não confundir com o Diagrama da UML). Um Caso de Uso é uma espécie de passo-a-passo da interação entre usuário e sistema, embora esse conceito possa variar um pouco. Além disso, geralmente ele descreve as pré-condições necessárias para a correta execução e as pós-condições, que são os resultados da ação realizada.

Note que ainda estamos em alto nível e nada aqui tem a ver com a solução tecnológica.

Continuando, o analista treinado em Orientação a Objetos e UML irá modelar o conhecimento sobre o domínio e o problema utilizando os diagramas adequados, que geralmente são: Diagrama de Caso de Uso, Diagrama de Atividades, Diagrama de Classes e Diagrama de Estados.

O Diagrama de Caso de Uso é uma representação visual simples das interações do sistema com o mundo externo. Os atores que interagem com o sistema são representações de usuários, outros sistemas ou qualquer entidade externa ao sistema que se comunique com o mesmo. Este diagrama não exclui a necessidade de mapear em detalhes os casos de uso conforme mencionado anteriormente.

O Diagrama de Atividades representa os passos do caso de uso numa espécie de fluxograma, incluindo bifurcações de cenários alternativos, cenários de erro, etc. Nem todos os cenários precisam ser representados no mesmo diagrama.

O Diagrama de Classes, neste estágio de um projeto, deve incluir apenas as classes de domínio, sem qualquer referência à tecnologia que será usada na implementação. Poderíamos chamar este diagrama de Diagrama de Classes de Domínio. A função do diagrama é representar as entidades necessárias e o relacionamento entre elas. Em suma, é a forma moderna e orientada a objetos do Diagrama de Entidade-Relacionamento (DER), embora ambos possam ser usados concomitantemente. No entanto, o DER geralmente é associado à modelagem estruturada.

O Diagrama de Estados é usado para as entidades do sistema que seguem um fluxo de estados. Por exemplo, uma parcela pode estar “em aberto”, “liquidada”, “em atraso”, “em prejuízo”. Este diagrama representa os estados e como ocorrem as transições entre eles.

Com tudo isso, o analista pode validar a solução verificando se as classes e os casos de uso atendem aos requisitos iniciais. Por exemplo, se houver um requisito de que “o gerente poderá extrair um relatório com o total de produtos vendidos no mês”, o analista deve olhar se a classe Produto possui um relacionamento “navegável” com Venda e ItemVenda e se é possível extrair a informação de totalização das vendas de forma lógica. Ele também pode adicionar os métodos e atributos importantes às classes de modo a atender aos requisitos.

Design (Projeto)

Com base em todas essas informações, entram em ação os arquitetos e desenvolvedores para propor uma solução tecnológica para o problema. Isso não necessariamente vem em sequência, muito pode ocorrer em paralelo.

Os projetistas técnicos poderão criar vários outros diagramas para representar o que será implementado. O Design pode ser feito de forma agnóstica, isto é, sem considerar quais tecnologias, frameworks e bibliotecas serão usadas. No entanto, creio que é mais produtivo modelar a divisão de componentes e classes já pensando um pouco na implementação, de forma a não gerar mais um gap de informação.

Os diagramas mais relevantes e geralmente usados são:

  • Diagrama de Componentes: representa a divisão em alto nível dos componentes principais do sistema. A divisão não representa a estrutura de pastas dos arquivos do projetos, mas é uma divisão lógica de responsabilidades.
  • Diagrama de Deployment: uma representação do ambiente onde o sistema será executado, incluindo servidores, bancos de dados, replicação, proxies, etc.
  • Diagrama de Sequência: para uma determinada ação no sistema, este diagrama representa a interação entre diversos objetos através das chamadas executadas e do retorno, permitindo visualizar a sequência de chamadas no decorrer tempo.

Note que cada um dos diagramas citados podem ser produzidos para vários casos diferentes. Quando falamos em Diagrama de Classes ou Diagrama de Componentes, não há necessariamente um único diagrama que representa o sistema como um todo. A representação pode ser feita em níveis diferentes, por exemplo, um mostrando os componentes gerais do sistema e outros diagramas mostrando a estrutura interna de cada componente individualmente. A representação também pode ser feita em contextos diferentes, por exemplo, vários diagramas para representar apenas o necessário para uma funcionalidade importante, ignorando classes e pacotes não relevantes naquele contexto.

Implementação

A implementação deve seguir o que foi definido no Design, porém não significa que cada método, classe, pacote e componente deve ser mapeado um-para-um no projeto “físico”, em seus arquivos e estrutura de diretórios.

O programador deve ter a liberdade de encontrar a melhor solução para atender ao que foi solicitado com a estrutura que ele desejar. Seria péssimo, do ponto de vista de boas práticas, impor cada detalhe do que deve ser implementado. Se isso fosse possível, não precisaríamos de programadores, mas de um gerador de código.

Construindo a ponte

Ao estudar com atenção as “fases” (entre aspas porque não são uma sequência linear) de um projeto de desenvolvimento de software, é possível notar que existe um grande salto (gap) entre cada uma delas. Uma analogia comumente usada nos livros de Engenharia de Software consiste em construir uma ponte entre o que o cliente precisa e a solução tecnológica.

Ainda hoje, a Engenharia de Software é uma disciplina um tanto imatura. Não temos uma forma padronizada de construção como a Civil ou Elétrica para nos apoiar. A validade de um Modelo de Análise, um Modelo de Design ou da solução implementada depende quase exclusivamente de fatores humanos, como a capacidade de comunicação e entendimento dos analistas, além da capacidade técnica dos desenvolvedores.

Não existem regras definitivas sobre como e em qual nível modelar, assim como não há regras sobre como traduzir uma necessidade em um requisito, um requisito em um modelo e um modelo numa implementação.

A UML foi um grande avanço, mas os diversos diagramas sempre variam em nível de detalhe, abrangência e muitos outros fatores de projeto para projeto, de equipe para equipe e de indivíduo para indivíduo.

Princípios de modelagem

Mesmo com as últimas afirmações acima, não quero ser pessimista. Embora não haja uma resposta definitiva para a modelagem de sistemas, existem alguns princípios que podem nos guiar:

  • Coloque a comunicação em primeiro lugar. O objetivo de um diagrama é comunicar informação e não simplesmente ser um espelho do código. Se um diagrama não comunica algo útil, não perca tempo com ele. Considere sua equipe e o seu projeto e faça os diagramas que forem relevantes com os detalhes relevantes para que as pessoas saibam o que estão fazendo. O seu time consegue se reunir numa mesa e discutir um diagrama, rabiscando-o e usando-o como base para a conversa?
  • Não faça diagramas de tecnologias específicas. Se alguém quiser saber como Servlets, Rails ou Django funcionam, é melhor comprar um livro. Você só vai confundir as pessoas. Já vi muitos diagramas por aí que nada mais são do que o modelo MVC com nomes diferentes.
  • Verifique se o diagrama atende os requisitos. O seu diagrama deve ser útil não só para entender o que deve ser feito, mas também para validar se a sua solução atende ao que o cliente precisa. Faça testes mentais lógicos, olhando para as classes, métodos e relacionamentos, verificando se elas tem motivo de estarem ali, se para um certo cenários você consegue extrair os dados necessários, etc.

Como mencionei algumas vezes, a Análise, o Design e a implementação provavelmente serão feitas muitas vezes durante o ciclo de desenvolvimento. Não espere ter tudo certo no começo. Investir tempo demais em detalhamento é ruim, vários autores já alertaram.

Identificando as classes necessárias

Existe um método para identificação de classes de domínio em potencial que consiste na análise de um texto à procura de substantivos. Esta técnica pode ser útil quando você não tem ideia do que está fazendo e quer algumas ideias iniciais. No entanto, é péssima porque em muitos lugares é ensinada como uma forma “burra” de extrair informação.

Volta e meia ouço alguém com a velha ideia de criar um interpretador mágico, um tipo de Inteligência Artificial, capaz de gerar um sistema com base num texto descrevendo as necessidades do usuário. Devaneios à parte, é melhor nos concentrarmos no que é real hoje.

Aliás, é comum usarmos a palavra “extrair” ou “levantar” indevidamente. Quando criamos um sistema, não extraímos ou levantamos os requisitos e as classes necessárias como se elas já existissem ali, ocultas de alguma forma.

Quanto aos requisitos, o termo “elicitar” é mais adequado, com o sentido de descobrir e descrever. Quanto às classes, nós simplesmente decidimos de forma espúria quais delas o sistema deverá conter de modo a atender às necessidades. Se verificarmos que elas não atendem aos requisitos, nós as modificamos para que o façam.

Tudo isso trata-se de um processo criativo e não de um processo de extração como se faz com matéria prima. Por “criativo”, não pense em arte pós-moderna, mas num processo criativo metódico e até científico.

Minha recomendação é identificar, através dos requisitos, quais dados são necessários para que o sistema funcione, assim como o relacionamento entre elos. Já dizem os DBAs: os dados são o coração do sistema. Assim conseguimos as classes que representam as entidades necessárias.

Depois, com base nas funcionalidades que o sistema deve ter, pode-se definir classes que serão responsáveis por tratar essas funcionalidades.

Considerações finais

A Engenharia de Software ainda possui muitos desafios pela frente para se tornar realmente uma engenharia no sentido completo da palavra.

Muitos hoje (inclusive eu) consideram-se mais artesãos tecnológicos do que engenheiros propriamente ditos. Isso é bom em certo sentido, mas também abre muitas brechas para as “artes abstratas”. 😉

Enfim, não há uma resposta definitiva para o sucesso na modelagem de um sistema, mas espero ter exemplificado bem um caminho que aprendi ao longo de alguns anos estudando e refletindo sobre como desenvolver software adequadamente, não apenas do ponto de vista técnico.


Este artigo foi baseado na minha resposta no StackOverflow em Português!

Vagas na GFT em Sorocaba e Alphaville

Não trabalho mais na GFT, porém o artigo permanece no site para referência.

Até onde eu sei, a GFT está contratando continuamente, então ainda dá para aproveitar as dicas aqui e aplicar para uma vaga lá, mas por favor não envie currículos para mim.

A GFT, empresa em que trabalho, está buscando profissionais para trabalhar com experiência Java e .NET, além de Gerentes de Projetos. A maioria das vagas é para a cidade de Sorocaba, mas há algumas de Java para Alphaville.

Por quê Sorocaba?

Para quem não sabe, eu decidi morar no interior. Em meu artigo "Morando no interior, trabalhando na capital" explico a dura rotina de morar no interior e trabalhar em São Paulo, além de listar algumas motivações que me levaram a fugir do caos urbano.

Você também não aguenta mais ficar de 2 a 5 horas no trânsito por dia? Não adianta esperar a construção de mais linhas de trens e metrô, novas linhas de ônibus e duplicação de avenidas. São Paulo não tem solução. Venha para o interior!

Além disso, Sorocaba possui várias universidades e faculdades, como UFSCAR, UNESP e FATEC. Ela fica a menos de 100 quilômetros São Paulo, sendo possível, por exemplo, fazer cursos na capital. Obviamente, não podemos comparar o mercado de trabalho com capitais e cidades metropolitanas maiores, mas Sorocaba é bem desenvolvida para uma cidade do seu porte.

Por quê a GFT?

Todos sabemos que existem poucas empresas boas para trabalhar fora das capitais brasileiras. A maioria não paga bem nem possui um plano de carreira decente. Em outras palavras: não há perspectiva de crescimento. Infelizmente, o que mais vejo é pessoas boas se formando no interior e migrando para inchar ainda mais a capital. Até quando São Paulo vai suportar isso?

Não querendo fazer propaganda, mas já fazendo, algumas empresas como a GFT nadaram um pouco contra a maré e decidiram investir no potencial dos profissionais do interior, ainda que mantendo uma unidade comercial mais próxima de São Paulo. Em minha opinião, empresas que investem nesse modelo merecem um reconhecimento.

A GFT é uma multinacional alemã e possui filiais espalhadas pela Europa e Estados Unidos. Aqui há vários projetos internacionais, então se você tiver com o Inglês afiado poderá não só praticar como também “precisar” viajar em situações específicas. Se o seu Inglês não é tão bom, junto com todos os benefícios há um curso de Inglês gratuito ministrado por uma das melhores escolas do país. Como toda boa empresa, o horário de trabalho é flexível e o ambiente não é estressante. Todas as quartas-feiras temos frutas grátis para incentivar a boa saúde.

Notas Pessoais

Posso dizer que não me arrependo da decisão de ter deixado a capital para morar em Sorocaba. Também estou gostando de trabalhar na GFT.

Há alguns dias conversei com um colega que também entrou na GFT há poucos meses. Ele veio da CI&T de Campinas. Ele disse que, comparando com o projeto em que ele estava alocado, a GFT está sendo um tanto melhor. Não quero com isso comparar as empresas, apenas dar uma noção de que a GFT não perde em nada para empresas conhecidas por serem boas para se trabalhar e que investem pesado em propaganda para divulgar isso.

Para mim tem sido um bom lugar para trabalhar. A única ressalva é que não é um ambiente ágil como uma startup ou uma empresa pequena. Como a maioria dos projetos é para o setor financeiro, há a rotineira burocracia. Nada que não seja comum quando trabalhamos com grandes instituições.

Finalmente, as vagas

Você pode conferir a lista de vagas neste link. Porém, não vá logo enviando seu currículo pelo site!

Antes de mais nada, veja o resumo das vagas*:

  • Desenvolvedor Java Pleno e Sênior: o candidato deve ter experiência com Java. Conhecer Spring bem e JPA são diferenciais.
  • Arquiteto Java: profissional com bastante experiência e Inglês fluente.
  • Engenheiro .NET: o candidato deve ter experiência na plataforma .NET e Inglês avançado.
  • Gerente de Projetos: profissional experiente para atuar em múltiplos projetos internacionais, incluindo reuniões com pessoas de vários países, com Inglês fluente.

* Leia com atenção a descrição das vagas clicando nos respectivos links pois este é um resumo que fiz por minha conta e risco.

Note que o Inglês é um grande diferencial em todas as vagas. Quanto maior o seu nível, melhor para você.

Além disso, há vagas para diferentes níveis de conhecimento. Se a empresa considerar que o seu perfil é adequado, ela vai encaixar você numa posição equivalente a sua experiência.

Quer uma ajudinha?

Todos sabem que enviar e-mail para o RH não surte tanto efeito quando uma indicação, certo?

Pois bem, se você quer entrar na GFT, estando em Sorocaba ou querendo vir para cá, entre em contato comigo pelo formulário do site ou pelo LinkedIn.

Eu mesmo irei analisar seu perfil e, se estiver dentro do que a empresa precisa, farei uma indicação ao RH e darei pessoalmente um retorno sobre o processo.

Caso tenha alguma dúvida, pode entrar em contato comigo pelos mesmos meios citados acima.

Pelo meu direito de não mais usar o Mozilla Firefox

Atenção: este artigo contém teor político e opinativo. Se você tem estômago fraco, pare de ler agora.


Quinta-feira, 3 de abril de 2014.

A Fundação Mozilla anuncia a renúncia de seu CEO, Brendan Eich, menos de suas semanas após assumir o cargo.

Brendan é, nada mais, nada menos que o criador da linguagem Javascript e cofundador do projeto Mozilla e da Fundação Mozilla. Mas, apesar de seu impressionante histórico profissional e várias contribuições para a comunidade de código livre, isso não foi suficiente para suportar sua permanência como CEO.

Como noticiou o Fox News, houve uma grande pressão por parte de alguns funcionários e muitos comentários no Twitter porque em 2008 Brendan doou mil dólares em apoio à Proposition 8, uma proposta de emenda constitucional para defender que “apenas casamentos entre um homem e uma mulher sejam válidos ou reconhecidos na Califórnia”.

Não, você não leu errado. Vou traduzir: alguns ativistas gays não suportaram a ideia de trabalhar para alguém que pensa diferente e há 6 anos fez com seu dinheiro pessoal algo eles não aprovam.

E a coisa fica pior. Artigos desastrados, tentando justificar o ocorrido, chegam a afirmar que “a posição de Eich era inaceitável no Vale do Silício, uma região do mundo dos negócios onde o liberalismo social está próximo de uma ideologia universal”.

Traduzo novamente: você é inaceitável para exercer um cargo a menos que seja um liberal e, consequentemente, defenda o casamento entre pessoas do mesmo sexo.

Comentários pessoais

Escrevi este artigo porque este é apenas um dos muitos casos de intolerância invertida ocorrendo no mundo. As supostas vítimas perseguem e atacam o “intolerante”. Então se uma empresa tem a maioria de funcionários conservadores ela tem o direito de demitir o chefe porque ele é um liberal?

Eu sou cristão, politicamente de direita, contra o socialismo e o assistencialismo, defensor do ensino em casa (homeschooling), defensor da criação de filhos com palmadas e por aí vai. Nem por isso julgo a capacidade profissional, o intelecto ou o caráter de meus colegas por suas convicções pessoais que certamente são diferentes e diversificadas, nem uso de estratagemas para prejudicá-los.

Pessoalmente, não tenho problemas em conviver como líder ou liderado por pessoas que pensam diferente. Faz parte da maturidade e da ética de cada ser humano ter cuidado em como declarar suas convicções pessoais para não ofender aos demais. O problema é que, no mundo em que vivemos, a recíproca parece não ser verdadeira. Logo serei eu a renunciar porque alguém vai vasculhar a internet atrás de algum comentário “polêmico” que eu tenha feito para me desmoralizar.

Vivemos num mundo de cabeça para baixo, onde filhos tentam dominar os pais, alunos aos professores, bandidos aos policiais e as minorias às maiorias. A sociedade está indo para um caminho perigoso. Qualquer que se sente vítima, ofendido por qualquer motivo, acha-se no direito de atacar quem quer que seja. Gente grande que é bom tá em falta.

Eu sou a favor da liberdade, mas não apenas dos liberais. Sou a favor de que sejamos livres, você e eu também.

Exercendo o meu direito de não mais usar o Mozilla Firefox

Por enquanto, posso apenas exercer meu direito e não usar mais o Firefox, além de democraticamente emitir a minha opinião e convidar você a fazer o mesmo.

Não apoie uma instituição que discrimina seus funcionários por suas convicções pessoais.

Não participe de grupos e discursos do fascismo liberal, que não suporta nada a não ser suas próprias ideias.

Desinstalando FF em 3… 2… 1…

Novidades sobre o blog!

Marco: 10.000 acessos

O blog tem crescido.

Nos primeiros meses, contava com uma média de 10 visitas diárias. Esses números vêm crescendo continuamente e hoje vai além de uma centena, com picos de 160 visualizações. Há alguns dias, o site atingiu o marco de dez mil visualizações somadas desde junho do ano passado.

views-blog

Novos horizontes: postagens em Inglês

O objetivo deste blog é contribuir para a comunidade de TI de forma geral. E nada melhor para atingir mais pessoas do que usar o idioma “oficial” da TI no mundo, o Inglês.

É com grande felicidade que inauguro hoje a versão “internacionalizada” do blog! Note a bandeirinha à direita.

Calma, as postagens em Português vão continuar e serão prioridade. Entretanto, vou iniciar a tradução dos artigos já publicados. Os visitantes de fala portuguesa não perderão nenhum conteúdo se ignorarem o outro idioma.

Atualmente, apenas uma pequena parcela dos acessos tem vindo de outros países. Pretendo aumentar a visibilidade internacional da comunidade de desenvolvedores brasileiros através deste blog, ainda que com uma contribuição bem pequena.

Mas, preciso confessar, tenho um objetivo duplo nisso tudo: praticar meu Inglês. Já escrevi alguns artigos sobre como melhorar o entendimento através da leitura. Bem, chegou a hora de fazer o mesmo com a escrita.

Justamente por isso coloquei um aviso grande e amarelo de que meu Inglês está em versão beta. Isso mesmo. Não é só o Google e outras empresas que adotam práticas ágeis que podem colocar “produtos” ainda inacabados em “produção”. 😉

O que você deve esperar

Tornar meu trabalho e minhas reflexões públicas não é sempre fácil. Como já escrevi, sou suscetível à crítica. Algo que é publicado na Internet é como uma marca de tatuagem: uma vez feita, você nunca mais consegue apagá-la totalmente. Então vou fazer o possível para manter o nível de qualidade das postagens elevado.

Além disso, os temas continuam divididos basicamente em quatro grandes categorias.

A primeira contém dicas técnicas de programação. Elas podem ser simples ou complicadas, mas tentarei publicar apenas as mais úteis, que podem salvar a vida de algum pobre programador num momento de desespero. A maioria vêm de contextos como este ocorridos em sites como StackOverflow e GUJ, dos quais tenho participado.

A segunda contém reflexões sobre Engenharia de Software. Por exemplo, quando escrevo sobre problemas no desenvolvimento de software ou dificuldades da estimação.

A terceira envolve Arquitetura de Software. Pretendo escrever uma série de artigos introdutórios teóricos e práticos sobre diversas tecnologias para servir uma referência para quem está aprendendo. Além disso, artigos sobre como definir e aplicar tecnologias específicas também podem entrar nesta categoria.

Por último, continuarei a publicar reflexões sobre carreira e desenvolvimento profissional. Considero isso essencial. Pretendo ministrá-los continuamente para tirar alguns profissionais da letargia, onde eu já estive um dia.

Creio que os quatro temas principais acima são importantes para todo bom Engenheiro de Software e produzem um resultado muito bom se ministrados na proporção certa.

Obrigado, caro leitor

Escrever para ninguém ler pode até ser útil como desabafo. Mas é muito bom saber que você tem acessado, lido, compartilhado e comentado.

Então, obrigado!

E continue curtindo, compartilhando e comentando sobre o conteúdo deste blog.

Ele é feito para você.

Desafio de Programação: algoritmo LCA (Lower Common Ancestor)

Há algum tempo me deparei com uma code question muito interessante sobre árvores. Não, isso não tem a ver com o problema do desmatamento ou com agricultura. Code questions são desafios de programação que frequentemente envolvem conceitos de Estrutura de Dados, Álgebra, Geometria Analítica, Teoria dos Grafos e uma boa capacidade de analisar e resolver problemas. Pena que muitas vezes isso me falta! :b

Code questions podem parecer algo fora da realidade prática, mas na verdade ajudam muito com o raciocínio e, principalmente, no uso correto de estruturas de dados. Diariamente usamos listas, conjuntos, árvores, pilhas e mapas, muitas vezes de forma equivocada. Por falta de treino, frequentemente temos dificuldades de criar lógicas simples e eficientes.

O problema

A referida questão envolvia o conceito de LCA (Lower Common Ancestor – Ancestral comum mais próximo), derivado da Teoria dos Grafos. LCA é o “pai” (ancestral) mais próximo de dois nós de uma árvore.

Se você não entendeu, pense na hierarquia de uma empresa. Temos o presidente como o topo ou raiz da árvore, os gerentes logo abaixo dele e em seguida subgerentes ou subordinados. Se houver um problema entre dois funcionários quaisquer, precisamos descobrir quem pode responder por ambos. Não podemos ir direto ao presidente, nem “pular” a hierarquia, mas devemos ir ao gerente mais baixo da hierarquia que seja chefe diretamente ou indiretamente de ambos.

Bem, na verdade existe um caso onde o LCA e o exemplo da empresa não são equivalentes. Se os dois nós da árvore que estou procurando forem pai e filho, por exemplo, a resposta para o algoritmo seria o próprio pai. Porém, se numa empresa ocorre um problema entre gerente e subordinado, não deveria ser o gerente o responsável por tratar do assunto. Hum… pensando bem, isso se aplica a muitas empresas, sim.

Para entender visualmente uma árvore, considere a imagem abaixo:

grafo-exemplo

Encontrar o ancestral mais comum é simples se cada nó da árvore tiver a informação de quem é seu ancestral. Basta localizar o par de nós e compara os seus pais. Porém, sem essa informação, temos que percorrer a árvore toda armazenando a informação de parentesco antes de fazer essa comparação. Obviamente a code question exigia o caso mais difícil.

Para piorar, a maioria das implementações disponíveis são para Árvores Binárias, isto é, a quantidade de descendentes de cada nó é de no máximo em dois. Isso facilita a solução, mas minha questão era mais complicada, pois cada nó poderia ter vários filhos.

Veja a seguir os atributos de um nó da árvore:

class Node {
    Integer id;
    List<Node> filhos;
}

Para resolver essa code question, precisamos de um algoritmo para encontrar o LCA, tendo como entrada dois nós com a estrutura do código acima.

Solução recursiva

Primeiramente fiz uma implementação recursiva baseada num algoritmo publicado numa resposta do StackOverflow.

O método ficou assim:

Node findClosestCommonAncestor(Node root, Node p, Node q) {
    if (root == null) {
        return null;
    }
    if(root == p || root == q) {
        return root;
    } else {
        Node aux = null;
        //if two children contains p and q, root is the ancestor
        for (Node current : root.children) {
            Node child = findClosestCommonAncestor(current, p, q);
            if (child != null) {
                if (aux == null) {
                    aux = child;
                } else {
                    return root;
                }
            }
        }
        if (aux != null) return aux;
    }
    return null;
}

É possível editar e executar esse código no site ideone.

O algoritmo acima basicamente analisa recursivamente cada nó e verifica se dois de seus filhos são o par de nós p e q. Quando um nó puder responder por ambos, temos a resposta.

Solução iterativa

Soluções recursivas são boas, e em alguns casos podem ser as mais eficientes. Porém, é sempre um desafio pensar numa solução iterativa, isto é, usando apenas laços e estruturas como pilhas.

A Wikipedia contém uma página sobre Tree Traversal que inclui pseudo-códigos para iterar sobre árvore binárias usando pilhas. Existem várias formas de iterar sobre uma árvore. Por exemplo, o código abaixo é do Pre-order Traversal, que significa percorrer os nós começando do nó mais à esquerda até acabar no mais da direita:

iterativePreorder(node)
  parentStack = empty stack
  while (not parentStack.isEmpty() or node ≠ null)
    if (node ≠ null)
      visit(node) #1
      parentStack.push(node) #2
      node = node.left
    else
      node = parentStack.pop() #3
      node = node.right

Anotei três pontos (#1, #2, #3), os quais irei comentar mais adiante.

Entretanto, para a resolução do nosso problema não estamos limitados a dois descendentes por nó, então são necessários alguns ajustes:

  1. Uma pilha auxiliar para armazenar o filho atual sendo visitado a cada nível que o algoritmo desce na árvore. Diferente de uma árvore binária, onde basta percorrer o nó da esquerda e depois o da direita, para percorrer um nó com n filhos precisamos de um contador. E como cada filho pode ter m filhos, então deve haver um contador para cada nível da árvore.
  2. Uma segunda pilha auxiliar para armazenar o caminho até o primeiro nó encontrado. Como um dos objetivos do algoritmo é encontrar dois nós, devemos armazenar o caminho até o primeiro e continuar até encontrar o segundo.

Um pseudo-código para encontrar o ancestral mais próximo dos nós p e q, dada a raiz node, ficou assim:

findClosestCommonAncestor(node, p, q)
  parentStack = empty stack
  childIndexStack = empty stack
  firstNodePath = null
  while (not parentStack.isEmpty() or node ≠ null)
    if (node ≠ null)

      #1
      if (node == p || node == q)
        if (firstNodePath ≠ null)
          parentStack.add(node)
          int n = min(parentStack.length, firstNodePath.length)
          for i = (n - 1)..0
            if (parentStack(i) == firstNodePath(i))
              return parentStack(i)
          return null
        else
          firstNodePath = copy parentStack
          firstNodePath.push(node)

      #2
      if (not empty node.children)
        parentStack.push(node)
        childIndexStack.push(0)
        node = node.children(0)
      else
        node = null

    else

      #3
      node = parentStack.peek()
      i = childIndexStack.pop() + 1
      if (i >= node.children.length)
        node = null
        parentStack.pop()
      else
        node = node.children(i)
        childIndexStack.push(i)

Certamente ficou mais complexo, mas o conceito é basicamente o mesmo do anterior. Note que também marquei neste algoritmo três pontos, pois são análogos ao anterior. Vejamos:

  • #1 Este é o bloco onde o valor do nó atual é processado. O visit(node) do primeiro algoritmo foi substituído por um bloco que verifica se um dos nós foi encontrado. Caso tenha encontrado o primeiro nó ele salva a pilha atual. Caso tenha encontrado os dois ele compara as pilhas, item a item, procurando pelo pai mais próximo.
  • #2 O algoritmo inicial adiciona o nó atual na pilha e avança para o filho da esquerda. O segundo algoritmo generaliza para n filhos avançando para o primeiro filho (0).
  • #3 O algoritmo inicial desempilha um nó e avança para o filho da direita. O segundo algoritmo generaliza avançando para o próximo filho (anterior + 1).

O código em Java ficou assim:

class Node {
    List<Node> children = new ArrayList<Node>();
    Integer id;
    Node(Integer id) {
        this.id = id;
    }
}
Node findClosestCommonAncestor(Node node, Node p, Node q) {
    Stack<Node> parentStack = new Stack<Node>();
    Stack<Integer> childIndexStack = new Stack<Integer>();
    Stack<Node> firstNodePath = null;
    while (!parentStack.empty() || node != null) {
        if (node != null) {
            if (node == p || node == q) {
                if (firstNodePath != null) {
                    parentStack.add(node);
                    int n = Math.min(parentStack.size(), firstNodePath.size());
                    for (int i = n - 1; i >= 0; i--) {
                        if (parentStack.get(i) == firstNodePath.get(i)) {
                            return parentStack.get(i); 
                        }
                    }
                    return null;
                } else {
                    firstNodePath = new Stack<Node>();
                    firstNodePath.setSize(parentStack.size());
                    Collections.copy(firstNodePath, parentStack);
                    firstNodePath.push(node);
                }
            }
            if (!node.children.isEmpty()) {
                parentStack.push(node);
                childIndexStack.push(0);
                node = node.children.get(0);
            } else {
                node = null;
            }
        } else {
            node = parentStack.peek();
            Integer i = childIndexStack.pop() + 1;
            if (i >= node.children.size()) {
                node = null;
                parentStack.pop();
            } else {
                node = node.children.get(i);
                childIndexStack.push(i);
            }
        }
    }
    return null;
}    

A versão completa do código Java está disponível para edição e teste no ideone.com.

Considerações

Se você é um leitor ocasional, não se preocupe se não entendeu nada dos códigos acima. Isso não é algo que você pega em cinco minutos. Por exemplo, eu passei alguns dias pensando na solução iterativa. É necessário estar “imerso” no problema.

Este artigo pode parecer dissonante em relação ao conteúdo do site, mas aprendi recentemente que estudar algoritmos deve fazer parte do aperfeiçoamento de todo desenvolvedor sério. Para usar tecnologias de ponta e de alto nível isso é essencial. Na verdade, não conhecer a teoria da computação é o principal motivo pelo qual temos dificuldades em entender e aplicar melhores soluções.

Você acha que empresas como Google, Facebook ou Amazon conseguem atender milhões de usuários simultânea e ininterruptamente usando implementações padrão de mercado? Os profissionais que mantêm grandes estruturas funcionando são os que entendem profundamente os fundamentos da computação, não APIs básicas de linguagens de programação.

Por muito tempo eu mesmo menosprezei esse tipo de conhecimento, considerando que não agregaria muito à minha carreira. Eu queria algo de “alto nível” e a teoria parecia perda de tempo. Estava errado!

O mercado de trabalho brasileiro ajuda a criar essa ilusão. Veja as vagas publicadas por aí. Eles pedem lógica, Inglês e uma lista infindável de frameworks. Então você sai freneticamente aprendendo várias coisas que, na verdade, são mais do mesmo.

Então um dia você acorda e percebe que é mais um programador medíocre que só sabe uma lista de frameworks. E pior, descobre que na prática as empresas nem mesmo valorizam esse conhecimento.


Este artigo foi baseado numa questão do StackOverflow em Português!

Criando e mantendo senhas seguras

Muito se discute hoje sobre a segurança da informação. Nós, meros mortais, usuários de um número cada vez maior de serviços online, temos boa parte de nossa segurança à mercê de uma grande variedade de empresas de tecnologia esparramadas mundo afora.

Um problema crescente desde o advento da Internet envolve o gerenciamento de senhas de autenticação para acesso a esses serviços. Do ponto de vista técnico, políticas de segurança devem ser tomadas, tais como impor um tamanho mínimo, exigir caracteres especiais, dígitos numéricos e variação de capitalização (mistura de maiúsculas e minúsculas). Pode-se ainda incluir uma autenticação adicional como um token (chaveiro, cartão, aplicativo para celular) ou dispositivo biométrico. Cada iniciativa para dificultar ações maliciosas tem o potencial de diminuir o número de ocorrências de segurança, mas os riscos nunca serão definitivamente eliminados.

Por outro lado, um peso considerável sobre a segurança recai sobre nós. Mas, como podemos nos proteger? Como criamos senhas seguras? Primeiro, precisamos entender alguns conceitos sobre senhas.

As pessoas usam senhas fáceis e repetidas

Quando a Sony foi invadida em 2011, contas de 77 milhões de usuários foram roubadas, incluindo seus números de cartão de crédito, e-mails, logins e senhas. Pouco tempo depois, mais um milhão de usuários tiveram seus dados capturados e expostos a qualquer interessado através de um arquivo torrent.

Um especialista em segurança analisou os dados disponibilizados e descobriu alguns fatos interessantes:

  • Mais de 50% dos usuários usam senhas com 7 caracteres ou menos
  • 50% das senhas consistem apenas de letras minúsculas e 45% apenas de letras maiúsculas
  • Apenas 4% das senhas continham alguma diversidade (números, maiúsculas e minúsculas, caracteres especiais)
  • 1% das senhas continham caracteres especiais
  • 36% das senhas estavam presentes em dicionários de senhas usados por hackers

Na mesma análise, comparando as senhas obtidas em dois serviços diferentes da Sony, verificou-se que 92% dos usuários usavam a mesma senha em ambos. Comparando com senhas obtidas de outro serviço fora da Sony, o reuso foi de 67%.

Definindo uma senha segura

Qualquer especialista em segurança lhe dirá que não existe nada 100% seguro. Para efeito de estudo, vamos considerar que uma senha segura é aquela onde o esforço para obtê-la é mais custoso do que o benefício advindo de tal ato.

Na área de segurança é comum usarmos o conceito de entropia para explicar a dificuldade de descobrir-se uma senha. Este termo é emprestado da física, mais especificamente da Segunda Lei da Termodinâmica, e define o grau de irreversibilidade ou desordem de um sistema. Quanto maior a entropia de uma senha, mais aleatória e complexa ela é, assim como será maior o esforço de alguém para obtê-la.

Fatores que aumentam a entropia de uma senha

A Universidade de Cambridge realizou um estudo empírico sobre segurança de senhas onde 288 estudantes foram divididos em 3 grupos: o primeiro poderia escolher uma senha qualquer, o segundo usaria um método para gerar uma senha aleatória e o terceiro usaria uma senha cujas letras eram as iniciais de uma frase qualquer.

O ataque mais efetivo foi o uso de um dicionário de palavras comuns, que conseguiu descobrir 30% das senhas escolhidas pelos alunos (primeiro grupo), 8% das senhas aleatórias e 6% das senhas com iniciais de uma frase. Em seguida, um ataque de força bruta foi realizado. Neste tipo de ataque, senhas sequenciais são geradas com base num conjunto de caracteres. Quanto menor a senha, mais suscetível ela é a um ataque de força bruta. Todas as senhas com menos de 6 caracteres foram quebradas.

A conclusão que podemos chegar até aqui é que uma senha segura contém 8 ou mais caracteres e deve ter variações de tipos de caracteres (maiúsculas, minúsculas, números e caracteres especiais). Mas senhas precisam ser fáceis de memorizar, e agora?

A questão da memorização

O quadrinho abaixo, em inglês, exemplifica uma ideia criativa para criar senhas seguras e fáceis de memorizar:

Criando uma senha segura

A ideia do autor, também defendida por uma parte da comunidade de segurança, é não usar uma senha complexa cheia variações especiais, pois ela seria de dificílima memorização para humanos, mas fácil de quebrar para computadores. Por outro lado, uma sequência de palavras de fácil memorização para humanos seria mais segura do ponto de vista computacional.

A princípio alguém poderia argumentar que selecionar quatro palavras não é tão diferente de uma senha de 4 dígitos, certo? Errado!

Análise combinatória da entropia das senhas: qual tipo de senha é mais segura?

Uma senha consiste numa combinação de elementos cuja ordem importa e onde existe repetição. Este é o conceito de arranjo com repetição, cuja fórmula é:

T = nr, sendo:

T = total de combinações (quanto maior, mais difícil de adivinhar)

n = quantidade de elementos (tamanho da senha)

r = número de elementos selecionados (número de possibilidades de cada caractere da senha)

Vamos aplicar esta fórmula a alguns tipos de senhas e obter o número de combinações possíveis:

  • Senha de 4 caracteres composta apenas de letras maiúsculas ou minúsculas (26 elementos selecionados)
        T = 426
  • Senha de 8 caracteres composta apenas de letras maiúsculas ou minúsculas (26 elementos selecionados)
        T = 826
  • Senha de 8 caracteres composta de letras, números e caracteres especiais “!”, “@”, “#”, “$”, “%”, “&”, “*”, “_”, “.” e “-” (72 elementos selecionados)
        T = 872
  • Senha de 4 palavras, considerando as 3 mil palavras mais comuns de uma língua
        T = 43.000
  • Senha de 4 palavras, selecionadas de um mini dicionário (um “mini Aurélio” possui 30 mil verbetes)
        T = 430.000

Os itens acima estão ordenados, de forma que o primeiro oferece a menor entropia e o último a maior. Portanto, senhas com palavras, ainda que contidas num dicionário, são muito mais seguras do que senhas complicadas que misturam números, letras maiúsculas e minúsculas e alguns caracteres especiais. Além disso, uma sequência de palavras é muito mais fácil de memorizar.

Senhas diferentes para sites diferentes

Você usa a mesma senha de outros sites quando faz um novo cadastro em um site qualquer? Já pensou que, se apenas uma das contas que você possui for comprometida, o hacker terá acesso a todas as suas outras contas? Voltamos ao problema da memorização: se devemos usar senhas diferentes para cada site, como poderemos lembrar-nos de cada uma delas?

Os simplistas dizem: simplesmente anote suas senhas num papel e guarde na carteira. Bem, se você já perdeu a carteira ou foi roubado, isso soa um tanto absurdo, não é mesmo?

A primeira solução apontada pelos especialistas é usar um aplicativo gerenciador de senhas. Você memoriza uma senha mestra e a usa para acessar as demais senhas. Porém, é ruim ficar dependente de um aplicativo. Se for um aplicativo para desktop você nem sempre vai estar com sua máquina ao seu lado, se for mobile alguém pode roubar seu celular e se for na nuvem (web) existem outras questões de segurança a serem consideradas.

Outra sugestão apontada em fóruns de segurança é criar um “algoritmo pessoal” baseado no site. Suponha que sua senha do Facebook seja “Eu amo o Facebook!”, então sua senha do Gmail é “Eu amo o Gmail!”. Bem, o problema com essa abordagem é óbvio: qualquer um poderia adivinhar sua senha em outro site. Soluções paliativas consistem em criar um algoritmo mais difícil do que simplesmente usar o nome do site, mas isso sempre irá cair na categoria de segurança por obscuridade, o que não é nada bom do ponto de vista da Segurança da Informação.

Uma abordagem usada por muitos, às vezes inconscientemente, é adotar três senhas principais. A primeira é uma senha com baixa entropia, usada em cadastros diversos na internet que não trazem grande risco. A segunda é de média entropia usada em serviços mais importantes e pessoais, como a conta do Facebook, blog pessoal, e-commerce, etc. A última é uma senha de alta entropia usada em serviços críticos e essenciais, como o e-mail pessoal, bancos, etc. Porém esta solução ainda não é a ideal, pois um vazamento de informação levaria a uma brecha de segurança em serviços do mesmo nível.

Além das senhas

Se você conseguiu acompanhar as informações até aqui, deve ter percebido o tamanho da dificuldade por parte dos provedores de serviços e dos usuários em gerenciar senhas. Mas, existe alguma alternativa? Felizmente, sim! Inclusive alguns acreditam que o uso de senhas é algo que deva ser abandonado.

Uma das opções que temos é o uso de certificados digitais. Por exemplo, em alguns acessos SSH e VPN configura-se o servidor para aceitar um determinado certificado e o usuário que possui esse certificado em seu sistema poderá acessar o serviço. Porém, o uso de certificados por usuários hoje é um tanto restrito a administradores de serviços e redes ou a serviços importantes como a emissão de notas fiscais online. Isso ocorre, em parte, pelo esforço que seria necessário para gerenciar os certificados por parte dos administradores de rede e, em parte, pelo custo junto às autoridades certificadoras. Além disso, certificados só funcionam bem se o computador ou dispositivo for usado somente por uma pessoa, já que eles ficam armazenados no disco.

Dispositivos biométricos são outra alternativa. Eu, que sou cliente do banco Itaú, já utilizo a autenticação por impressão digital há alguns meses no caixa eletrônico, entretanto ainda estamos um pouco longe de ter tal tecnologia disponível em todos os serviços do dia-a-dia.

Outra solução mais recente é a autenticação colaborativa, cujos exemplos mais conhecidos são OpenID e OAuth. Nesse sistema, você usa uma conta única em diferentes serviços. É o caso do “Login com Facebook” ou “Login com Google”. Ao se cadastrar em um site com suporte a essas tecnologias, você não precisa criar um novo cadastro e fornecer a senha para aquele site, apenas autoriza o Facebook, Google ou outro serviço onde você possui uma conta a compartilhar algumas de suas informações.  Este mecanismo é muito interessante, pois você precisa gerenciar apenas uma senha “forte” e os demais serviços nunca chegam a processar sua senha. Num caso de invasão a um deles, um hacker não terá a menor chance de invadir outros serviços. O risco verdadeiro está em sua conta principal, mas pelo menos você sabe em quem está confiando. Porém, existe uma dificuldade em usar o OpenID de forma abrangente, pois as intranets não irão querer depender de um serviço externo para autenticarem seus usuários.

Conclusões

A criação de senhas seguras, usando diversidade de caracteres e um comprimento razoável, é importante para a segurança de nossas contas. Uma senha composta de quatro ou mais palavras é mais segura que uma senha complexa e aleatória de oito dígitos.

Mas o problema está mais embaixo. Deveríamos criar senhas diferentes para cada serviço, o que, por sua vez, dificulta a memorização. Uma solução é usarmos grupos de senhas, isto é, uma senha simples para serviços de pouca importância, outra mais segura para serviços essenciais e uma senha complexa para serviços críticos.

Existem alternativas melhores ao uso de senhas, como certificados, dispositivos biométricos e os padrões OpenID e OAuth. Infelizmente o uso desses mecanismos de autenticação é limitado por diversos fatores no mundo atual.

Enfim, não há uma solução fácil disponível hoje. Aliás, se você tiver uma, me conte e vamos ficar bilionários! 😉

Planejou considerando tempo para refatoração. Nada mal!

obama-refatoração

Os planejamentos de projetos de desenvolvimento de software de maneira geral englobam atividades de especificação, codificação, testes e talvez um tempo para correções. Nós pensamos nas funcionalidades implementadas como blocos de uma construção, sendo colocados uns sobre os outros.

Porém, não é assim que um software funciona. Ao adicionarmos novas funcionalidades no decorrer do projeto, o sistema como um todo pode ser afetado e, por vezes, torna-se necessário adaptar ou reescrever parte do que já estava “pronto”. Por isso, um sistema 90% “pronto” pode levar muito mais que 10% do tempo planejado para ser efetivamente concluído.

Pense nisso!

Sempre que um programador concatena um parâmetro da requisição na query, uma fada morre

fada-parametro-query
E isso vale para qualquer dado vindo de fora do sistema: banco de dados, arquivos, web services e por aí vai.

Página 4 de 6

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.