en pt

Tag: csharp

Value Objects: Uma Técnica Para Código Auto-Documentado E Menos Erros

NOTA: Eu escrevi este post originalmente para o blog da NDepend. Você pode conferir o artigo original, em inglês, no site deles. Aproveite que está lá e faça o download do NDepend.

Você já ouviu falar de value objects? Eu imagino que sim. Apesar de não se falar tanto a respeito deles como eu gostaria, eles ainda são mencionados o bastante para que muitos desenvolvedores tenham no mínimo alguma familiaridade com o termo.

Porém, “alguma familiaridade” não é bom o bastante. Então é isso que vamos consertar com esse post. Hoje vamos aprender o que value objects são e como você, por meio do C#, pode usar todo o poder deles para tornar seu código mais claro, auto-documentável e menos propenso a erros.

O que são Value Objects?

Value objects são um dos blocos fundamentais do DDD (Domain Driven Design, ou Projeto Guiado por Domínio, em tradução livre), conforme proposto por Eric Evans em seu livro Domain-Driven Design: Tackling Complexity in the Heart of Software.

Da maneira mais simples possível, um value object é um objeto que representa um valor. E sim, eu estou ciente de que isso soa óbvio e tedioso quando dito dessa forma. Então, por que tanto barulho por causa desse conceito?

Algumas Propriedades

Eu acho que é mais fácil de entender value objects se eu parar de tentar explicar o que eles são e, ao invés disso, falar sobre as suas características.

Value Objects Não Têm Identidade

Eu acho que é justo dizer que a principal característica dos value objects é que eles não possuem identidade. Mas o que isso realmente quer dizer na prática?

Digamos que você vá até o caixa eletrônico mais próximo e deposite uma nota de 50 reais em sua conta. Então você dirige umas duas horas até outra cidade, entra em uma agência bancária, e saca 50 reais.

Pergunta: faz alguma diferença para você o fato de que a nota que você tem em mãos não é a mesma que você depositou antes? É claro que não! Quando se trata de dinheiro o que as pessoas geralmente se importam é com seu valor, não com o veículo daquele valor.

Em outras palavras, nós não damos a mínima para a identidade daquela cédula em particular. A única coisa com a qual nós nos importamos é o seu valor.

Não é coincidência o fato de que dinheiro é um exemplo clássico de value object na literatura.

Value Objects São Imutáveis

Você consegue mudar o número cinco? Não. Não há nada que você (ou qualquer outra pessoa) possa fazer para mudar o valor do número 5. Se você adicionar 1, ele não muda. Você tem agora 6, que é um outro número.

É possível alterar uma data? Não, também não dá para fazer isso. Se você inicia com “2018-01-09” e adiciona um dia, você chega em “2018-01-10.” O valor original não é alterado de forma alguma. Na verdade, a imutabilidade de um value object é uma consequência direta do ponto anterior; como um value object não possui identidade, podemos dizer que o value object é o seu valor. Portanto, nem sequer faz sentido falar sobre mudá-lo.

A implicação disso no desenvolvimento é que value objects são intrinsecamente mais seguros e mais fáceis de serem compreendidos. Não tem perigo de mudar por acidente aquilo que você não pode mudar de jeito nenhum.

Value Objects Têm Igualdade Estrutural

Imagine que você pudesse magicamente transportar pessoas para qualquer lugar que você quisesse e, por alguma razão bizarra, você tenha decidido trocar dois homens chamados “João da Silva” durante a noite. Como você acha que as suas respectivas famílias reagiriam ao encontrar um estranho em casa na manhã seguinte?

Obviamente, pessoas não são intercambiáveis, mesmo que compartilhem uma ou mais características. Mesmo se nossos dois “Joães” (Joões?) tivessem não apenas o mesmo nome, mas também a mesma altura, peso, cor de pele e cabelo, eles ainda seriam duas pessoas completamente diferentes. Mesmo gêmeos idênticos (ou clones, caso você esteja numa onda meio Black Mirror) continuam sendo pessoas completamente diferentes, apesar de serem tão iguais um ao outro quanto é possível ser.

Por outro lado, as pessoas mudam constantemente durante a vida, mas continuam sendo as mesmas pessoas. Pelo menos (enquanto a gente não resolver filosofar com “um homem não entra no mesmo rio duas vezes” e coisas do tipo).

Você talvez esteja se perguntando se eu divaguei demais aqui, mas é tudo de propósito. Tudo isso serve apenas para ilustrar a diferença crucial entre entidades e value objects. Com as entidades, nós nos importamos com a sua identidade, não com o valor de seus atributos. Com value objects, é exatamente o oposto.

A implicação disso, em termos de programação, é que value objects tipicamente apresentam igualidade estrutural. Faz sentido compará-los pelos seus valores, não suas referências ou identidades. Então, quando for implementar um value object, sempre faça override dos métodos Equals e GetHashCode.

O que Você Ganha Com Isso?

A essa altura você já deve ter uma boa ideia do que value objects são. O que ainda não está claro é: por que você deveria usá-los? Para responder isso, vamos dar uma olhada na linha de código a seguir:

    double  distance  =  4.5;

Tem algo errado com a linha acima? Bom, eu poderia dar uma de Ben Kenobi e dizer que está errada “de um certo ponto de vista.” Mas eu não vou. Ao invés disso, vou dizer que está definitivamente, sem sombra de dúvidas, errada. Não importa que compila corretamente. Também não importa que funciona um pouco (ou até na maioria) do tempo.

O problema aqui é o code smell conhecido como “obsessão primitiva”, isto é, a modelagem de conceitos de domínio usando tipos primitivos. As próximas seções vão explicar melhor porque isso é um problema tão sério e como o uso de value objects podem ajudar.

