Critérios de Aceite de Código Para Times Internos ou Terceirizados

MarcioMarchini 1,287 views 17 slides Jun 28, 2017
Slide 1
Slide 1 of 17
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17

About This Presentation

Esse documento exemplifica critérios objetivos de aceite de código desenvolvido por terceirizadas, devendo também ser seguido por times internos para que o nível de qualidade seja análogo e os resultados de produtividade e qualidade das entregas possam ser analisados.


Slide Content

Critérios de Aceite de Código Para
Times Internos ou Terceirizados
Marcio Marchini
2015/01/19

Introdução ..................................................................................................................................................... 1
1 - Requisitos Funcionais ........................................................................................................................ 1
1.1 - Formato: User Stories e BDD ................................................................................................. 2
1.2 - Serem Executáveis: Imprescindível para dar o Aceite (DONE) .............................. 2
REST ....................................................................................................................................................... 3
GUI WEB ............................................................................................................................................... 3
DONE ...................................................................................................................................................... 4
2 - Requisitos Não Funcionais .............................................................................................................. 4
2.1 - Performance .................................................................................................................................. 5
2.2 - Engenharia de Software ........................................................................................................... 5
2.3 - Dashboards ................................................................................................................................. 15
2.4 - Deployment ................................................................................................................................ 16
2.5 - Evolução de Database Schema: Migrations .................................................................. 16
2.6 – Escalabilidade ........................................................................................................................... 17
2.7 – Segurança ................................................................................................................................... 17

Introdução
Ajudando clientes percebemos um tema constante: alta viscosidade do software,
tornando excepcionalmente caro adicionar novas funcionalidades. O impacto de
qualquer mudança é gigante e quebra diversos pontos no sistema. E como se chegou
a esse ponto? “É de pequenino que se torce o pepino”. Características internas do
software simplesmente não foram controladas (faltou gestão técnica para tal).

Esse documento exemplifica critérios objetivos de aceite de código desenvolvido
por terceirizadas, devendo também ser seguido por times internos para que o nível
de qualidade seja análogo e os resultados de produtividade e qualidade das entregas
possam ser analisados.
1 - Requisitos Funcionais
Em demandas solicitadas ao time (através de um mecanismo qualquer de backlog),
serão explicitados ao time os requisitos de funcionalidades observáveis. Trataremos
do formato e de detalhes de ferramenta.

1.1 - Formato: User Stories e BDD
Para requisitos funcionais deverá ser usada a abordagem ágil de User Stories do XP
(Extreme Programming). Mais especificamente, no seguinte formato canônico.

Como <papel>, devo poder <ação> para que <valor/motivação da funcionalidade>.

Um exemplo de simples compreensão escrito no FitNesse:


Tais User Stories terão cada qual uma série de critérios de aceite com suas nuances
de regras de negócio1. Tais critérios serão escritos no formato Given-When-Then do
BDD:

DADO QUE <pré-condição>, QUANDO < ação> ENTÃO <pós-condição>

Eis um exemplo simples no caso do Login:



Note que é uma especificação-cenário, com dados exemplares (concretos e não
abstratos – usuário tal-e-tal, etc). É a abordagem de Specification by Example.

1.2 - Serem Executáveis: Imprescindível para dar o Aceite (DONE)
O conceito de DONE muitas vezes tem compreensão distinta entre o cliente e o
fornecedor da implementação. Visando eliminar esse tipo de problema, os requisitos

1 O que acontece se o login for válido? O que acontece se o login for invalido? O que

ágeis serão executáveis via ferramenta FitNesse ou similares2. Através da utilização
das fixtures como Xebium e RestFixture, tanto a camada REST quanto a camada GUI
poderão ser validadas contra a User Story e os GIVEN-WHEN-THEN.

REST
Exemplo no FitNesse de User Story sendo executada contra a camada REST:




GUI WEB
Exemplo da User Story sendo executada contra a camada de UI WEB:



2 Robot Framework, por exemplo.

Note que mesmo cenários específicos de uma camada apenas – GUI WEB – podem
ser descritos com a mesma abordagem (no caso, autocomplete de um campo da
tela):


DONE
Como vimos anteriormente, o conceito de DONE de um requisito é quando existe
uma spec para ele e esta passa (verde) quando executada no FitNesse. O objetivo é
ter essas validações ocorrerem de forma automática no build. Não há mais
ambiguidades.

