A culpa é do sistema: decisões mal planejadas podem arruinar muito mais do que dados

Quem nunca ouviu isso ao entrar em contato com um prestador de serviços indignado porque uma operação crítica não está funcionando? Desde gestores técnicos de grandes corporações até o usuário final, todos sofrem as mazelas de uma implantação ou migração mal sucedida.

A bola da vez é a Dafiti, uma das maiores e mais bem sucedidas empresas de e-commerce. A loja, que com muito esforço construiu uma reputação sólida e ganhou credibilidade pela qualidade dos seus produtos, processos e atendimento, está agora em maus lençóis. Segundo matérias publicadas pela imprensa, desde junho a empresa sofre de um “apagão” no atendimento. Serviços como entregas, trocas ou devoluções estão inativos e os insistentes contatos dos consumidores lesados estão sendo ignorados, por determinação da direção da empresa.

Segundo matéria publicada no Estadão, a polêmica decisão foi porque “não há o que dizer aos clientes agora. Quando houver algo a ser dito, eles serão comunicados.” Com isso, somente no mês de julho, o site de reclamações Reclame Aqui registrou um número que se aproxima das 11 mil reclamações contra a empresa. Além disso, diversos processos foram abertos e o Procon de SP gerou uma multa superior a R$ 300 mil. Isso sem considerar os danos à imagem, que dificilmente terá sua credibilidade recuperada. Talvez no longo prazo.

A culpa é do sistema

Em nota oficial, a Dafiti informa que está realizando uma migração de tecnologia, implantando um sistema que custou à empresa R$ 20 milhões. Antes mesmo da virada de chave, a comerciante iniciou ações com o objetivo de alertar os consumidores de que as comunicações e prazos estariam prejudicados, até que tudo estivesse funcionando. O problema, é que o consumidor não quer saber se a culpa é do sistema, do hardware, do atendente, diretor ou de quem quer que seja. E em geral, o cliente vai ser o maior afetado por qualquer falha tecnológica que venha a ocorrer. Logo, nesse caso, a empresa poderia somar ao investimento o alto custo dos danos à sua imagem, as multas, processos judiciais e as vendas perdidas, o que facilmente poderá triplicar os custos de implantação.

O case acima expôs os riscos de uma decisão mal planejada e uma ação mal executada. Será que antes de aprovar a aquisição dessa tecnologia, todos esses transtornos nessa escala haviam sido previstos? Dificilmente.

Foco nos objetivos da organização

Quando uma empresa toma a decisão de utilizar determinada tecnologia, ela deve analisar todos os possíveis efeitos que aquela decisão causará. E quando delegar a atividade para seus times e fornecedores, é importante compartilhar as implicações para que todos tenham ciência das responsabilidades que essa ação gera. Expondo ao time a importância e riscos da decisão, talvez, aquele analista mais quietinho que tenha previsto um possível atraso ou falhas na virada de chave, seja mais cauteloso ao expor suas percepções técnicas.

Plano B

Não sabemos de fato o que aconteceu no caso da Dafiti. Não estamos lá dentro e nem temos negócios com essa empresa. Mas utilizemos o case como alerta, pois em um caso como esse, onde a mudança no sistema vai afetar drasticamente as operações, o ideal é que a migração seja feita de forma gradual e que haja um plano de contingência que permita fazer um downgrade, caso a virada de chave mostre falhas.

Que fique o exemplo para todos, sejam fornecedores de TI, como é o caso da Mobiltec, seja para as grandes corporações. É importante nunca esquecer que os sistemas todos servem apenas para melhorar processos que aumentem a produtividade e consequentemente lucratividade das empresas. Se isso não for uma premissa, então a decisão já nasce comprometida.

 Escrito por Aline Roque

Desvendando as DataInteractions do Espresso

Não é a primeira vez que falamos sobre Espresso aqui no blog. Para quem não leu, no primeiro post sobre os testes de interface utilizando o Espresso, foram mostrados os passos iniciais para quem quer utilizar a API da Google em seus testes. Agora, vamos abordar alguns conceitos mais práticos com relação às DataInteractions, conhecer o código por trás da aplicação, o que torna muitas vezes mais fácil entender como utilizá-las.

