Código Buggy Rails: Os 10 erros mais comuns que os desenvolvedores Rails cometem
Publicados: 2022-03-11Ruby on Rails (“Rails”) é um framework de código aberto popular, baseado na linguagem de programação Ruby que se esforça para simplificar e agilizar o processo de desenvolvimento de aplicativos da web.
Rails é construído sobre o princípio de convenção sobre configuração. Simplificando, isso significa que, por padrão, o Rails assume que seus desenvolvedores especialistas seguirão as convenções de práticas recomendadas “padrão” (para coisas como nomenclatura, estrutura de código e assim por diante) e, se você fizer isso, as coisas funcionarão para você “auto -magicamente” sem a necessidade de especificar esses detalhes. Embora esse paradigma tenha suas vantagens, também tem suas armadilhas. Mais notavelmente, a “mágica” que acontece nos bastidores da estrutura às vezes pode levar a headfakes, confusão e “o que diabos está acontecendo?” tipos de problemas. Também pode ter ramificações indesejáveis no que diz respeito à segurança e desempenho.
Da mesma forma, embora o Rails seja fácil de usar, também não é difícil de usar indevidamente. Este tutorial analisa 10 problemas comuns do Rails, incluindo como evitá-los e os problemas que eles causam.
Erro comum nº 1: Colocar muita lógica no controlador
Rails é baseado em uma arquitetura MVC. Na comunidade Rails, temos falado sobre fat model, skinny controller há algum tempo, mas várias aplicações Rails recentes que herdei violaram esse princípio. É muito fácil mover a lógica de exibição (que é melhor alojada em um auxiliar), ou lógica de domínio/modelo, para o controlador.
O problema é que o objeto do controlador começará a violar o princípio da responsabilidade única, tornando as alterações futuras na base de código difíceis e propensas a erros. Geralmente, os únicos tipos de lógica que você deve ter em seu controlador são:
- Sessão e manipulação de cookies. Isso também pode incluir autenticação/autorização ou qualquer processamento adicional de cookies que você precise fazer.
- Seleção do modelo. Lógica para encontrar o objeto de modelo correto de acordo com os parâmetros passados da solicitação. Idealmente, isso deve ser uma chamada para um único método find definindo uma variável de instância a ser usada posteriormente para renderizar a resposta.
- Solicitar gerenciamento de parâmetros. Reunindo parâmetros de solicitação e chamando um método de modelo apropriado para persisti-los.
- Renderização/redirecionamento. Renderizando o resultado (html, xml, json, etc.) ou redirecionando, conforme apropriado.
Embora isso ainda ultrapasse os limites do princípio de responsabilidade única, é o mínimo que o framework Rails exige que tenhamos no controlador.
Erro comum nº 2: colocar muita lógica na exibição
O mecanismo de modelagem do Rails pronto para uso, ERB, é uma ótima maneira de criar páginas com conteúdo variável. No entanto, se você não for cuidadoso, em breve poderá acabar com um arquivo grande que é uma mistura de código HTML e Ruby que pode ser difícil de gerenciar e manter. Esta também é uma área que pode levar a muita repetição, levando a violações dos princípios DRY (não se repita).
Isso pode se manifestar de várias maneiras. Uma é o uso excessivo de lógica condicional nas visualizações. Como um exemplo simples, considere um caso em que temos um método current_user
disponível que retorna o usuário conectado no momento. Muitas vezes, haverá estruturas lógicas condicionais como esta em arquivos de visualização:
<h3> Welcome, <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %> </h3>
Uma maneira melhor de lidar com algo assim é garantir que o objeto retornado por current_user
esteja sempre definido, esteja alguém conectado ou não, e que ele responda aos métodos usados na visualização de maneira razoável (às vezes chamado de null objeto). Por exemplo, você pode definir o helper current_user
em app/controllers/application_controller
assim:
require 'ostruct' helper_method :current_user def current_user @current_user ||= User.find session[:user_id] if session[:user_id] if @current_user @current_user else OpenStruct.new(name: 'Guest') end end
Isso permitiria que você substituísse o exemplo de código de exibição anterior por esta linha de código simples:
<h3>Welcome, <%= current_user.name -%></h3>
Algumas práticas recomendadas adicionais do Rails:
- Use layouts de visualização e parciais adequadamente para encapsular coisas que são repetidas em suas páginas.
- Use apresentadores/decoradores como a gema Draper para encapsular a lógica de construção de visão em um objeto Ruby. Você pode adicionar métodos a esse objeto para executar operações lógicas que, de outra forma, você poderia ter colocado em seu código de exibição.
Erro comum nº 3: Colocar muita lógica no modelo
Dada a orientação para minimizar a lógica em views e controllers, o único lugar que resta em uma arquitetura MVC para colocar toda essa lógica seria nos models, certo?
Bem, não exatamente.
Muitos desenvolvedores Rails realmente cometem esse erro e acabam colocando tudo em suas classes de modelo ActiveRecord
levando a arquivos mongo que não apenas violam o princípio de responsabilidade única, mas também são um pesadelo de manutenção.
Funcionalidades como gerar notificações por e-mail, fazer interface com serviços externos, converter para outros formatos de dados e similares não têm muito a ver com a responsabilidade central de um modelo ActiveRecord
que deve fazer pouco mais do que encontrar e persistir dados em um banco de dados.
Então, se a lógica não deve entrar nas visualizações, e não deve entrar nos controladores, e não deve entrar nos modelos, bem, então, para onde ela deve ir?
Insira objetos Ruby antigos simples (POROs). Com um framework abrangente como o Rails, os desenvolvedores mais novos costumam relutar em criar suas próprias classes fora do framework. No entanto, mover a lógica do modelo para os POROs geralmente é exatamente o que o médico receitou para evitar modelos excessivamente complexos. Com POROs, você pode encapsular coisas como notificações por e-mail ou interações de API em suas próprias classes, em vez de colocá-las em um modelo ActiveRecord
.
Então, com isso em mente, de um modo geral, a única lógica que deve permanecer no seu modelo é:
- Configuração
ActiveRecord
(ou seja, relações e validações) - Métodos de mutação simples para encapsular a atualização de alguns atributos e salvá-los no banco de dados
- Acessar wrappers para ocultar informações do modelo interno (por exemplo, um método
full_name
que combina os camposfirst_name
elast_name
no banco de dados) - Consultas sofisticadas (ou seja, que são mais complexas do que um simples
find
); de um modo geral, você nunca deve usar o métodowhere
, ou qualquer outro método de construção de consulta como ele, fora da própria classe de modelo
Erro comum nº 4: usar classes auxiliares genéricas como depósito de lixo
Este erro é realmente uma espécie de corolário do erro #3 acima. Conforme discutido, o framework Rails enfatiza os componentes nomeados (ou seja, modelo, visão e controlador) de um framework MVC. Existem definições bastante boas dos tipos de coisas que pertencem às classes de cada um desses componentes, mas às vezes podemos precisar de métodos que parecem não se encaixar em nenhum dos três.
Geradores Rails convenientemente constroem um diretório auxiliar e uma nova classe auxiliar para acompanhar cada novo recurso que criamos. Torna-se muito tentador, no entanto, começar a colocar qualquer funcionalidade que não se encaixe formalmente no modelo, visão ou controlador nessas classes auxiliares.
Embora o Rails seja certamente centrado em MVC, nada impede que você crie seus próprios tipos de classes e adicione diretórios apropriados para armazenar o código dessas classes. Quando você tiver funcionalidades adicionais, pense em quais métodos se agrupam e encontre bons nomes para as classes que contêm esses métodos. Usar uma estrutura abrangente como Rails não é desculpa para deixar boas práticas de design orientado a objetos de lado.
Erro comum nº 5: usar muitas gemas
Ruby e Rails são suportados por um rico ecossistema de gems que coletivamente fornecem praticamente qualquer capacidade que um desenvolvedor possa imaginar. Isso é ótimo para criar um aplicativo complexo rapidamente, mas também vi muitos aplicativos inchados em que o número de gems no Gemfile
do aplicativo é desproporcionalmente grande quando comparado à funcionalidade fornecida.
Isso causa vários problemas no Rails. O uso excessivo de gems torna o tamanho de um processo Rails maior do que o necessário. Isso pode diminuir o desempenho na produção. Além da frustração do usuário, isso também pode resultar na necessidade de configurações maiores de memória do servidor e aumento dos custos operacionais. Também leva mais tempo para iniciar aplicativos Rails maiores, o que torna o desenvolvimento mais lento e faz com que os testes automatizados demorem mais (e como regra, testes lentos simplesmente não são executados com tanta frequência).
Tenha em mente que cada gem que você traz para seu aplicativo pode, por sua vez, ter dependências de outras gems, e essas, por sua vez, podem ter dependências de outras gems e assim por diante. Adicionar outras gemas pode, portanto, ter um efeito de composição. Por exemplo, adicionar a gem rails_admin
trará mais 11 gems no total, um aumento de 10% em relação à instalação básica do Rails.
Até o momento, uma nova instalação do Rails 4.1.0 inclui 43 gems no arquivo Gemfile.lock
. Isso é obviamente mais do que está incluído no Gemfile
e representa todas as gems que o punhado de gems padrão do Rails trazem como dependências.
Considere cuidadosamente se a sobrecarga extra vale a pena à medida que você adiciona cada gema. Como exemplo, os desenvolvedores geralmente adicionam casualmente a gem rails_admin
porque ela essencialmente fornece um bom front-end da web para a estrutura do modelo, mas na verdade não é muito mais do que uma ferramenta de navegação de banco de dados sofisticada. Mesmo que seu aplicativo exija usuários administradores com privilégios adicionais, você provavelmente não deseja dar a eles acesso bruto ao banco de dados e seria mais bem servido desenvolvendo sua própria função de administração mais simplificada do que adicionando essa jóia.
Erro comum nº 6: Ignorando seus arquivos de log
Embora a maioria dos desenvolvedores Rails esteja ciente dos arquivos de log padrão disponíveis durante o desenvolvimento e na produção, eles geralmente não prestam atenção suficiente às informações nesses arquivos. Embora muitos aplicativos dependam de ferramentas de monitoramento de log como Honeybadger ou New Relic em produção, também é importante ficar de olho em seus arquivos de log durante todo o processo de desenvolvimento e teste de seu aplicativo.