2 - Requisitos Não Funcionais
Há vários requisitos não funcionais (não são features de funcionalidades direta aos
usuários) mas que beneficiam os usuários direta ou indiretamente. Por exemplo, a
escalabilidade de um servidor/serviço pode ser avaliada dessa forma.

2.1 - Performance
No caso de necessidade de escalabilidade e performance, esse requisito deve ser
escrito também em formato de User Story e GIVEN-WHEN-THEN.
Exemplo:

Como patrocinador do projeto, devo poder hospedar milhares de usuários
simultaneamente sem degradação de performance para que eu maximize a utilização
de recursos e o lucro.

DADO QUE há 1000 usuários logados simultaneamente comigo
QUANDO solicito os dados do processo xyz123
ENTÃO a resposta chega em menos que 500 ms em 95% dos casos

Esse GIVEN-WHEN-THEN torna-se então o critério de aceite da User Story, que foi
escrita sem parâmetros concretos (Quão rápido? Quantos milissegundos?). Temos
mais uma nuance de regra de negócio. Nesse caso, um requisito de performance,
onde se descreve o percentil (95%) e o indicador de resposta (500ms por request).

É responsabilidade dos Product Owners (usualmente da contratante) explicitar
esses requisitos não funcionais de forma precisa, sem ambiguidades.

2.2 - Engenharia de Software

Sabe-se que cerca de 44% do esforço de software vai em adaptá-lo para novas
funcionalidades (67% de 67%):

O custo dessa adaptabilidade é ligado diretamente às práticas de engenharia de
software que são utilizadas. Existência de testes automatizados, tamanho das
classes ou dos métodos etc todas contribuem para uma adaptabilidade mais cara ou
mais barata. Visando baratear o TCO (Total Cost of Ownership) desse código fonte,
faz-se necessária a utilização de boas práticas de software de maneira mensurável,
idealmente numa ferramenta de build como Jenkins, usualmente combinado com
SONAR.

Microarquitetura e métricas

A um nível micro, de classes e métodos, há um conjunto de métricas que devem ser
observadas. Dentre as principais destacamos algumas, que são mostradas em
ferramentas diversas como o SourceMonitor:



As métricas acima devem permanecer dentro da área verde (uma faixa). Essa faixa
de valores deve obedecer os seguintes valores default para as métricas:

Macroarquitetura e métricas

Sistemas mal modularizados tendem a ser difíceis de manter. APIs bem projetadas
permitem que esses sistemas evoluam de forma separada, com baixo impacto das
partes no todo. Tais componentes macro devem ser construídos aplicando os
princípios SOLID:

• Single Responsibility Principle
• Open Closed Principle
• Liskov Substitution Principle
• Interface Segregation Principle
• Dependency Inversion Principle

A não conformidade a esses princípios pode ser observada indiretamente com
várias métricas diferentes, que veremos a seguir.

Tangle e FAT

Duas métricas presentes na ferramenta Structure101 para analisar uma boa
arquitetura são: Tangle e FAT.

A nível de componentes a DSM deverá ser triangular inferior, sem nenhum valor
acima da diagonal da matriz. Exemplos bons:

São bons pois não há elementos acima da diagonal. Exemplo inaceitável (alto débito
técnico):

A métrica de triangularização inferior é fácil de ser computada: somatório das
células abaixo da diagonal dividido pelo somatório de todas as células. O resultado
deve dar 1. Qualquer valor inferior a isso significa presença de TANGLE.

A DSM pode ser visualizada em ferramentas como IntelliJ IDEA (Java), SONAR (no
caso de Java) ou Structure 101 (várias linguagens).

Haja vista que uma forma de burlar uma DSM imperfeita é amalgamar um ou mais
componentes “emaranhados” em um só (mais monolítico – aumentando o débito
técnico), a métrica FAT deve ser observada para o projeto terceirizado também. O
monolito seria um único componente, de métrica FAT máxima – algo indesejável.

Como balancear o tamanho dos componentes? O ponto de Tangle X Fat deve ficar na
região verde (e não na região vermelha), segundo o Structure 101. Eis um exemplo
indesejável:

Que técnicas usar? É preciso aplicar heurísticas para controlar a coesão e o
acoplamento de tais módulos:

• Reuse-release equivalence principle (REP)
• Common-reuse principle (CRP)
• Common-closure principle (CCP)
• Acyclic Dependencies Principle (ADP)
• Stable-dependencies principle (SDP)
• Stable-abstractions principle (SAP)

Maiores detalhes podem ser encontrados na bibliografia ágil (Bob Martin, etc) ou
até mesmo no wikipedia.