Value Objects Proporcionam Contexto

Por que obsessão primitiva é algo ruim? Há várias razões, mas um dos principais problemas com a linha de código que você viu na seção anterior é que há uma informação extremamente importante faltando. Como você pode ver, o código atribui o valor 4.5 à variável. Mas 4.5 o que? Metros? Quilômetros? Milhas? Parsecs? Em outras palavras, falta uma unidade de medida.

Isso pode ser uma receita para o desastre. Tudo o que você precisa é alguém recuperar um valor do banco de dados ou de um arquivo, pensando que representa metros mas na verdade são quilômetros. Quando a pessoa resolve usar o valor em um cálculo, adicionando quilômetros com metros…silêncio. Você agora tem um programa que, no lugar de falhar rápido como deveria, se comporta de maneira errada silenciosamente enquanto corrompe dados e gera resultados inconsistentes. Ainda bem que você usa testes unitários…certo?

Tudo bem, nada impede você de colocar essa informação no próprio nome da variável:

	double  distanceInKilometers  =  4.5;

Certo, isso é um pouco melhor do que a versão anterior, mas ainda é uma solução muito frágil. A qualquer momento o valor pode ser atribuído a outra variável ou passado como argumento para uma função, e aí a unidade de medida é perdida.

Usando value objects, você pode eliminar esse problema facilmente. Você teria apenas que escolher uma unidade para ser a representação interna do tipo - para distância, faz sentido usar o metro, por ser uma unidade do Sistema Internacional de Medidas. E aí você pode implementar diversos métodos estáticos para fabricar valores para cada unidade necessária:

	var  distance  =  Distance.FromMeters(4000);
	var  distance2  =  Distance.FromKilometers(4);
	Assert.AreEqual(distance,  distance2);

Você poderia ainda sobrecarregar o operador “+” (ou criar um método Plus). Dessa forma seria possível somar duas distâncias que se originaram de diferentes unidades de medida já que a representação interna é a mesma.

Value Objects Apresentam Segurança de Tipo

Digamos que você tem um método com a seguinte assinatura:

	double  PerformSomeImportantCalculation(double  distance,  double  temperature);

O que aconteceria se você cometesse um erro e invertesse os valores ao chamar o método? O programa iria silenciosamente se comportar erro, e você nem ficaria sabendo. Com sorte, a sua empresa teria um bom processo de garantia de qualidade que poderia pegar esse erro antes de chegar no ambiente de produção. Mas ficar dependendo de sorte não é lá uma grande estratégia, concorda?

Pois acontece que esse é o tipo de problema que value objects são ótimos em evitar. Você teria apenas que usar topos customizados para cada conceito em vez de tipos primitivos:

	double  PerformSomeImportantCalculation(Distance distance,  Temperature temperature);

Dessa forma, se torna impossível passar os parâmetros na ordem errada: o compilador simplesmente não deixa!

Value Objects Evitam Duplicação de Lógica de Domínio

Quando você usa tipos primitivos para modelar conceitos de domínio, a tendência é você ter um monte de código relacionado a esses conceitos espalhados por toda a aplicação. Imagine que você está criando um programa que tem o conceito de uma placa de carro, e você está usando strings para representá-las. É claro que nem todas as strings válidas são placas válidas. Então o seu código acaba entupido de validações de placas de carro por todo lugar.

Isso seria evitado criando uma classe chamada LicensePlate e fazendo todas as validações necessárias em seu construtor. Dessa forma você consolidaria todo o código de validação em apenas um lugar; se esse código precisar mudar no futuro, você tem que mudar em apenas um lugar.

Value Objects e Tipos de Valor Não São Sinônimos

Essa seção é necessária para clarificar um erro comum, que é confundir value objects com o conceito de value types (tipos de valor) que existe no C#. É assim: na linguagem C# nós temos duas categorias de tipos: tipos de referência e tipos de valor.

Sim, não há nada que impeça você de usar structs (tipos de valor) para implementar value objects - exemplos na Base Class Library (BCL) seriam o tipo DateTime ou os tipos numéricos primitivos. Mas você também pode usar classes tranquilamente.

Por outro lado, structs não podem ser considerados value objects automaticamente. Por exemplo, embora manter structs imutáveis seja uma prática recomendada, eles não são imutáveis por padrão.

Resumidamente, “tipo de valor” é um detalhe de implementação em C#/.NET e “value object” é um padrão de projeto. Mantenha isso em mente e consulte as diretrizes de design da Microsoft e tudo vai ficar bem.

Value Objects Valem a Pena!

O uso de value objects é uma técnica com um custo relativamente baixo que pode aumentar bastante a manutenibilidade e claridade do seu código. Colocando value objects em prática, você pode tornar seu código mais fácil de ser compreendido, criando interfaces que são auto-documentáveis, difíceis de serem usadas da maneira errada, e intrinsecamente type-safe.

leia mais...

Boas Práticas De Programação Para Os Apressados

Photo by Ales Nesetril on Unsplash

NOTA: Este post foi originalmente escrito para o blog da the SubMain. Você pode ler o artigo original no site deles, em inglês. Quando estiver por lá, baixe e experimente o CodeIt.Right.

Um dos tópicos em desenvolvimento de software que me interessa muito são boas práticas de codificação. Eu estou sempre pesquisando e buscando maneiras de aperfeiçoar meu trabalho e entregar valor de forma rápida e consistente.

Pode ser meio espinhoso definir o que “é realmente uma boa prática”. Há pessoas que inclusive sugerem aposentar o termo! Mas um ponto em que praticamente todos concordam é: descobrir e implementar estratégias - não importa o nome que você coloca nelas - para melhorar o resultado do seu trabalho é algo que qualquer programadora ou programador que faz jus a esse nome deveria fazer continuamente.