Como mencionado anteriormente neste tutorial, o framework Rails faz muita “mágica” para você, especialmente nos modelos. Definir associações em seus modelos torna muito fácil puxar relações e ter tudo disponível para suas visualizações. Todo o SQL necessário para preencher seus objetos de modelo é gerado para você. Isso é ótimo. Mas como você sabe que o SQL que está sendo gerado é eficiente?
Um exemplo que você encontrará com frequência é chamado de problema de consulta N+1. Embora o problema seja bem compreendido, a única maneira real de observá-lo acontecendo é revisar as consultas SQL em seus arquivos de log.
Digamos, por exemplo, que você tenha a seguinte consulta em um aplicativo de blog típico, onde você exibirá todos os comentários de um conjunto selecionado de postagens:
def comments_for_top_three_posts posts = Post.limit(3) posts.flat_map do |post| post.comments.to_a end end
Quando olharmos para o arquivo de log de uma solicitação que chama esse método, veremos algo como o seguinte, onde uma única consulta é feita para obter os três objetos post e mais três consultas são feitas para obter os comentários de cada um desses objetos:
Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700 Processing by PostsController#some_comments as HTML Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (5.6ms) ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]] Comment Load (1.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 3]] Rendered posts/some_comments.html.erb within layouts/application (12.5ms) Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)
A capacidade de carregamento antecipado do ActiveRecord
no Rails torna possível reduzir significativamente o número de consultas, permitindo que você especifique antecipadamente todas as associações que serão carregadas. Isso é feito chamando o método includes
(ou preload
) no objeto Arel ( ActiveRecord::Relation
) que está sendo construído. Com includes
, o ActiveRecord
garante que todas as associações especificadas sejam carregadas usando o número mínimo possível de consultas; por exemplo:
def comments_for_top_three_posts posts = Post.includes(:comments).limit(3) posts.flat_map do |post| post.comments.to_a end end
Quando o código revisado acima é executado, vemos no arquivo de log que todos os comentários foram coletados em uma única consulta em vez de três:
Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700 Processing by PostsController#some_comments as HTML Post Load (0.5ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (4.4ms) SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3) Rendered posts/some_comments.html.erb within layouts/application (12.2ms) Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)
Muito mais eficiente.
Esta solução para o problema N+1 é realmente apenas um exemplo do tipo de ineficiência que pode existir “por baixo do capô” em seu aplicativo se você não estiver prestando a devida atenção. A conclusão aqui é que você deve verificar seus arquivos de log de desenvolvimento e teste durante o desenvolvimento para verificar (e resolver!) ineficiências no código que cria suas respostas.
A revisão de arquivos de log é uma ótima maneira de ser avisado sobre ineficiências em seu código e corrigi-los antes que seu aplicativo entre em produção. Caso contrário, você pode não estar ciente de um problema de desempenho do Rails resultante até que seu sistema entre em operação, já que o conjunto de dados com o qual você trabalha no desenvolvimento e teste provavelmente será muito menor do que na produção. Se você estiver trabalhando em um novo aplicativo, até mesmo seu conjunto de dados de produção pode começar pequeno e seu aplicativo parecerá estar funcionando bem. No entanto, à medida que seu conjunto de dados de produção cresce, problemas do Rails como esse farão com que seu aplicativo seja executado cada vez mais devagar.
Se você achar que seus arquivos de log estão entupidos com um monte de informações que você não precisa, aqui estão algumas coisas que você pode fazer para limpá-los (as técnicas lá funcionam tanto para o desenvolvimento quanto para os logs de produção).
Erro comum nº 7: falta de testes automatizados
Ruby e Rails fornecem recursos de teste automatizados poderosos por padrão. Muitos desenvolvedores Rails escrevem testes muito sofisticados usando estilos TDD e BDD e fazem uso de frameworks de teste ainda mais poderosos com gems como rspec e pepino.
Apesar de quão fácil é adicionar testes automatizados ao seu aplicativo Rails, porém, fiquei muito desagradavelmente surpreso com quantos projetos eu herdei ou entrei onde não havia literalmente nenhum teste escrito (ou na melhor das hipóteses, muito poucos) pelo anterior equipe de desenvolvimento. Embora haja muito debate sobre o quão abrangente seu teste deve ser, é bastante claro que pelo menos alguns testes automatizados devem existir para cada aplicativo.
Como regra geral, deve haver pelo menos um teste de integração de alto nível escrito para cada ação em seus controladores. Em algum momento no futuro, outros desenvolvedores Rails provavelmente desejarão estender ou modificar o código, ou atualizar uma versão Ruby ou Rails, e essa estrutura de teste fornecerá a eles uma maneira clara de verificar se a funcionalidade básica do aplicativo é trabalhando. Um benefício adicional dessa abordagem é que ela fornece aos futuros desenvolvedores um delineamento claro da coleção completa de funcionalidades fornecidas pelo aplicativo.
Erro comum nº 8: Bloqueio de chamadas para serviços externos
Provedores terceirizados de serviços Rails geralmente facilitam muito a integração de seus serviços em seu aplicativo por meio de gems que envolvem suas APIs. Mas o que acontece se o seu serviço externo tiver uma interrupção ou começar a funcionar muito lentamente?
Para evitar o bloqueio dessas chamadas, em vez de chamar esses serviços diretamente em seu aplicativo Rails durante o processamento normal de uma solicitação, você deve movê-los para algum tipo de serviço de enfileiramento de tarefas em segundo plano sempre que possível. Algumas gems populares usadas em aplicações Rails para esta finalidade incluem:
- Trabalho atrasado
- Resque
- Sidekiq
Nos casos em que for impraticável ou inviável delegar o processamento a uma fila de trabalhos em segundo plano, você precisará garantir que seu aplicativo tenha tratamento de erros e provisões de failover suficientes para as situações inevitáveis quando o serviço externo ficar inativo ou estiver com problemas . Você também deve testar seu aplicativo sem o serviço externo (talvez removendo da rede o servidor em que seu aplicativo está) para verificar se isso não resulta em consequências imprevistas.
Erro comum nº 9: casar com migrações de banco de dados existentes
O mecanismo de migração de banco de dados do Rails permite que você crie instruções para adicionar e remover automaticamente tabelas e linhas de banco de dados. Como os arquivos que contêm essas migrações são nomeados de maneira sequencial, você pode reproduzi-los desde o início para trazer um banco de dados vazio para o mesmo esquema da produção. Essa é, portanto, uma ótima maneira de gerenciar alterações granulares no esquema de banco de dados de sua aplicação e evitar problemas com Rails.
Embora isso certamente funcione bem no início do seu projeto, com o passar do tempo, o processo de criação do banco de dados pode demorar um pouco e às vezes as migrações são mal colocadas, inseridas fora de ordem ou introduzidas de outros aplicativos Rails usando o mesmo servidor de banco de dados.
O Rails cria uma representação do seu esquema atual em um arquivo chamado db/schema.rb
(por padrão) que geralmente é atualizado quando as migrações de banco de dados são executadas. O arquivo schema.rb
pode até mesmo ser gerado quando nenhuma migração estiver presente executando a tarefa rake db:schema:dump
. Um erro comum do Rails é verificar uma nova migração em seu repositório de origem, mas não o arquivo schema.rb
atualizado correspondentemente.
Quando as migrações saem do controle e demoram muito para serem executadas, ou não criam mais o banco de dados corretamente, os desenvolvedores não devem ter medo de limpar o diretório de migrações antigo, despejar um novo esquema e continuar a partir daí. A configuração de um novo ambiente de desenvolvimento exigiria um rake db:schema:load
em vez do rake db:migrate
no qual a maioria dos desenvolvedores confia.
Algumas dessas questões também são discutidas no Rails Guide.
Erro comum nº 10: Verificando informações confidenciais em repositórios de código-fonte
A estrutura do Rails facilita a criação de aplicativos seguros, imunes a muitos tipos de ataques. Parte disso é feito usando um token secreto para proteger uma sessão com um navegador. Embora esse token agora esteja armazenado em config/secrets.yml
e esse arquivo leia o token de uma variável de ambiente para servidores de produção, as versões anteriores do Rails incluíam o token em config/initializers/secret_token.rb
. Esse arquivo muitas vezes é verificado erroneamente no repositório de código-fonte com o restante do seu aplicativo e, quando isso acontece, qualquer pessoa com acesso ao repositório pode comprometer facilmente todos os usuários do seu aplicativo .
Você deve, portanto, certificar-se de que o arquivo de configuração do seu repositório (por exemplo, .gitignore
para usuários do git) exclua o arquivo com seu token. Seus servidores de produção podem então pegar seu token de uma variável de ambiente ou de um mecanismo como o que a gem dotenv fornece.
Conclusão do tutorial
Rails é um framework poderoso que esconde muitos detalhes feios necessários para construir uma aplicação web robusta. Embora isso torne o desenvolvimento de aplicativos web Rails muito mais rápido, os desenvolvedores devem prestar atenção aos possíveis erros de design e codificação, para garantir que seus aplicativos sejam facilmente extensíveis e de fácil manutenção à medida que crescem.
Os desenvolvedores também precisam estar cientes dos problemas que podem tornar seus aplicativos mais lentos, menos confiáveis e menos seguros. É importante estudar a estrutura e certificar-se de que você entende completamente as compensações de arquitetura, design e codificação que você está fazendo ao longo do processo de desenvolvimento, para ajudar a garantir um aplicativo de alta qualidade e alto desempenho.