Instabilidade e Abstração
A utilização de Interfaces e classes abstratas permite que clientes e provedores
sejam desacoplados. Sendo assim, precisamos também de métricas que avaliem
quão concretos ou abstratos são os sistemas sendo utilizados. Felizmente as
métricas de Instabilidade e Distância da Main Sequence ( conhecidas como métricas
Bob Martin ) nos permitem avaliar o grau de maleabilidade de uma solução de
software. Basicamente queremos componentes que evitem a zona da dor e a zona da
inutilidade:


Exemplo aceitável:

As ferramentas stan4j e php_depend são exemplos de utilitários que plotam esse
tipo de gráfico:

Outra alternativa é srccheck https://github.com/sglebs/srccheck#oo-instability-
and-abstractness-plots :

O Dependency Inversion Principle deve ser seguido para que não haja elementos na
zona da dor. O ralo do grafo de dependência deve ser abstrato e estável (Interfaces
ou classes abstratas).

Arquitetura Intencional Explícita e em Camadas
A arquitetura do software deve ser algo intencional e controlado. Uma visão de
caixinhas e não violação de camadas de forma visual deve ser projetada e
acompanhada em ferramentas apropriadas. Eis um exemplo onde os
desenvolvedores violaram a arquitetura desejada pelo arquiteto, por falta desse tipo
de controle:

Em uma arquitetura Model-View-Presenter (MVP), por exemplo, a camada modelo
não deve ter conhecimento da (referências estáticas para a) camada View e nem
Presenter, e isso deve ser explicitado na ferramenta e garantido no build. Exemplo
real:


Similarmente, o princípio DIP (Dependency Inversion Principle) pode ser garantido
formalmente em linguagens estáticas da seguinte forma:

Vemos que uma camada Impl faz referencia às APIs, e a camada Builder conecta as
partes compatíveis a nível de API. O fato de as APIs estarem no ralo do grafo
(somente arestas incidentes e não de saída) demonstra aderência aos princípios de
Stable Abstractions Principle e Stable Dependencies Principle.
Sub-arquiteturas: design patterns
Com o intuito de evitar reinventar a roda, sempre que padrões de projeto existirem
para solucionar determinado problema e sua utilização flexibiliza e simplifica a
implementação, esses devem ser adotados. Exemplos como Observer (e sua variação
web chamada de WebHook), Model-View-Contoller. O padrão Singleton (global
disfarçada) deverá ser evitado ao máximo.
Se o seu time não é versado em Design Patterns, recomendamos capacitá-los. No
caso de e-learning, recomendamos o curso da Industrial Logic bem como o livro
Refactoring to Patterns.

Bugfinders

No caso de caça-bugs através de análise estática do código, devem haver 0 erros
severos e 0 erros críticos ou medianos. Apenas Warnings serão aceitas com
ressalvas, de comum acordo. Ferramentas possíveis são Findbugs (Java), PC-Lint
(C/C++), jsLint (JavaScript) dentre outros, a serem escolhidas de comum acordo
dependendo da plataforma usada (Java, Python, etc).

Cobertura
A cobertura de código realizada por testes automatizados (sejam as User Stories
executáveis no FitNesse ou sejam os testes unitários xUnit ou uma combinação
desses) deve ser igual ou superior a 70%. No build, os testes devem ser executados
com instrumentação para que essa cobertura seja coletada.

Code Cloning
Esse é o termo técnico para a “reusabilidade copy/paste”, que produz muito código
duplicado. A taxa de duplicação de código do projeto deverá ser inferior a 1%. Há
uma diversidade de ferramentas – algumas específicas da linguagem usada – que
podem ser usadas para medir tal duplicação de código.

Ferramentas para esse tipo de análise existem tanto como Open Source ou produto
comercial: Simian, CPD (Copy/Paste Detector), etc.

APIs
Documentação de APIs
Todas as APIs públicas de serviços e façades devem estar documentadas com
Javadoc ou equivalente na linguagem usada. Parâmetros, exceções, valores de
retorno devem estar claramente explicados.

No caso de APIs REST, essas deverão ser documentadas com Swagger.

Padronização de APIs
As APIs REST devem ser desenvolvidas de acordo com o padrão de facto da
indústria. Recomendamos aderir às guidelines do ebook “Web API Design – Crafting
Interfaces that Developers Love” da apigee.

No caso de APIs de componentes ou fachadas na linguagem de programação,
guidelines de boas APIs como o material de Joshua Bloch devem ser seguinda: How
to Design a Good API and Why It Matters [pdf] [palestra].