Claro, não existe almoço grátis. A adoção de uma boa prática leva tempo, o que você provavelmente não tem muito sobrando para começo de conversa. Isso sem mencionar a gerência, nem sempre muito animados a tentarem coisas novas.

Então, o que fazer se a sua equipe de desenvolvimento está sofrendo com a baixa qualidade de uma base de código, mas não tem tempo para implementar as boas práticas que remediariam a situação?

A resposta que eu ofereço é o que eu vou chamar de “pacote emergencial de boas práticas”: uma pequena lista de boas práticas de programação que você pode adotar em relativamente pouco tempo para levar sua equipe e sua aplicação do completo caos para um estado mais gerenciável.

Sim, eu sei que há tantos conselhos sobre boas práticas por aí que é até difícil não se sentir sobrecarregado. Por causa disso, eu restringi a minha lista de boas práticas a itens que atendam aos seguintes critérios:

  • As boas práticas precisam ser fundamentais, no sentido de que elas são blocos básicos a partir dos quais você pode implementar práticas mais sofisticadas depois.
  • Você pode adotá-las em relativamente pouco tempo. (Eu diria que uma semana é praticável.)
  • O seu custo é zero ou perto disso.

As práticas a seguir atendem os critérios listados. E sem mais enrolação, aqui está: meu pacote emergencial de boas práticas de codificação, com itens listados na ordem que eles deveriam ser adotados, começando pelo mais crítico.

Sistema de Controle de Versão

Eu trabalhei uma vez em uma empresa de desenvolvimento de software na qual nenhum sistema de controle de versão era usado. Os arquivos de código fonte ficavam em uma pasta compartilhada que qualquer desenvolvedor podia acessar. Qual era o processo usado para poder editar um arquivo? Você provavelmente adivinhou: nós criávamos uma cópia do arquivo e adicionávamos “_OLD” ao final do nome.

Isso aconteceu há oito ou nove anos, o que significa que as coisas devem ter melhorado, certo? Bom, provavelmente melhoraram, um pouco, mas não totalmente. Ainda tem empresas por aí que não usam controle de versão.

Como proceder?

De agora em diante, eu vou assumir que você concorda que versionamento é uma boa prática fundamental. Caso esse não seja o caso, há muitos recursos pela web afora explicando o que um versionador é e porque você deveria usar um.

Com isso resolvido, é hora de sermos mais específicos. Qual ferramenta usar? Como proceder com a sua adoção?

Git é uma escolha sólida. E apesar de ter uma curva de aprendizado mais acentuada paraquem já está mais acostumado com sistemas de controle de versão centralizados, como Subversion ou TFVC, Git é o padrão de facto da indústria. Então, sem sombra de dúvidas, você deve aprender git. Não fazê-lo pode prejudicar a sua carreira no futuro.

Mas é possível que o Git não seja a melhor escolha para o seu time agora. Lembre-se, você não tem muito tempo. Nós precisamos que a sua equipe adote as boas práticas o mais rápido possível.

Como nós podemos fazer isso? Suponha que você tenha experiência com Subversion, pois esse era o versionador usado na empresa que você trabalhou anteriormente. Sua experiência com Git, porém, é nenhuma. Se esse é o caso, eu diria que Subversion é a melhor escolha para você. Ter que aprender um novo sistema e ensiná-lo para seus colegas ao mesmo tempo que o coloca em vigor na empresa seria demais: você iria apenas se sobrecarregar.

Revisão de Código

Eu não vou mentir: eu sou um grande fã de revisão de código. E eu não estou sozinho nisso.. Eu já testemunhei em primeira mão como um bom processo de revisão de código pode reduzir o número de problemas em uma aplicação, tornar o código mais consistente e, mais importante ainda, espalhar conhecimento por todo o time de desenvolvimento.

E aqui vai uma ótima vantagem dessa prática: revisão de código é algo relativamente fácil de ser implementado. Comece da maneira mais simples possível, e então faça adaptações na sua abordagem conforme as necessidades aparecerem.

Minha Definição de Revisão de Código

Falar de revisão de código pode ser complicado. As pessoas às vezes tem ideias totalmente diferentes sobre o que a expressão significa. Então eu acho que uma clarificação se faz necessária.

Eu não sou a favor de um processo de revisão de código altamente burocrático e estressante, no qual o seu código é esmiuçado, em público, durante horas. Eu não acredito que envergonhar as pessoas em público é uma forma eficaz de aumentar a qualidade de um projeto. Ao contrário, o tipo de revisão de código que eu defendo é um processo simples, geralmente iniciado ao submeter um pull request ou usando sua IDE favorita.

Como Proceder

Agora que nós estamos sintonizados em relação ao significado de “revisão de código”, a próxima pergunta é: “como implementar isso na prática?” Da maneira mais simples possível que funcione.

Por exemplo, se a sua empresa desenvolve em .NET e usa TFS/TFVC, você pode começar instalando uma política de check-in que exige uma revisão de código para cada check-in. Se a sua equipe usa GitHub, vocês podem usar pull requests. Apenas comece a realizar revisões de código. Então, com o tempo, vá fazendo os ajustes e adaptações necessários.

Estas são algumas das questões que podem surgir ao refinar o seu processo:

  • Qual é o objetivo da revisão de código? Estamos procurando por bugs? Tentando melhorar a legibilidade?Checando se o código adere ao padrão de codificação da empresa?
  • Como separamos “sugestões” de “impedimentos”? É OK recusar a alteração de alguém por causa de uma indentação ruim ou um nome de variável ligeiramente equivocado?
  • O que fazer se revisor e revisado não conseguem chegar a um consenso? Trazer um mediador para dar a palavra final? E quem seria essa pessoa?

