Quatro regras de design de software iOS mais simples

No final dos anos 90, enquanto desenvolvia a Extreme Programming, o famoso desenvolvedor de software Kent Beck criou uma lista de regras para o design simples de software.

De acordo com Kent Beck, um bom design de software:

  • Executa todos os testes
  • Não contém duplicação
  • Expressa a intenção do programador
  • Minimiza o número de classes e métodos

Neste artigo, discutiremos como essas regras podem ser aplicadas ao mundo do desenvolvimento iOS, fornecendo exemplos práticos do iOS e discutindo como podemos nos beneficiar deles.

Executa todos os testes

O design de software nos ajuda a criar um sistema que atue como pretendido. Mas como podemos verificar se um sistema agirá como pretendido inicialmente por seu design? A resposta é criando testes que a validem.

Infelizmente, no universo de desenvolvimento iOS, os testes são evitados na maioria das vezes ... Mas, para criar um software bem projetado, sempre devemos escrever o código Swift com a testabilidade em mente.

Vamos discutir dois princípios que podem tornar a escrita de teste e o design do sistema mais simples. E são princípios de responsabilidade única e injeção de dependência.

Princípio de responsabilidade única (SRP)

O SRP afirma que uma classe deve ter um e apenas um motivo para mudar. O SRP é um dos princípios mais simples e um dos mais difíceis de acertar. Misturar responsabilidades é algo que fazemos naturalmente.

Vamos fornecer um exemplo de código que é realmente difícil de testar e depois refatorá-lo usando SRP. Em seguida, discuta como ele tornou o código testável.

Suponha que atualmente necessitamos apresentar um PaymentViewController do nosso atual controlador de exibição, PaymentViewController deve configurar sua exibição dependendo do preço do produto de pagamento. No nosso caso, o preço é variável, dependendo de alguns eventos do usuário externo.

O código para esta implementação atualmente se parece com o seguinte:

Como podemos testar esse código? O que devemos testar primeiro? O desconto no preço foi calculado corretamente? Como podemos zombar dos eventos de pagamento para testar o desconto?

Escrever testes para essa classe seria complicado, devemos encontrar uma maneira melhor de escrevê-lo. Bem, primeiro vamos abordar o grande problema. Precisamos desembaraçar nossas dependências.

Vemos que temos lógica para carregar nosso produto. Temos eventos de pagamento que qualificam o usuário para um desconto. Temos descontos, um cálculo de desconto e a lista continua.

Então, vamos tentar simplesmente traduzi-los para o código Swift.

Criamos um PaymentManager que gerencia nossa lógica relacionada a pagamentos e separamos o PriceCalculator que é facilmente testável. Além disso, um carregador de dados responsável pela interação da rede ou do banco de dados para carregar nossos produtos.

Também mencionamos que precisamos de uma classe responsável pelo gerenciamento dos descontos. Vamos chamá-lo de CouponManager e também gerenciar cupons de desconto do usuário.

Nosso controlador de visualização de pagamentos pode ter a seguinte aparência:

Podemos escrever agora testes como

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

e muitos outros! Agora, ao criar objetos separados, evitamos duplicação desnecessária e também criamos um código para o qual é fácil escrever testes.

Injeção de dependência

O segundo princípio é injeção de dependência. E vimos nos exemplos acima que já usamos injeção de dependência em nossos inicializadores de objetos.

Existem dois grandes benefícios em injetar nossas dependências como acima. Isso deixa claro quais dependências nossos tipos dependem e nos permite inserir objetos simulados quando queremos testar, em vez dos reais.

Uma boa técnica é criar protocolos para nossos objetos e fornecer implementação concreta pelo objeto real e simulado, como o seguinte:

Agora podemos decidir facilmente qual classe queremos injetar como dependência.

O acoplamento apertado dificulta a gravação de testes. Assim, da mesma forma, quanto mais testes escrevemos, mais usamos princípios como DIP e ferramentas como injeção de dependência, interfaces e abstração para minimizar o acoplamento.