Evolução de APIs REST
Todas as APIs públicas de serviços REST devem evoluir gradativamente, sem
quebrar código existente anterior. Todas as APIs oficiais de uma Release N deverão
ser suportadas na versão N+1 sem quebras. Algumas podem ser marcadas como
Deprecadas mas ainda assim precisam rodar por mais uma Release. Apenas APIs
deprecadas na Release X podem ser removidas na Release X+1. Isso oferece uma
janela de tempo de adaptação, com uma Release intermediária servindo de warning
das APIs a serem removidas.
2.3 - Dashboards

Todas essas métricas devem estar consolidadas em dashboards Jenkins e SONAR.
Eis um exemplo real:

A análise pode ser instrumentada facilmente em um pipeline de build. Para
experimentos, nossa imagem docker pode ser usada:
https://github.com/sglebs/mysonar .
2.4 - Deployment
Desenvolvimento ágil moderno usualmente engloba rodar testes locais
primeiramente e depois testes em um ambiente DEV. Caso passem no DEV, o
sistema é “deployado” para um ambiente STAGING (release candidate) e, caso os
testes passem no STAGING, ele é promovido (deployado) para o LIVE. Esse processo
deve ser todo automatizado. Para tanto, aconselha-se o uso de algumas tecnologias
facilitadoras, como por exemplo Vagrant, Docker, Maven, Git etc. Combinadas,
algumas dessas ferramentas conseguem implementar uma plataforma altamente
ágil e flexível de deploy como o Heroku (basta um git push, todas os parâmetros são
definidos por variáveis de ambiente, componentes como banco de dados etc podem
ser “attachados”). Maiores detalhes no artigo 12factor.

O runtime de deployment deverá ser baseado em docker – seja em tsuru, OpenShift,
CoreOS ou outros.

2.5 - Evolução de Database Schema: Migrations
Uma fonte comum de dor de cabeça, bugs e custo de implantação ou atualização são
os scripts de migração de schema do banco de dados. Felizmente esse problema já
foi resolvido na comunidade ágil (por exemplo, Ruby/Rails Active Record
Migrations) com o conceito de migrations. No caso de Python/Django, por exemplo,
a ferramenta South. Uma solução independente de linguagem/plataforma é o

Liquibase. A Shopify.com, por exemplo, conta com 140 mil lojas online e é capaz de
executar 75 deploys diários sem downtime para essas lojas graças ao uso do Large
Hadron Migrator.

Ou seja, o projeto a ser entregue pela terceirizada não deve fazer uso de scripts ad-
hoc ou de intervenção manual para upgrade/downgrade de schema. Todo esse
processo de upgrade/downgrade deverá ser feito de forma automática no banco
através do uso desse mecanismo de migrations – o específico da plataforma/banco
usado.

2.6 – Escalabilidade
A programação do sistema não deve impedir a escalabilidade do mesmo na forma de
N instâncias. Ou seja, guardar valores-cache em variáveis estáticas/globais d
alinguagem de programação é inviável, uma vez que podem haver 2,3,4,5,...
máquinas respondendo aos requests de forma round-robin. Storage permanente
e/ou volátil deve ser tratado por servidores dedicados pra isso – PostgreSQL,
Memcache, Redis etc.

O limite do sistema deve ser demonstrável através de ferramentas como tsung ou
locust. Um perfil de teste de build deve ser o stress_test, o qual deverá exercitar
esses testes de carga do deploy DEV segundo um perfil em escada. Exemplo:
• Um request a cada 1000ms por 10 segundos (para warm-up)
• Um request a cada 500ms por 10 segundos
• Um request a cada 250ms por 10 segundos
• Um request a cada 125ms por 10 segundos
• Um request a cada 60ms por 10 segundos
• Um request a cada 30ms por 10 segundos
• Um request a cada 15ms por 10 segundos
• Um request a cada 7ms por 10 segundos

Os relatórios do build para esse teste de carga permitirão prever o “ponto de
quebra” na arquitetura atual para um número N de requests simultâneos. Deve-se
observar também os tempos médios de resposta para os percentis 95% e 99%.
Fatores como auto-escalabilidade do runtime também serão colocados à prova com
essa estratégia.

2.7 – Segurança
Sistemas Web são frequentemente alvo de ataques de hackers. Visando uma
qualidade e preocupação maior com a segurança, o código entregue será colocado
para rodar e será analisado com relação a vulnerabilidades. Mais especificamente,
usaremos a ferramenta Zed Attack Proxy: ZAP [home].