A resposta para todas as perguntas acima podem ser encontradas na automação. Uma boa parte do desconforto das revisões de códigos pode ser eliminada quando você emprega um analisador de código para lidar com as partes automatizáveis do processo.

Por exemplo, SubMain possui um produto chamado CodeIt.Right que oferece feedback em tempo real de dentro do Visual Studio, lhe alertando de possíveis problemas de codificação, inclusive corrigindo problemas quando possível.

Ao abraçar a automação, você deixa as pessoas da equipe livres para se preocuparem com questões de mais alto nível durante as revisões, como claridade do código ou decisões arquiteturais.

Builds Automatizados

Talvez você esteja pensando que eu me equivoquei nessa seção. Afinal de contas, sequer faz sentido falar de builds automatizados sem mencionar testes automatizados?

Eu vou argumentar que sim, faz sentido, por uma razão muito simples: builds automatizados eliminam o famoso problema de “na minha máquina funciona”. 

Ter um local central onde os builds são feitos joga luz em vários tipos de problemas, de mal gerenciamento de dependências a falta de disciplina em testes.

Como Proceder

Meu conselho aqui é o mesmo que na seção anterior: faça a coisa mais simples possível que funcione.

Se sua equipe já usa TFS, aprenda como criar uma definição de build. Se os seus projetos estão no GitHub, dê uma olhada no Travis CI.

Com o tempo, você vai melhorando a sua estratégia. Lembra dos analisadores de código que eu mencionei anteriormente? É possível integrá-los no seu processo de build. Testes unitários e outros tipos de testes automatizados também são uma valiosa adição.

E por falar nisso…

Ausências Notáveis

Você talvez tenha se surpreendido por minha lista de boas práticas não incluir testes unitários, mesmo eu sendo um defensor da importância de testes automatizados para a qualidade de uma aplicação. Qual é a razão disso?

Infelizmente, adicionar testes unitários a uma aplicação legada é muito difícil, ao ponto de existir até um livro famoso que foca apenas nisso. Não é uma tarefa fácil de se fazer em pouco tempo.

Também é possível que muitos de vocês esperavam que eu falasse sobre código limpo ou os princípios SOLID. Eu encorajo vocês a lerem e pesquisarem sobre esses tópicos, mas eu não acredito que eles encaixam no propósito do post de hoje. Como o próprio nome deixa claro, eles são princípios. Pense neles como diretrizes filosóficas. Úteis? Claro. Mas não tão fáceis de decompor em conselhos pequenos, simples e acionáveis.

Implemente Essas Práticas Para Ontem!

É possível que vários de vocês tenha achado essas práticas extremamente básicas e não dignas de um post. “Quem é que não usa controle de versão em 2018???”

Bom, não é tão difícil assim encontrar evidência (anedótica, mas ainda assim) que as coisas ainda não são tão perfeitas.

Acreditar que mesmo práticas tão fundamentais como versionamento de código ou testes automatizados são aplicadas universalmente é mais ingenuidade do que talvez queremos admitir.

Para o restante de vocês, eu espero que essa lista seja útil.

Você já deve ter ouvido o ditado. “Quando estiver em um buraco, para de cavar.” E é exatamente esse o tipo de ajuda que eu quis oferecer com esse post: correções rápidas e fáceis, para que você e as demais pessoas em seu tipo possam recuperar o suficiente de sanidade para poderem focar e recuperar o controle de sua aplicação, garantido sua saúde a longo prazo.

leia mais...

4 Erros Comuns Com Data e Hora no C# — E como evitá-los

NOTA: Eu escrevi este post originalmente para o blog da SubMain. Você pode conferir o original no site deles, em inglês. Enquanto estiver por lá, dê uma conferida no CodeIt.Right, uma ferramenta que pode lhe ajudar com problemas relacionados a tempo e muitos outros.

Você se lembra daqueles posts no estilo “inverdades que programadores acreditam sobre X” que ficaram bastante populares em blogs de software há alguns anos? O primeiro foi sobre nomes, mas logo apareceram vários outros, cobrindo tópicos como endereços, geografia e compras online.

O meu favorito era o post sobre tempo. Até esse ponto, eu não havia pensado profundamente sobre tempo e e suas intricacies, e eu fiquei intrigado em saber que um domínio tão fundamental pudesse ser um terreno tão fértil para bugs e confusões.

Agora, mesmo eu tendo gostado do post, eu vejo um problema com ele: o post lista vários suposições erradas, e basicamente para por aí. Quem lê o artigo provavelmente termina se perguntando:

  • Por que essas suposições são falsas?
  • Qual é a probabilidade de eu me dar mal por causa dessas inverdades?
  • Qual é a maneira adequada de lidar com esses problemas?

O artigo é interessante, mas eu acho que faria sentido oferecer informações um pouco mais acionáveis.

E é exatamente esse é o objetivo do post de hoje. Eu vou mostrar 4 erros comuns que as pessoas cometem ao lidar com tempo em C#/.NET. E não para por aí. Eu também vou mostrar o que você deve fazer para evitar esses erros e tornar seu código mais seguro e mais fácil de ser compreendido.

1. Calculando Durações de Maneira Ingênua

Considere o código abaixo:

Ele funciona corretamente? Depende de onde e quando ele será executado.

Quando você usa DateTime.Now, o valor que você obtém representa a data e hora locais em relação à máquina atual (ou seja, a propriedade Kind está configurada para Local).

Se o lugar que você mora observa Horário de Verão, então você sabe que existe um dia do ano no qual você deve adiantar os relógios em uma certa medida (geralmente 1 hora, embora existam lugares que ajustam por outras quantidades). E é claro, existe também um dia no qual o oposto acontece.