Tornar o código mais testável não apenas elimina nosso medo de quebrá-lo (já que escreveremos o teste que nos fará backup), mas também contribui para escrever um código mais limpo.

Esta parte do artigo estava mais preocupada em como escrever código que será testável do que escrever o teste de unidade real. Se você quiser saber mais sobre como escrever o teste de unidade, confira este artigo em que crio o jogo da vida usando o desenvolvimento orientado a testes.

Não contém duplicação

A duplicação é o principal inimigo de um sistema bem projetado. Representa trabalho adicional, risco adicional, acrescenta complexidade desnecessária.

Nesta seção, discutiremos como podemos usar o padrão de design do modelo para remover a duplicação comum no iOS. Para facilitar a compreensão, refatoraremos a implementação de um bate-papo da vida real.

Suponha que atualmente tenhamos em nosso aplicativo uma seção de bate-papo padrão. Um novo requisito surge e agora queremos implementar um novo tipo de bate-papo - um bate-papo ao vivo. Um bate-papo que deve conter mensagens com um número máximo de 20 caracteres e esse bate-papo desaparecerá quando descartarmos a exibição do bate-papo.

Este bate-papo terá as mesmas visualizações do bate-papo atual, mas terá algumas regras diferentes:

  1. A solicitação de rede para o envio de mensagens de bate-papo será diferente.

2. As mensagens de bate-papo devem ser curtas, com no máximo 20 caracteres para a mensagem.

3. As mensagens de bate-papo não devem persistir em nosso banco de dados local.

Suponha que estamos usando a arquitetura MVP e atualmente lidamos com a lógica para enviar mensagens de bate-papo em nosso apresentador. Vamos tentar adicionar novas regras ao nosso novo tipo de bate-papo chamado live-chat.

Uma implementação ingênua seria a seguinte:

Mas o que acontece se, no futuro, teremos muito mais tipos de bate-papo?
Se continuarmos adicionando, caso contrário, que verifique o estado do nosso bate-papo em todas as funções, o código ficará confuso e difícil de ler e manter. Além disso, é dificilmente testável e a verificação de estado seria duplicada em todo o escopo do apresentador.

É aqui que o Padrão do Modelo entra em uso. O Modelo Padrão é usado quando precisamos de várias implementações de um algoritmo. O modelo é definido e construído com mais variações. Use este método quando a maioria das subclasses precisar implementar o mesmo comportamento.

Podemos criar um protocolo para o Chat Presenter e separamos métodos que serão implementados de maneira diferente por objetos concretos nas fases do Chat Presenter.

Agora podemos fazer com que nosso apresentador esteja em conformidade com o IChatPresenter

Nosso apresentador agora lida com o envio de mensagens chamando funções comuns dentro de si e delega as funções que podem ser implementadas de maneira diferente.

Agora podemos fornecer objetos de criação que estejam em conformidade com as fases do apresentador e configurar essas funções com base em suas necessidades.

Se usarmos injeção de dependência em nosso controlador de exibição, agora podemos reutilizar o mesmo controlador de exibição em dois casos diferentes.

Usando Design Patterns, podemos realmente simplificar nosso código iOS. Se você quiser saber mais sobre isso, o artigo a seguir fornece mais explicações.

Expressivo

A maior parte do custo de um projeto de software está em manutenção a longo prazo. Escrever código de fácil leitura e manutenção é essencial para desenvolvedores de software.

Podemos oferecer código mais expressivo usando bom teste de Nomeação, Uso de SRP e Gravação.

Nomeação

A primeira coisa que torna o código mais expressivo - e está nomeando. É importante escrever nomes que:

  • Revelar a intenção
  • Evite desinformação
  • São facilmente pesquisáveis

Quando se trata de nomear classes e funções, um bom truque é usar um substantivo ou frase substantivo para classes e verbos do usuário ou nomes de frases verbais para métodos.