Para melhor acompanhar o post, recomendo que você já tenha clonado o repositório do Espresso (https://code.google.com/p/android-test-kit/) ou ao menos baixado o código fonte (https://code.google.com/p/android-test-kit/source/browse/).

Todos os códigos aqui mostrados são referentes à versão 1.1 (última na data do post).

Ao iniciar a implementação dos testes de interface, creio que uma das maiores dificuldades na utilização do Espresso seja lidar com Listas, Spinners ou qualquer outra classe derivada de AdapterViews. Esse tipo de View não carrega todas as informações quando o usuário chega à tela, de modo que só existem efetivamente uma parte das Views na tela, não sendo tão simples acessar os elementos para executar ações e verificações.

Como havia citado no primeiro post, o mais importante é se atentar ao fato de que deve-se procurar um objeto em específico, não a View que contém os dados desse objeto. Mas por quê? Como funciona isso?

Bom, primeiramente, vamos dar uma olhada nos métodos que a classe DataInteraction possui:

public DataInteraction onChildView(Matcher childMatcher)
public DataInteraction inRoot(Matcher rootMatcher)
public DataInteraction inAdapterView(Matcher adapterMatcher)
public DataInteraction atPosition(Integer atPosition)
public DataInteraction usingAdapterViewProtocol(AdapterViewProtocol adapterViewProtocol)

public ViewInteraction perform(ViewAction... actions)
public ViewInteraction check(ViewAssertion assertion)

private AdapterDataLoaderAction load()
private Matcher makeTargetMatcher(AdapterDataLoaderAction adapterDataLoaderAction)
private Matcher displayingData(final Matcher adapterMatcher,
final Matcher

 

O primeiro grupo é utilizado para especificar os parâmetros (discutidos no final do post) para buscar nossa DataInteraction, o último são métodos privados utilizados internamente pela classe.

Os dois métodos que mais interessam para entender como funciona são os do segundo grupo (spoiler: na verdade eles chamam os métodos do terceiro grupo, mas vamos por partes), que é quando o Espresso efetivamente busca a View que você procura:

public ViewInteraction perform(ViewAction... actions) {
    AdapterDataLoaderAction adapterDataLoaderAction = load();

    return onView(makeTargetMatcher(adapterDataLoaderAction))
                 .inRoot(rootMatcher)
                 .perform(actions);
}

public ViewInteraction check(ViewAssertion assertion) {
    AdapterDataLoaderAction adapterDataLoaderAction = load();

    return onView(makeTargetMatcher(adapterDataLoaderAction))
                 .inRoot(rootMatcher)
                 .check(assertion);
}

(Sim, em suma, eles fazem a mesma coisa para buscar os dados.)

Primeiramente, eles utilizam uma ViewAction para encontrar e carregar o dado procurado no AdapterView:

private AdapterDataLoaderAction load() {
    AdapterDataLoaderAction adapterDataLoaderAction =
        new AdapterDataLoaderAction(dataMatcher, atPosition, adapterViewProtocol);

    onView(adapterMatcher)
          .inRoot(rootMatcher)
          .perform(adapterDataLoaderAction);

    return adapterDataLoaderAction;
}

Sim, vamos ter que ir mais fundo no código (codeception) para entender, mas vamos direto ao método perform dessa ViewAction, que é onde a mágica acontece:

AdapterView adapterView = (AdapterView) view;
List matchedDataItems = Lists.newArrayList();

for (AdapterViewProtocol.AdaptedData data : adapterViewProtocol.getDataInAdapterView(adapterView)) {
    if (dataToLoadMatcher.matches(data.data)) {
        matchedDataItems.add(data);
    }
}

Nessa primeira parte, basicamente, ele utiliza um AdapterViewProtocol para buscar todos os dados do AdapterView.

A classe AdapterViewProtocol em geral não é algo a se preocupar. Ela apenas abstrai algumas questões de scroll e busca dos itens. A não ser que você implemente uma classe derivada da AdapterView que tenha propriedades muito diferentes da original, em geral, o protocolo padrão que ele utiliza não trará problemas.

O método getDataInAdapterView utiliza o método getItemAtPosition(int position) da classe AdapterView, iterando sobre todas as posições. Então, é com o retorno desse método que você deve se preocupar na hora de buscar um item. Se você analisar a sua classe do adaptador será fácil entender como buscar o item que você quer utilizar.

O dataToLoadMatcher é o matcher que você passou para o parâmetro onData. Então, por exemplo, se você tem uma ListView com um determinado Adapter que tem dois tipos de itens, ele vai iterar sobre todos os itens, mas se você tiver especificado uma classe (is(instanceOf(classeX.class))), a lista matchedDataItems vai conter somente os objetos daquela classe.

Após isso, ele verifica se ao menos um item bate com o matcher passado, e, caso sim, segue adiante:

if (atPosition.isPresent()) {
    int matchedDataItemsSize = matchedDataItems.size() - 1;
    if (atPosition.get() > matchedDataItemsSize) {
        throw new PerformException.Builder()
                                  .withActionDescription(this.getDescription())
                                  .withViewDescription(HumanReadables.describe(view))
                                  .withCause(new RuntimeException(String.format(
                                             "There are only %d elements that matched but requested %d element.",
                                             matchedDataItemsSize, atPosition.get())))
                                  .build();
    } else {
        adaptedData = matchedDataItems.get(atPosition.get());
    }
} else {
    if (matchedDataItems.size() != 1) {
        StringDescription dataMatcherDescription = new StringDescription();
        dataToLoadMatcher.describeTo(dataMatcherDescription);
        throw new PerformException.Builder()
                                  .withActionDescription(this.getDescription())
                                  .withViewDescription(HumanReadables.describe(view))
                                  .withCause(new RuntimeException("Multiple data elements " +
                                           "matched: " + dataMatcherDescription + ". Elements: " + matchedDataItems))
                                  .build();
    } else {
        adaptedData = matchedDataItems.get(0);
    }
}

Basicamente, se você especificou a posição do elemento desejado, ele busca entre os itens que bateram com o matcher passado, o item que está naquela posição. Se não, você deve passar um matcher que coincida com um e exclusivamente um elemento, que será retornado.

Por fim são realizadas algumas operações na Thread principal (onde ocorrem as operações de UI) tentando realizar um scroll até o item requisitado.

Uma vez encontrado o item e realizado o scroll com sucesso, o método makeTargetMatcher é o responsável por, de certa forma, transformar o nosso dataMatcher em um viewMatcher:

private Matcher makeTargetMatcher(AdapterDataLoaderAction adapterDataLoaderAction) {
    Matcher targetView = displayingData(adapterMatcher, dataMatcher, adapterViewProtocol,
                                              adapterDataLoaderAction);
    if (childViewMatcher.isPresent()) {
        targetView = allOf(childViewMatcher.get(), isDescendantOfA(targetView));
    }

    return targetView;
}

E o método displayingData retorna um ViewMatcher com as seguintes características:

Optional data = adapterViewProtocol.getDataRenderedByView( (AdapterView) parent, view);
if (data.isPresent()) {
    return adapterDataLoaderAction.getAdaptedData().opaqueToken.equals(data.get().opaqueToken);
}

O que está acontecendo aqui? Bom, ele busca entre todas as Views da tela uma que seja renderizada pelo AdapterView em questão, ou seja, que esteja dentro do AdapterView, e que esteja na mesma posição do item encontrado com o DataMatcher.

Então, por fim, depois de carregar o dado procurado e realizado o scroll até ele, podemos utilizar esse ViewMatcher como parâmetro do método onView, como é feito nos métodos check e perform da classe DataInteraction, já que agora temos certeza de que a View está na tela.

 

E sobre os parâmetros? Bom, a partir do código acima e dando uma olhada nas inicializações feitas na classe DataInteraction, podemos chegar às seguintes conclusões:

public DataInteraction onChildView(Matcher childMatcher)

  • Não é estritamente necessário para localizar um item dentro do AdapterView.
  • Valor Padrão: Não possui (Se não for especificado, não é utilizado)
  • Utilização: Quando quiser se referir a alguma view específica dentro do layout do item em questão
  • Exemplo: Se utilizar Layout personalizado para os itens de uma ListView que tenha duas labels e queira se referir somente a uma delas e não ao Layout base para fazer uma verificação ou executar uma ação.

public DataInteraction inRoot(Matcher rootMatcher)

  • Valor Padrão: Janela que estiver com o foco.
  • Utilização: Quando quiser se referir a algum AdapterView que não esteja na janela exibida atualmente.
  • Exemplo: Se uma janela de diálogo estiver sendo exibida e você quer se referir a um AdapterView da Activity que está por trás da mensagem de diálogo.

Cuidado ao utilizar esse método! O AdapterView deve existir na tela ainda. Você não pode utilizar isso para se referir a um layout de uma Activity que não é mais mostrada na tela.

public DataInteraction inAdapterView(Matcher adapterMatcher)

  • Valor Padrão: isAssignableFrom(AdapterView.class)
  • Utilização: Sempre que houver mais de um AdapterView.
  • Exemplo: Uma activity com uma ListView e um Spinner, poderia-se utilizar, para se referir ao Spinner:
    inAdapterView(isAssignableFrom(Spinner.class))

public DataInteraction atPosition(Integer atPosition)

  • Valor Padrão: Não possui (Se não for especificado, utiliza o primeiro e único elemento retornado)
  • Utilização: Sempre que o matcher especificado bater com mais de um item dentro do AdapterView.
  • Exemplo: Clicar no primeiro item do tipo String dentro de um AdapterView:
    onData(is(instanceOf(String.class))).atPosition(0)

public DataInteraction usingAdapterViewProtocol(AdapterViewProtocol adapterViewProtocol)

  • Valor Padrão: Protocolo padrão implementado pelo Espresso (Classe StandardAdapterViewProtocol)
  • Utilização: Implementações de AdapterViews que apresentam problemas com a implementação padrão.
  • Exemplo: ExpandableListView é um exemplo citado nos comentários do código da classe AdapterViewProtocol em que não se pode usar os métodos getAdapter e getItemAtPosition, de modo que é necessário implementar uma interface a parte para utilizar o método onData.
Esperamos que os conceitos apresentados possam ser úteis para quem deseja se aventurar na utilização das DataInteractions da API do Google.
Escrito por Rodrigo Lessinger

Gerência do ciclo de vida de uma aplicação

Já se passaram um ano e meio aproximadamente desde que Eduardo Klein falou sobre automação de build e deploy no M3 aqui no blog. Em seu post, ele focou em aspectos mais conceituais desse processo. De lá pra cá, o produto M3 evoluiu bastante e diversos outros projetos foram iniciados na Mobiltec. Por isso, entendemos que agora é um ótimo momento para revisitar o assunto. O objetivo desse post, portanto, é abordar novamente o tema build e deploy, mas dessa vez com um foco maior nas ferramentas utilizadas durante todo o processo de desenvolvimento.

Antes de seguir adiante é importante deixar claro sobre o que exatamente estamos falando:

O termo comumente encontrado é ‘ALM’, que significa “Application Lifecycle Management”, ou “Gerenciamento do ciclo de vida de aplicações”. Apesar do nome bastante descritivo, o conceito de “ciclo de vida” para uma aplicação envolve diversas etapas. Algumas delas já foram citadas pelo Eduardo, como o controle de versão de código, o build e o processo de deploy (também chamado de “Release Management”, ou apenas “Release”). Além dessas, também foi citada a etapa de testes, da qual falaremos mais adiante de forma diferenciada para testes unitários, testes de integração e testes de sistema.

A imagem a seguir representa o conjunto de ferramentas de ALM da Microsoft, na época do Visual Studio 2010. De todos esses componentes, nem todos são utilizados pela Mobiltec (como as ferramentas integradas do Office com o TFS), e outros ainda nem existiam, como o Release Management que citaremos em seguida.

Motivação

Deixando de lado as terminologias por um momento, qual a grande motivação de seguir essas etapas? Por que mantemos diversos servidores, cada um com vários serviços especializados para cumprir cada passo? Como foi muito bem colocado no artigo antigo, o custo (tanto em equipamentos quanto em conhecimento das ferramentas) se justifica rapidamente, e nos ajuda a ser cada vez mais produtivos.

Recentemente, uma forte discussão surgiu em relação à prática de Test Driven Design (TDD) na comunidade de desenvolvedores, por conta de um post polêmico escrito por David Heinemeier. Todo o desdobramento que se seguiu pode ser acompanhado nesse resumo, feito pelo Martin Fowler, que também participou da discussão. Mesmo aos que não gostam da área de testes recomendo fortemente que assistam.

O que essa série de discussões sobre testes tem a ver com o nosso assunto? Diretamente, nada. No entanto, um dos pontos mais importantes, enfatizados em todos os vídeos em diversos momentos, é a necessidade de feedback sobre o desenvolvimento. Quando escrevemos testes (e os executamos com frequência), temos a garantia de que os comportamentos esperados estão corretos, e consequentemente podemos melhorar o design das classes afetadas e mantê-las com maior segurança.

Outro processo que tem muita relação com esse conceito é o desenvolvimento ágil, mais especificamente o Scrum, que nos últimos meses vem sendo adotado por diversos de nossos times de desenvolvimento. Novamente, o que existe em comum entre o Scrum e as ferramentas a serem discutidas (além de outros fatores que não entraremos no mérito aqui) é o feedback que obtemos do processo como um todo. Como existem iterações de tempo fixo, ao final de cada etapa podemos analisar a resposta do time ao Sprint anterior, ver os problemas e atacá-los com objetividade, para que as iterações seguintes sejam cada vez mais eficientes.

Se pensarmos pelo lado de “validação”, esses dois mecanismos se assemelham bastante às etapas do desenvolvimento. O servidor de build, por exemplo, não passa de mais um mecanismo de validação, que nos dá mais informação para corrigir problemas que seriam detectados muito mais tarde se o processo não existisse. A ferramenta de release também serve esse propósito, pois automatiza um algoritmo potencialmente complexo de instalação, sujeito a falhas humanas, de forma que a atualização de versão em um ambiente de produção se reduza a uma “decisão”, e não mais a um “problema”: o pipeline de release é disparado com um único clique e a aplicação é instalada automaticamente no destino.

Ferramentas

Abaixo, listamos as principais ferramentas utilizadas. Nosso objetivo aqui é dar uma visão geral sobre as mesmas, explicando onde elas se inserem em nossos processos. E novos posts vamos aprofundar um pouco mais cada uma delas.

- Team Foundation Server (TFS):
Sistema de controle de versão de código da Microsoft. É o ponto de entrada do nosso pipeline de desenvolvimento. Permite a aplicação de diversos processos de desenvolvimento como o Agile ou o Scrum, através de atividades, chamadas de “work items”, que mantêm o histórico de modificações e requisitos de um projeto. Idealmente instalado em uma máquina física ou virtual dedicada.

- TFS Build:
Subcomponente do TFS, responsável por fazer o processamento dos arquivos fonte em um ambiente isolado, onde é possível reproduzir compilações e testes unitários de forma consistente, sem particularidades de uma máquina de desenvolvedor. Dividido em dois outros módulos principais:

  1. Build Controller: gerencia de forma centralizada as requisições de build dos diversos projetos. Sua responsabilidade é coordenar os agentes de build para executar as requisições provenientes de todos os projetos atrelados à instância do TFS. O controlador é um componente relativamente leve e, portanto, pode conviver sem problemas no mesmo computador que o TFS principal.
  2. Build Agents: serviços que executam o workflow de build propriamente dito, de acordo com o que for indicado pelo controller. Eles devem ser configurados em máquinas com características específicas para cada projeto, e requerem uma boa configuração de hardware para manter o processo de validação o mais responsivo possível.

- Microsoft Test Manager:
É um componente relativamente novo, ótimo para gerência de testes de sistema, de carga e de interface. É integrado ao TFS pelos chamados planos de teste (TestPlans). Cada plano contém inúmeros Test Cases (um tipo específico de work item), que por sua vez são linkados a User Stories ou Product Backlog Items, que representam os requisitos do software. Permite a configuração de topologias de teste avançadas, utilizando diretamente máquinas virtuais gerenciadas pelo Hyper-V ou pelo SCVMM (System Center Virtual Machine Manager). Pode ser ativado tanto pelo processo de build, via workflow de Lab Management ou na etapa de release via commandline. De forma análoga ao build, também é dividido em dois módulos:

  1. Test Controller: centraliza as requisições de processamento de planos de teste, delegando aos agentes definidos no plano a execução dos testes associados.
  2. Test Agent: serviço que executa os testes passados pelo controller com as configurações especificadas.

- Release Management:
Ferramenta recente de gerenciamento de releases. Antes conhecida como InRelease e desenvolvida pela InCycle Software, foi adquirida pela Microsoft no início do ano passado e oficialmente lançada em Novembro de 2013. Permite a criação e configuração de um pipeline customizado de release, com etapas definidas pelo usuário, mecanismos de transição entre etapas, configuração por ambiente, etc. Featureset bem similar ao Octopus Deploy, também utilizado internamente por alguns projetos da Mobiltec. Possui três componentes distintos:

  1. Release Management Client: aplicação que permite a configuração do pipeline de release. Em geral instalado em todas as máquinas de desenvolvedores que participam do projeto.
  2. Deployment Server: análogo ao Build e Test controllers, se comunica com o mecanismo de build do TFS para iniciar os processos de release.
  3. Deployment Agent: instalado nas máquinas destino para permitir o download e execução da instalação dos artefatos do build remotamente.

Por hora é isso! Em um próximo post, mostraremos detalhes da primeira ferramenta, o TFS como source control. Discutiremos algumas vantagens e desvantagens de estratégias de branch distintas, como estruturar os projetos e solutions, tipos de projeto e work items, checkin policies, entre outros.

Fique atento!

Escrito por Juliano Gonçalves

Windows Workflow Foundation na prática

O Windows Workflow Foundation, integrado ao .NET Framework 3.0 em 2006, é uma tecnologia desenvolvida pela Microsoft que fornece um conjunto de bibliotecas para auxiliar no desenvolvimento de fluxos de trabalho de maneira rápida e fácil. Oferece uma linguagem de mais alto nível para criação de tarefas paralelas e assíncronas, além de fornecer suporte a escalabilidade das aplicações. Em um post anterior do blog, foi apresentada uma visão geral da tecnologia, que você pode conferir aqui.

Agora, o objetivo é apresentar o Windows Workflow na prática! Vamos criar uma aplicação bem simples, utilizando alguns elementos da tecnologia. A aplicação deve calcular os n primeiros dígitos da sequência de Fibonacci. O fluxo está descrito abaixo.

1. Solicitar ao usuário o valor de n. Em caso de entrada inválida, exibir uma mensagem de erro e encerrar.
2. Calcular a sequência de Fibonacci.
3. Exibir a sequência na tela.

Primeiro, vamos criar um novo projeto. Na janela de novo projeto, em Templates -> Visual C# -> Workflow, escolha a opção Workflow Console Application, como na Figura 1.


Figura 1

Delete o arquivo Workflow1.xaml e adicione um novo fluxo com um nome mais apropriado, como FibonacciWorkflow.xaml, conforme a Figura 2.


Figura 2

Na classe Program.cs, onde o fluxo é criado e chamado pelo Main da aplicação, faça as alterações necessárias para que o novo fluxo seja usado, conforme código abaixo. A última linha foi adicionada para que o console seja fechado após alguma tecla ser digitada, de modo que o usuário possa visualizar a resposta.

static void Main(string[] args){
       Activity workflow = new FibonacciWorkflow();
       WorkflowInvoker.Invoke(workflow);

       Console.ReadKey();
}

 

O primeiro passo do fluxo é solicitar o valor de n para o usuário. Por isso, adicionamos uma atividade do tipo WriteLine no fluxo. Essa atividade deve ser adicionada dentro de uma sequência, visto que teremos que adicionar mais atividades depois. A Figura 3 ilustra esse passo.


Figura 3

Em seguida, devemos verificar se a entrada é válida. Para isso, tentaremos converter essa entrada em inteiro e, em caso positivo, passá-lo com parâmetro para uma atividade calcular a sequência. Em caso negativo, uma mensagem de erro deve ser exibida. Adicionaremos uma variável ao fluxo para armazenar o inteiro, conforme Figura 4.


Figura 4

Para converter a entrada, vamos utilizar o método Parse para Int32, armazenando na variável recém criada. Como esse método pode disparar uma exceção em caso de falha na conversão, iremos colocar o restante da lógica em uma instrução Try-Catch. Em caso de exceção, uma mensagem de erro deve ser exibida. A Figura 5 ilustra esses passos.


Figura 5

Se a entrada for um inteiro, devemos passá-lo a uma atividade que irá calcular a sequência de Fibonacci. Vamos criar essa atividade antes de prosseguir com o fluxo. Para isso, devemos inserir uma CodeActivity, conforme Figura 6.


Figura 6

A atividade precisa de um argumento de entrada inteiro, o valor de n, e um argumento de saída, a lista de números da sequência. Isso é feito adicionando os seguintes membros a atividade:

public InArgument N { get; set; }
public OutArgument> Sequence { get; set; }

O método Execute é responsável por executar a atividade, por isso deve conter a lógica para calcular a sequência de Fibonacci, além de popular o argumento de saída. Nesse exemplo, o corpo do método é bem simples, conforme código abaixo.

protected override void Execute(CodeActivityContext context) {
         int n = context.GetValue(this.N);

         List sequence = CalculateSequence(n);

         Sequence.Set(context, sequence);
}

 

Recompilando a solução, a atividade recém criada aparece na Toolbox e pode ser utilizada no nosso fluxo agora, conforme a Figura 7.


Figura 7

Nas propriedades da atividade, devemos especificar qual variável será passada com parâmetro n e qual variável receberá o valor de retorno da atividade. A Figura 8 ilustra esse passo, considerando que uma variável local já foi criada para armazenar o retorno.


Figura 8

Por último, devemos exibir a sequência na tela, inserindo um laço ForEach logo após a atividade do cálculo, conforme Figura 9.


Figura 9

O laço deve apenas percorrer a sequência e escrever na tela, conforme a Figura 10.


Figura 10

O objetivo desse exemplo simples é introduzir o Windows Workflow Foundation na prática, por isso ainda não fizemos uso de todos os recursos que oferecem suporte ao paralelismo e a escabilidade. Então, o que achou? Qualquer dúvida ou sugestão, deixe nos comentários.

Escrito por Paula Burguêz

Estimativa de Esforço e o Pensamento Ágil

A adoção de uma metodologia de desenvolvimento ágil de sistemas traz mudanças significativas ao trabalho do time, sejam estas por práticas inerentes à metodologia adotada, mas também mudanças de paradigma relacionadas ao pensamento ágil.

A estimativa de esforço de desenvolvimento é uma prática onde ocorre mudanças impactantes. No Scrum, por exemplo, esta atividade é realizada pelo próprio time de desenvolvimento, principalmente nas etapas de refinamento do Product Backlog e planejamento do Sprint Backlog.

Recentemente iniciamos a adoção do Scrum em novos times de desenvolvimento que foram formados na Mobiltec, onde muitos dos profissionais envolvidos não possuem experiência prática com a metodologia em questão. Os primeiros Sprints apresentaram problemas naturais de estimativa, originados tanto da falta de histórico de referência como também da carência de um processo adequado para realizar as estimativas.

Nestes primeiros Sprints o Burndown fugiu bastante da expectativa, o que é aceitável visto que o time não tem histórico para identificação de sua velocidade real. No entanto, alguns sintomas foram identificados durante o processo. A análise e correção desses pontos auxiliaram o time a evoluir sua prática de estimativa:

  1. Embora o time se sentia satisfeito com os resultados do Sprint Planning, esta cerimônia estava tomando menos da metade do tempo que a metodologia aponta como adequado.
  2. As estimativas, que a uma primeira vista pareciam coerentes, eram consolidadas apenas por parte do time. Os demais estimavam influenciados pelos mais experientes ou apenas se omitiam.
  3. Mesmo com o atraso evidente do Sprint, visualizado no Burndown, percebeu-se que em alguns momentos um ou mais desenvolvedores do time estavam abaixo de sua produtividade, principalmente quando os mais experientes (aqueles que realizavam a estimativa de esforço) não estavam presentes.

Com a observação destes fatos, conseguimos identificar algumas falhas do nosso processo de planejamento. Como o processo de estimativa não estimulava a participação de todos, muitos problemas de entendimento de escopo e de meios de implementação não eram explicitados. Isto gerou uma dependência, que baixou a produtividade geral do time em momentos de ausência dos mais experientes, pois explicações sobre os objetivos se faziam necessárias a qualquer momento.

Com isso, houve o entendimento de que era necessário evoluir as práticas de planejamento. Todos os membros do time de desenvolvimento precisam sair da reunião de Sprint Planning com o claro entendimento dos objetivos do Sprint. O time debateu e optou pelo uso do Planning Poker, técnica de estimativa mais difundida entre praticantes do Scrum, pois entendeu que esta prática apóia a redução dos problemas identificados.

Para quem não conhece, o Planning Poker é uma técnica para realização de estimativas que utiliza como ferramenta um baralho individual, que pode ser físico ou virtual.  Esse baralho segue uma numeração próxima a sequencia de Fibonaccci para que as diferenças entre as complexidades não sejam muito baixas. Na reunião de planejamento do backlog, cada uma das user stories é apresentada ao time pelo Product Owner, para que os membros questionem e esclareçam suas dúvidas. Após, o time avalia a complexidade dessa tarefa. Cada um dos membros deve selecionar a carta com a complexidade estimada em story points, colocando-a na mesa com a face do valor oculta. Quando todos já tiverem feito suas escolhas, as cartas devem ser viradas e exibidas ao mesmo tempo. Assim, nenhum integrante é influenciado pela escolha de outro membro.

Os benefícios do Planning Poker

Durante os primeiros Sprints, o time trabalhou as estimativas sem processo formal. Após a sua adoção, foi possível observar diversos avanços:

  • Evita a influência dos participantes: Esta é justamente a proposta do Planning Poker, razão pela qual a técnica é muito difundida. Como durante o Poker todos devem apresentar suas estimativas ao mesmo tempo, cada um é forçado a pensar de forma independente.
  • Valida o entendimento do escopo: O fato de cada participante ter que pensar na sua estimativa individualmente lhe força o correto entendimento das funcionalidades. Antes do Planning Poker, era possível seguir o planejamento sem o correto entendimento, apenas aceitando as estimativas de outros participantes. Com o Planning Poker, discrepâncias de estimativas são um ótimo alerta de que alguém não está com o correto entendimento.
  • Aceita a incerteza: Ao utilizar a sequencia de Fibonacci o Planning Poker estimula os participantes a trabalhar com a ideia de estimar itens maiores com relativo grau de incerteza, deixando o processo mais leve e ágil.

Os desafios da mudança de paradigma: Story Points x Tempo

É muito comum em times iniciando no Scrum passar pela dificuldade de estimar o tamanho das estórias (Story Points), pois esta é uma unidade relativa de medida, que envolve esforço, complexidade e risco, entre outros fatores.

Como é uma unidade relativa, é preciso de uma base de comparação para criação das relações. Justamente porque os times estão iniciando o processo, precisam formar esta base para nortear o processo de estimativa de Story Points.

Em postagem publicada na comunidade da Scrum Alliance, Narasimhan Anantharangachar sugere uma abordagem evolutiva bem interessante para estimativas de estórias:

1. Quebra de estórias em tarefas e estimativa de tarefas em horas

Esse é o ponto de partida típico para times que são novos no Scrum. É relativamente fácil de fazer e já inicia-se a construção de um histórico para medição futura de velocidade.

Pontos negativos de estimativa de esforço em horas:

  • É comum a quebra de tarefas no ciclo análise, projeto, desenvolvimento, teste, que gera muita dependência entre tarefas e membros do time e, consequentemente, muito desperdício de tempo em espera.
  • Inconsciente ou não, estimativa acaba virando meta. Os membros do time tendem a tentar cumprir o tempo estipulado, mesmo este estando errado (para cima ou para baixo). Tarefas subestimadas tendem a ficar com qualidade comprometida, enquanto que para o caso das tarefas superestimadas a teoria da “expansão dos gases” acaba prevalecendo.

2. Quebra de estórias em tarefas, sem estimativa de esforço de tarefas em horas

Aqui o time ainda quebra as estórias em tarefas para ter uma base para a estimativa da estória, mas não mais atribui um tempo específico por tarefa. O autor aponta benefícios importantes nesta abordagem:

  • Como as tarefas não possuem uma estimativa de esforço, os membros do time podem aplicar o tempo necessário para implementá-las com qualidade, sem prejudicar o objetivo do Sprint.
  • Promove a colaboração do time pois não há um controle apurado sobre início e término das tarefas. No longo prazo a colaboração em times Ágeis é um benefício.
  • O progresso é medido por estórias completadas, que agregam valor ao usuário, e não por tarefas completadas, que tem menor significado.

Pontos negativos:

  • A divisão em tarefas ainda gera dependência desnecessária entre membros do time, que gera desperdícios de tempo.

3. O time estima apenas as estórias, sem desmembrá-las em tarefas

Aqui o time se compromete não mais com tarefas, mas com estórias que agregam valor para o usuário do sistema. Segundo o autor é neste estágio que o time apresenta os melhores resultados pois:

  • A colaboração aumenta, pois os membros do time precisam interagir mais para desenvolver juntos as estórias, inclusive praticando pareamento.
  • Os membros do time se focam em desenvolver tarefas necessárias para cumprir com a estória, ao invés de simplesmente concluir itens de uma lista.
  • Do ponto de vista de resultados, é positivo pois o Burndown apresenta uma visão mais real do que está realmente pronto

Referências

Escrito por Eduardo Klein