Agora imagine o que seguinte: hoje é 12 de março de 2017, e você more na cidade de Nova York. Você começa a usar o programa acima. O método StartMatch() é executado exatamente às 13h. Uma hora e quinze minutos mais tarde, o método EndMatch é executado. O cálculo é realizado e o texto abaixo é exibido:

Duration of the match: 00:02:15

Eu imagino que você compreendeu o que aconteceu aqui: quando os relógios estavam prestes a marcar 14h, o Horário de Verão entrou em efeito, movendo-os diretamente para 15h. Então o método EndMatch recuperou o horário atual, somando uma hora adicional ao cálculo. Se o experimento tivesse acontecido no fim do Horário de Verão, o resultado seria apenas 15 minutos!

Sim, o código mostrado é apenas um exemplo, uma brincadeira. Mas e se fosse algo mais sério? Uma aplicação de folha de pagamento, digamos. Você gostaria de pagar o valor errado a um funcionário?

O que fazer?

Quando precisar calcular a duração de atividades humanas, use UTC para os tempos de início e fim. Dessa forma, você será capaz de referenciar de maneira não ambígua um ponto específico no tempo. Ao invés de usar a propriedade Now, use UtcNow para recuperar a data e hora já em formato UTC para realizar os cálculos:

Mas e se os valores DateTime que você tem já são do tipo Local? Nesse caso, você deve usar o método ToUniversalTime() para convertê-los para UTC:

Uma Rápida Advertência Sobre ToUniversalTime()

O uso do método ToUniversalTime() - e seu irmão, ToLocalTime() - pode ser um pouco chato. O problema é que esses métodos fazem suposições sobre o que você quer baseados no valor da propriedade Kind do objeto datetime que você tem, o que pode trazer resultados inesperados.

Ao chamar ToUniversalTime(), uma das seguintes coisas vai acontecer:

  • Se Kind estiver configurado como UTC, o mesmo valor é retornado.
  • Por outro lado, se estiver configurado como Local, então o valor correspondente em UTC é retornado.
  • Finalmente, se Kind estiver como Unspecified, então é assumido que o objeto sempre teve a intenção de ser local,, e você recebe o valor correspondente à conversão para UTC.

O problema aqui é que valores de data/hora locais não não “transportáveis”. Como assim? Eles são locais enquanto eles permanecerem no contexto da máquina atual. Se você salva um datetime local para um banco de dados e depois o recupera de lá, a informação de que ele é local se perde: agora ele é Unspecified.

Assim, o seguinte cenário pode acontecer:

  • Você recupera a data e hora atuais usando DateTime.UtcNow.
  • Você salva esse valor no banco de dados.
  • Outra parte do código recupera esse valor. Sem estar ciente de que o valor já está em UTC, chama o método ToUniversalTime() na instância.
  • Como o valor recuperado do banco possui o tipo Unspecified, o método vai tratá-lo como local e realizar uma conversão desnecessária, gerando um valor errado.

Como evitar que isso aconteça? Uma prática recomendada é usar UTC para armazenar o tempo em que um evento aconteceu. Minha sugestão é seguir esse conselho e também esse fato bem explícito. Coloque o sufixo “UTC” em cada coluna de tabela no banco de dados e também em nomes de propriedades que se referem a um valor em UTC. Ao invés de “Inclusao”, use “InclusaoUTC” e assim por diante. Não é tão bonito, mas com certeza é mais claro.

2. Não Usar UTC Quando Deveria (e vice-versa)

Nós podemos definir isso como uma regra universal: use UTC para registrar quando eventos aconteceram. Ao logar, auditar, e registrar todo tipo de timestamps na sua aplicação, UTC é a resposta.

Então, é só usar UTC em todo lugar! Certo? Não, não tão rápido.

Digamos que você precisa ser capaz de reconstruir o tempo local - na perspectiva do usuário - de quando algo aconteceu, e a única informação que você tem é um timestamp em UTC. Mal dia.

Em casos assim, faria mais sentido (a) registrar o momento em UTC e gravar também o fuso horário do usuário ou (b) usar o tipo DateTimeOffset, que armazena a data/hora local junto com o deslocamento, ou offset, para UTC, permitindo que você reconstrua o valor em UTC quando precisar.

Outro caso de uso comum para o qual UTC não é a solução correta é o agendamento de eventos locais no futuro. Você não quer que seu alarme acorde você uma hora mais cedo ou uma hora mais tarde nos dias de transição do Horário de Verão, certo? Pois é exatamente isso que aconteceria se você configurasse o seu alarme pelo horário UTC.

3. Não Validar Entrada dos Usuários

Imagine que você criou uma aplicação desktop simples que permite que usuários configurem lembretes. A pessoa informa a data e hora que quer receber o lembrete, clica em um botão, e pronto.

Tudo parece estar funcionando direito até que alguém do Brasil envia um e-mail para você, reclamando que o lembrete que ela configurou para 15 de outubro às 0h15 não funcionou. O que será que aconteceu?

O Horário de Verão Contra Ataca

O vilão aqui é o bom e velho Horário de Verão novamente. Em 2017, o Horário de Verão no Brasil começou à meia-noite do dia 15 de outubro. Então, a combinação de data e hora que a usuária informou simplesmente não existe em seu fuso-horário!

É claro que o problema oposto também é possível. Quando o Horário de Verão chega ao fim e os relógios são atrasados, isso gera horas ambíguas.

Qual É A Solução?

Como lidar com esse tipo de problema no C#? A classe TimeZoneInfo pode lhe salvar. Ela serve para representar um fuso horário e também oferece métodos para verificar se um determinado objeto DateTime é válido:

Mas o que fazer então? O que deveria substituir os comentários “do something” nos trechos acima?