Além disso, ao usar padrões de design diferentes, às vezes é bom acrescentar os nomes dos padrões, como Comando ou Visitante, no nome da classe. Portanto, o leitor saberia imediatamente qual padrão é usado lá, sem a necessidade de ler todo o código para descobrir isso.

Usando SRP

Outra coisa que torna o código expressivo é o uso do Princípio de Responsabilidade Única mencionado acima. Você pode se expressar mantendo suas funções e classes pequenas e com um único objetivo. Classes e funções pequenas geralmente são fáceis de nomear, fáceis de escrever e fáceis de entender. Uma função deve servir apenas para um propósito.

Teste de escrita

Escrever testes também traz muita clareza, especialmente ao trabalhar com código legado. Testes de unidade bem escritos também são expressivos. Um objetivo principal dos testes é atuar como documentação por exemplo. Alguém que esteja lendo nossos testes deve conseguir entender rapidamente o que é uma aula.

Minimize o número de classes e métodos

As funções de uma classe devem permanecer curtas, uma função deve sempre executar apenas uma coisa. Se uma função tiver muitas linhas, é possível que esteja executando ações que podem ser separadas em duas ou mais funções separadas.

Uma boa abordagem é contar linhas físicas e tentar apontar para um máximo de quatro a seis linhas de funções; na maioria dos casos, qualquer coisa que ultrapasse esse número de linhas, pode ser difícil ler e manter.

Uma boa idéia no iOS é cortar as chamadas de configuração que geralmente fazemos nas funções viewDidLoad ou viewDidAppear.

Dessa maneira, cada uma das funções seria pequena e sustentável, em vez de uma função mess viewDidLoad. O mesmo deve se aplicar ao delegado do aplicativo. Devemos evitar lançar cada configuração do método ondidFinishLaunchingWithOptions e separar funções de configuração ou classes de configuração ainda melhores.

Com as funções, é um pouco mais fácil medir se o mantemos longo ou curto; na maioria das vezes, podemos contar apenas com a contagem das linhas físicas. Nas aulas, usamos uma medida diferente. Contamos responsabilidades. Se uma classe possui apenas cinco métodos, isso não significa que a classe é pequena; pode ser que ela tenha muitas responsabilidades apenas com esses métodos.

Um problema conhecido no iOS é o tamanho grande dos UIViewControllers. É verdade que, pelo design do controlador do Apple View, é difícil manter esses objetos para servir a um único propósito, mas devemos tentar o nosso melhor.

Existem várias maneiras de diminuir o tamanho do UIViewControllers. Minha preferência é usar uma arquitetura que tenha uma melhor separação de preocupações, como VIPER ou MVP, mas isso não significa que não possamos melhorar o MVC da Apple também.

Ao tentar separar tantas preocupações, podemos alcançar um código bastante decente com qualquer arquitetura. A idéia é criar classes de propósito único que possam servir como auxiliares dos controladores de exibição e tornar o código mais legível e testável.

Algumas coisas que podem ser simplesmente evitadas sem desculpa nos controladores de exibição são:

  • Em vez de escrever o código de rede diretamente, deve haver um NetworkManager, uma classe responsável pelas chamadas de rede
  • Em vez de manipular dados nos controladores de exibição, podemos simplesmente criar um DataManager, uma classe que é responsável por isso.
  • Em vez de brincar com as seqüências de caracteres UserDefaults no UIViewController, podemos criar uma fachada sobre isso.

Em conclusão

Acredito que devemos compor software a partir de componentes com nomes precisos, simples, pequenos, responsáveis ​​por uma coisa e reutilizáveis.

Neste artigo, discutimos quatro regras para o design simples de Kent Beck e demos exemplos práticos de como podemos implementá-las no ambiente de desenvolvimento iOS.

Se você gostou deste artigo, aplaude para mostrar seu apoio. Siga-me para ver muitos outros artigos que podem levar suas habilidades de desenvolvedor para iOS a um próximo nível.

Se você tiver alguma dúvida ou comentário, sinta-se à vontade para deixar uma nota aqui ou envie um email para arlindaliu.dev@gmail.com.