Você poderia mostrar uma mensagem dizendo que a data informada é inválida. Ou você poderia escolher outra data para a pessoa automaticamente.

Vamos abordar o caso das horas inválidas primeiro. Suas opções são: mover para frente ou para trás. É uma decisão meio arbitrária, então qual você deve escolher? Por exemplo, o app do Google Calendar no Android move para frente. E até que faz sentido se você parar pra pensar. Isso é exatamente o que seus relógios fizeram devido ao horário de verão. Por que sua aplicação não pode fazer o mesmo?

E no caso das horas ambíguas? Você também tem duas opções: escolher entre a primeira e segunda ocorrências. Novamente, é meio arbitrário, mas eu aconselho você a escolher a primeira ocorrência, pelo simples fato de tornar as coisas mais simples.

4. Confundir um Offset com um Fuso Horário

Considere o timestamp a seguir: 1995-07-14T13:05:00.0000000-03:00. Quando alguém pergunta o que o “-03:00” no final é chamado, muita gente responde “o fuso horário”.

A questão é essa. Essas pessoas provavelmente assumem corretamente que o número representa o offset, ou deslocamento, em relação a UTC. Também é provável que elas sabem que podem reconstruir a hora correspondente em UTC por meio desse offset. (Muitos desenvolvedores não entendem que, em uma string assim, o offset já está aplicado: para obter o tempo em UTC, você deve inverter o sinal do offset. E só depois, aplicá-lo ao valor da hora).

O erro está em achar que o offset é a única informação que um fuso horário representa. Mas não é. Um fuso horário é uma área geográfica, e contém muitas informações, tais como:

  • Um ou mais offsets. (Horário de verão existe, afinal de contas.)
  • As datas nas quais as transições do horário de verão acontecem. (As quais podem mudar e mudam, sempre que os políticos resolvem).
  • A quantidade de tempo pelo qual os relógios são atrasados ou adiantados na transição. (Não é uma hora em todo lugar.)
  • O registro histórico das mudanças nas regras acima.

Em resumo: não tente adivinhar um fuso horário pelo offset. Você vai errar a maioria das vezes.

Quer aprender sobre tempo? Já não era sem tempo!

Esta lista não é de forma alguma exausitiva. Eu apenas quis oferecer a vocês uma introdução ao fascinante e meio bizarro mundo dos problemas com hora e data em programação. Há muitos recursos valiosos por aí, comoa tag time zone no Stack Overflow ou blogs como o de Jon Skeet e o de Matt Johnson que são autores da popular biblioteca NodaTime.

E finalmente, sempre use as ferramentas que estão à sua disposição. Por exemplo, o produto da SubMain chamado CodeIt.Right tem uma regra que você a especificar um IFormatProvider em situações nas quais é opcional, o que pode acabar salvando você de bugs difíceis ao fazer tratamento de datas.

leia mais...

Programação Cargo Cult É A Arte de Programar Por Coincidência

NOTA: Eu escrevi este post originalmente para o blog da NDepend. Você pode clicar aqui para ler o artigo original no site deles, em inglês. Enquanto estiver por lá, baixe e experimente o NDepend.

Eu ouvi falar em programação cargo cult a primeira vez há alguns anos. Eu me lembro de ter pensado na época: “Que nome estranho para um conceito relacionado com programação”.

Se você compartilha do estranhamento do meu “eu” do passado, o post de hoje é para você.

Primeiramente, você verá o que programação cargo cult é e por que você deve se importar. Então, vamos dar uma olhada em alguns exemplos práticos, usando a linguagem C#. Finalmente, nós encerraremos com conselhos sobre o que você pode fazer para não cair nessa armadilha.

Programação Cargo Cult: Fazendo as coisas porque sim.

Segundo a versão em inglês da Wikipedia

Cargo cult programming is a style of computer programming characterized by the ritual inclusion of code or program structures that serve no real purpose.

Em tradução livre

Programação cargo cult é um estilo de programação de computadores caracterizado pela inclusão ritualística de código ou estruturas de programação que não servem nenhum propósito real.

Em outras palavras, é quando um(a) desenvolvedor(a) escreve código sem entender realmente o que aquele código faz. Talvez uma abordagem por tentativa e erro tenha sido usada - copia o código de um lugar, cola em outro, e vai mexendo e testando até que funciona, mais ou menos. Quando chega nesse ponto a pessoa geralmente para de mexer no código, por medo de fazer parar de funcionar. No processo, talvez sobrem resquícios de código que não servem realmente para nada.

Ou talvez a pessoa tenha tentado usar uma técnica aprendida com um colega, mas falhou em compreender que os conceitos são diferentes e que a tal técnica é inútil na situação atual.

Por fim, também é possível que o problema seja simplesmente educação insuficiente: talvez o desenvolvedor tenha um entendimento pobre a respeito de como as ferramentas usadas funcionam.

Por que a programação cargo cult é um problema?

Como Eric Lippert diz, programadores cargo cult sofrem para fazer alterações significativas em um programa e acabam usando uma abordagem de tentativa e erro já que eles não entendem o funcionamento interno do código que estão prestes a alterar.

Isso não é tão diferente do que os programadores pragmáticos chamam de “programação por coincidência”:

Fred doesn’t know why the code is failing because he didn’t know why it worked in the first place. It seemed to work, given the limited “testing” that Fred did, but that was just a coincidence.

Em tradução livre:

Fred não sabe porque o código está falhando porque ele não sabe porque ele funcionou da primeira vez. Parecia estar funcionando, com o “teste” limitado que Fred fez, mas era apenas uma coincidência.

A frase acima resume tudo para mim: se você não sabe como ou por que seu código funciona, você também não vai entender o que aconteceu quando ele parar de funcionar.

Origem do termo

Embora práticas que são consideradas “culto à carga” (cargo cult) atualmente tenham sido registradas tão cedo quanto o final do século XIX, o termo em si data de 1945, quando foi usado pela primeira vez para descrever práticas que surgiram durante e depois da Segunda Guerra Mundial entre habitantes da Melanésia.

Os nativos começaram a imitar o comportamento dos soldados, vestindo-se como controladores de voo e balançando gravetos, na esperança de que isso faria com que aviões carregados de suprimentos descessem dos céus.

Desde então, o termo culto à carga tem sido usado em uma variedade de contextos para significar imitar forma sem conteúdo - copiar perfeitamente os elementos superficiais mas ao mesmo tempo falhando em entender de maneira mais profunda o significado e funcionamento do que se está tentando emular.

Falar é fácil; me mostre o código!

Chega da aula de História por hoje. Hora de ver código! Eu vou mostrar cinco exemplos de programação cargo cult usando a linguagem C#. Vamos lá.

Checar um tipo de valor não-nulável for Null

O primeiro item é algo que me incomoda já que eu vejo isso bastante em código de produção. É algo assim:

	public Product Find(int id)
	{
   	   if (id != null) // essa verificação é inútil
	   {
	       Console.WriteLine("Esta linha sempre será executada.");
	   }
	
	   return new Product();
	}

Aqui nós temos o caso de um(a) desenvolvedor(a) que provavelmente não entende a diferença entre tipos de valor e referência. Seria completamente perdoável, no caso de um profissional iniciante, se não fosse pelo fato de que o compilador te avisa disso.

Você pode achar isso um exagero da minha parte. Afinal de contas, o código vai rodar perfeitamente mesmo assim. Na verdade, a verificação não será nem ao mesma incluída no IL resultante, como você pode ver nesse print de uma ferramenta de descompilação:

Uma imagem mostrando um trecho de código que não contem a checagem de nulo.

Você pode ver no trecho de código acima que o compilador otimizou o código, removendo a checagem por nulo.

Tem problemas muito piores, claro. Sim, a aplicação não vai quebrar por causa disso. Então, qual é o ponto?

Bom, pra começo de conversa, eu me preocuparia com uma empresa de desenvolvimento cujo único critério de qualidade é “roda sem quebrar”. Mas o problema de verdade aqui é que esse tipo de código demonstra uma falta de entendimento sobre características fundamentais da linguagem e da plataforma que podem lhe causar problemas no futuro.

Uso Desnecessário de ToList() em consultas do LINQ to Object

Assim como o problema anterior, o item atual é algo que eu rotineiramente vejo em código de produção. Considere o código abaixo:

	var result = users.ToList()
	.Where(x => x.PremiumUser)
	.ToList()
	.Select(x => new { Name = x.Name, Birth = x.DateOfBirth })
	.ToList();

O problema que temos aqui é que as chamadas ao método ToList() são totalmente desnecessárias (exceto talvez a última, caso você realmente precisasse que o resultado fosse um lista e não apenas um IEnumerable).

Em minha experiência, isso acontece quando quem escreveu o código não entende bem a natureza do LINQ; eles erroneamente acham que os métodos do LINQ pertencem ao tipo concreto List<T> ao invés de serem métodos de extensão que podem ser usados com qualquer implementação de IEnumerable<T>.

Ao chamar ToList() diversas vezes dessa forma, o desenvolvedor na verdade cria diversas listas novas, o que pode prejudicar o desempenho da aplicação.

O código acima pode ser reescrito da seguinte forma:

	var result = users.Where(x => x.PremiumUser).Select(x => new { Name = x.Name, Birth = x.DateOfBirth });

Conversões Desnecessárias

Considere a linha seguinte:

	DateTime creationDate = DateTime.Parse(row["creation_date"].ToString());

Aqui temos não apenas uma mas duas conversões desnecessárias. Primeiro, criamos uma nova string e então a “parseamos” para DateTime quando um simples cast seria suficiente:

	DateTime creationDate = (DateTime)row["creation_date"];

Esse exemplo assume que o tipo no banco de dados é um tipo específico para lidar com datas (como date ou datetime no SQL Server). É claro que se você estivesse usando um tipo inadequado (como varchar) então isso já seria um outro problema.

Try-Catch em todo lugar

Também conhecido como síndrome Pokémon (“Gotta catch’em all!”), o anti-pattern aqui é adicionar um bloco try-catch em cada linha em que exista a remota possibilidade de uma exceção ser disparada.

Pontos bônus se o código estiver tentando capturar System.Exception ao invés de uma exceção mais específica, acabando com a distinção entre erros esperados e não esperados.

Mais pontos se o bloco do catch não conter código nenhum!

A dica geral aqui é: jamais capture uma exceção a não ser que você tenha uma razão muito específica para fazê-lo. Do contrário, deixe que a exceção suba até que o gerenciador de exceções geral no nível mais alto lide com ela.

Se esse conselho parece vago (“Como vou saber se eu tenho uma boa razão para capturar a exceção?”), é porque de fato é. Explorar esse tema mais a fundo iria além do escopo desse post, , mas ler o excelente artigo do Eric Lippert chamado “Vexing Exceptions” vai aumentar e muito o seu entendimento sobre exceções.

Usar StringBuilder Demais

Você já deve ter visto o filme: depois de ler em algum lugar que concatenar strings usando + é ineficiente, nosso intrépido desenvolvedor resolve tomar pra si a tarefa hercúlea de mudar cada concatenação de string no projeto para o uso de StringBuilder.

A justificativa para isso, claro, é que System.String é imutável. Então, cada vez que você “muda” a string, você na verdade está criando uma instância nova na memória, o que pode prejudicar o desempenho da aplicação.

Mas adivinha só. O compilador é bem esperto. Digamos que você tenha a seguinte linha:

	string a = "Hello " + "World";

Isso vai ser no fim das contas traduzido para:

	string a = "Hello World";

A regra geral é: tudo bem usar concatenação simples se você sabe o número de strings a anexar em tempo de compilação. Do contrário, o uso de StringBuilder provavelmente faz mais sentido.

Lógico, alguns cenários não são tão claros assim. O único conselho que faz sentido dar aqui é: faça seu dever de casa. Quando estiver com dúvida, pesquise e faça benchmark sem dó.

Eu termino com mais uma dica sensata do Eric Lippert:

Unnecessary code changes are expensive and dangerous; don’t make performance-based changes unless you’ve identified a performance problem.

Existe Solução?

Eu diria que é justo supor que pessoas com menos experiência são mais propensas a cometer erros devido à programação cargo cult. Mas desenvolvedor nenhum está realmente a salvo, independentemente de seu nível de conhecimento ou experiência.

Nós somos apenas humanos no fim das contas. Cansaço, prazos, vieses cognitivos e (para ser realmente honesto) a preguiça eventual pode transformar até o melhor de nós em um programador cargo cult.

Infelizmente, não há uma maneira 100% garantida de impedir isso de acontecer. Mesmo assim, aqui vão algumas medidas que você pode tomar para, ao menos, diminuir as chances.

Vamos ver algumas delas.

Use Revisão de Código/Programação em Par

A primeira medida que você pode tomar para evitar o cargo cult é simplesmente ter uma segunda pessoa olhando seu código. Os benefícios de ter uma outra pessoa revisando cada linha de código antes que ela chegue em produção não podem ser subestimados. E embora revisão de código e programação em par não são exatamente equivalentes, ambas as práticas podem lhe trazer esse benefício.

Sempre Teste Suas Hipóteses

Escreva testes de unidade (e outros tipos de testes também). Monitore sua aplicação em produção. Se algo não está tendo um bom desempenho, faça benchmarks exaustivos. Não faça só suposições. Testar as suas hipóteses pode trazer insights valiosos e salvar a sua pele naqueles momentos em que a sua intuição não for certeira.

Leia Código de Outras Pessoas

Ler código escrito por outras pessoas é uma ótima maneira de aprender. É uma ferramenta perfeita para comparar suas ideias e suposições contra o que outros desenvolvedores estão fazendo, expondo você a novos conceitos que podem lhe forçar a ganhar um entendimento maior dos problemas que está tentando resolver.

Na era do GitHub, não tem muita desculpa para não fazer isso.

Aprenda Com Suas Ferramentas

Existe um número enorme de ferramentas que podem ajudar sua equipe com a qualidade do seu código. Aqui vai a dica principal: você não deve só usar essas ferramentas. Você deve também aprender com elas. Se você usa NDepend, leia sobre suas regras. Tente entender a justificativa por trás de cada uma delas. Quais são os princípios e boas práticas que guiaram seus autores durante a criação delas?

A mesma dica vale para outros tipos de ferramentas, e até para os warnings que o compilador lhe mostra.

Ciência da Computação, Não Superstição da Computação

Embora ninguém seja imune à programação cargo cult, nós devemos nos esforçar para superá-la. Há sabedoria na nossa área à nossa disposição, acumulada lentamente ao longo de mais de 7 década. Vamos usá-la. Vamos entender melhor nossas ferramentas e nossa profissão e escrever software de qualidade.

leia mais...

Funcionalidades do C# 8.0: Um Vislumbre do Futuro

C# 8.0 está chegando e vai trazer algumas funcionalidades muito interessantes. Vamos dar uma olhada no que o futuro reserva.

leia mais...

Testes Unitários Para Iniciantes - Parte 2

Antes tarde do que mais tarde! Hora de continuar nossa série sobre testes unitários para iniciantes. Hoje você vai escrever seu primeiro teste unitário.

leia mais...

Testes Unitários Para Iniciantes - Parte 2

Antes tarde do que mais tarde! Hora de continuar nossa série sobre testes unitários para iniciantes. Hoje você vai escrever seu primeiro teste unitário.

leia mais...

Funcionalidades do C# 7 que vale a pena conhecer - Parte 2

Neste artigo, vamos continuar a ver algumas das features mais interessantes do C# 7.

leia mais...

Funcionalidades do C# 7 que vale a pena conhecer - Parte 1

C# 7 está finalmente entre nós. Hora de conhecer algumas de suas features.

leia mais...

Já está na hora de começar a usar essas features do C# 6!

A sétima versão do C# está chegando, e vai provavelmente trazer várias funcionalidades novas e úteis para nossas caixas de ferramentas. Mas deixa eu perguntar uma coisa: você já está usando as funcionalidades da versão anterior?

leia mais...

Tipos de valor e referência em C#, Parte 2 - Por que DateTime não pode ser nulo?

“Por que uma variável DateTime não pode receber null?” Esta é uma pergunta que vive aparecendo no StackOverflow e site similares. Às vezes escrita de modo um pouco diferente, às vezes com um tipo diferente, mas no fundo é a mesma dúvida. O que é natural, se você considerar que provavelmente milhares de pessoas entram na área a cada ano.

leia mais...

Tipos de valor e referência em C#

Este é o meu primeiro post pra valer aqui no meu blog, e eu decidi escrever sobre tipos de valor e tipos de referência em C#. Isso é um assunto relativamente básico, no sentido de que é algo que você já deveria entender caso você programe em C# profissionalmente. Mas ao mesmo tempo, é algo que pode ser um pouco contra-intuitivo caso você não seja um desenvolvedor experiente.

leia mais...