Dados, necessidades básicas e arquitetura de sistemas

Dados estão por toda parte. Eles sempre estiveram. Cada dia em maior número e mais diversos do que costumamos pensar.

Dados são a origem fundamental a partir da qual toda informação existe.

Any time scientists disagree, it’s because we have insufficient data. Then we can agree on what kind of data to get; we get the data; and the data solves the problem. Either I’m right, or you’re right, or we’re both wrong. And we move on.

Neil deGrasse Tyson

Dados são fatos em estado bruto, não organizados, que precisam ser processados. Por vezes, podem parecer aleatórios, mas quando processados, organizados, analisados e apresentados em um determinado contexto, nos dão informações úteis para o conhecimento factual do mundo ao nosso redor.

Evidence based medicine (EBM) is the conscientious, explicit, judicious and reasonable use of modern, best evidence in making decisions about the care of individual patients. EBM integrates clinical experience and patient values with the best available research information.

Izet Masic, Milan Miokovic, and Belma Muhamedagic [article]

Sem conhecimento factual não há como ter evidências de que algo é real ou irreal, verdadeiro ou falso, promissor ou um completo desperdício de tempo e dinheiro. Evidências são a razão para acreditar.

Primeiro as primeiras coisas

“Big data, AI, machine learning, deep learning, data-driven”, não sei se em outras áreas é assim, mas em desenvolvimento de software adoramos a nova palavra da moda. Não importa se o objeto ao qual a palavra se refere é algo novo ou não, o que importa é que se “todo mundo” está falando, vamos falar também.

No final das contas, a coisa acaba ficando mais ou menos como a famosa frase do professor de psicologia e economia comportamental Dan Ariely:

Big Data is like teenage sex: everyone talks about it, nobody really knows how to do it, everyone thinks everyone else is doing it, so everyone claims they are doing it.

Dan Ariely, Duke University [tweet]

O que é menos conhecido é um outro comentário dele, desta vez sobre educação e aprender a lidar com dados:

Instead I proposed that management education and practice should become much more experimental and data-driven in nature — and I can tell you that it is amazing to realize how little business know and understand how to create and run experiments or even how to look at their own data! We should teach the students, as well as executives, how to conduct experiments, how to examine data, and how to use these tools to make better decisions.

Dan Ariely, Duke University [post]

Educação antes de chavão. Aprender a lidar com dados para então poder ser guiado por eles. A menos que você não se importe de se deparar com a beira do penhasco tarde de mais.

Começando por baixo

Tomando emprestado um pouco da teoria da Hierarquia de Necessidades de Maslow e extrapolando para o nosso assunto dados, dá para pensar em uma pirâmide mais ou menos como a seguir:

E enquanto vemos que muito se fala de inteligência artificial, machine learning, deep learning, etc, pouco ainda se discute sobre o fundamental, sobre a base da pirâmite: coletar dados de maneira consistente, confiável e que permita escalar a pirâmide com segurança. Ou pelo menos, com mais segurança do que nenhuma segurança.

Já ouvi inumeros casos de empresas que contrataram cientista de dados quando nem sequer tinham dados para serem trabalhados. Melhor dizendo, não é que não tinham dados, porque como vimos no começo deste post, dados estão por toda parte; o que eles não tinham era coletado seus dados de maneira sistemática e disponibilizado para que pudessem ser usados para analise, modelagem, predição e automação de processos.

Dados precisam fluir

Quando se pensa em coleta de dados, logo se pensa em integração de dados.

O problema de integração de dados existe há décadas, provavelmente, desde que dados armazenados e processados em um certo mainframe precisou ser transportado, armazenado e processado em outro mainframe, para servir de consolidação ou qualquer outro propósito. Bem, certamente, muito antes dos mainframes existirem, se formos considerar o modo como os censos demográficos dos países eram realizados há séculos. Mas vamos nos ater aos computadores modernos.

Denunciando um pouco a minha idade, eu entrei para o saudoso colegial técnico em processamento de dados em 1997, quando tínhamos uma matéria que se propunha a nos ensinar Técnicas de Sistemas de Processamento de Dados. Na época, usavamos DBase III+ e Clipper.

De lá para cá, vi sistemas serem integrados por arquivo texto transferidos por disquete ou modem; depois foram os drives compartilhados na rede local e o FTP para quem estava de fora.

Então, depois de um tempo, começamos a fazer integrações em banco de dados – Interbase, Oracle, DB2, Sybase. Todo mundo se conectava ao gerenciador de banco de dados que estava disponível na rede e era aquela festa. “Deadlock? Yeah, true story, bro.”

Aí foi a vez do SOA com seus imponentes ESBs. Bem, imponentes até a página dois, quando a festa da integração via banco de dados migrou para eles.

A moral da história qual é? Dados precisam fluir. Eles precisam ir de um lugar para o outro; e de uma maneira ou de outra, isso vai acontecer.

Deixando os dados fluírem

Se você chegou até aqui, você deve estar se perguntando: ok, deve haver uma maneira certa de fazer os dados fluírem, certo? A resposta é depende.

– Para aonde esses dados precisam ir?
– Com que frequência?
– Qual o seu volume?
– Eles são sensíveis? (LGPD)
– O que acontece se eles forem corrompidos?

Estas e outras perguntas é que vão nos guiar à maneira mais correta de implementar o fluxo dos dados que entrarão, circularão e sairão da organização em que trabalhamos. Arrisco a dizer que não há certo e errado, mas sim, a maneira que dará mais ou menos dor de cabeça no curto, médio e longo prazo.

Acrescente à conta:

– Custo de implementação da solução inicial;
– Custo de manuteção da solução em produção;
– Custo de migração para uma possível nova solução;
– Expertise da equipe que vai fazer tudo isso.

Uma dentre muitas

Muito embora, possa não existir “a maneira certa” ou “a maneira errada” em absoluto, há muitas maneiras que já foram documentadas, aqui, ali, e acolá; e há certamente bastante terreno comum entre elas.

O que eu quero, então, é dedicar o final deste looongo post para falar brevemente, em alto nível, sobre uma maneira que há alguns anos tem emergido com sucesso e sido recomendada para muitos casos de plataforma de dados.

Eu acho que a primeira coisa que se pode notar no diagrama acima é que trata-se de uma arquitetura de sistemas centrada em um log stream.

Arquiteturas desta natureza tem o log stream como canal de entrada de dados para um contexto onde possam ser conhecidos, usados e manipulados por diversos sistemas e aplicações de um ecosistema.

Uma característica fundamental de logs é que são append only por natureza. Já a durabilidade de seus registros pode variar de acordo com a finalidade deles, podendo ser finita ou indeterminada.

Algumas empresas vão tão longe quanto terem seu log stream como fonte única de verdade para seu ecosistema de aplicações. Na prática, um registro histórico. Ou seja, em caso de perda de dados ou bug de processamento em alguma aplicação, que tornou seu estado persistente inválido, é possível usar o log para voltar no tempo, reprocessar seus registros factuais cronologicamente e chegar a um novo estado persistente, agora correto.

Viagem no tempo, eles disseram.

Desnecessário mencionar que, em arquiteturas orientadas a eventos, o log stream é a espinha dorsal de onde os eventos são propagados.

Okay, vamos segmentar um pouco o diagrama acima.

A Entrada

A ideia é que este canal permida a entrada tanto de dados brutos quanto limpos. Podendo os últimos serem fatos e eventos do processo de negócio, tais como perfil de usuário criado, pedido de compra realizado, pagamento confirmado, etc. Já os primeiros, dizem mais respeito a logs de acessos, telemetria, clickstream, tudo o que precise ser analisado dentro de um contexto, para então produzir alguma possível informação útil para o negócio em um futuro não imediato.

Protocolos, schemas, metadados, são todos importantes desde este momento para que o fluxo dos dados seja confiável e sem entraves ao longo do caminho; principalmente, porque o tempo de vida dos dados pode ser indeterminado. O que quer dizer que você pode ter que lidar com um registro malformado por muito, muito, muito tempo mesmo. Mas isto não quer dizer que não deva haver flexibilidade. Pelo contrário, significa que deve haver intenção no formato dos dados, seja ele bruto ou tratado, e metadados que os descrevam. Isto vai promover descoberta e uso por um longo tempo.

Uma coisa interessante sobre dados é que eles geralmente vivem mais tempo do que as aplicações pelo meio das quais foram registrados pela primera vez.

Dados brutos, crus, podem ser trasnformados em algo útil para uma determinada aplicação (a.k.a. event sourcing) e/ou serem enviados para um data lake, para depois então servir de input para analises diversas ao escalar a pirâmide que vimos há pouco.

Dados limpos, por sua vez, geralmente vão ser eventos que precisam ser respondidos por algum agente primário e podem ter muitos outros espectadores secundários, outros agentes interessados em fazer algo com o fato noticiado. Por exemplo, a notícia de que um pedido acabou de ser finalizado por um determinado cliente de e-commerce. Quantos processos podem ser iniciados a partir deste fato? Desde processos transacionais, para que o produto seja finalmente entregue, quanto processos analítcos, para entender a venda realizada, colocando tudo no papel, a lista poderia ficar tão longa quanto este post.

O Processamento

O que acontece nessa etapa? Quem é que sabe! Isso vai depender da organização.

Em geral, o que vai acontecer são limpezas, transformações, reestruturações, enriquecimentos de dados, para então serem carregados em outras bases de dados com finalidades específicas, sejam elas relacionais, não-relacionais, analíticas, fulltext search, ou mesmo sistemas ERP, CRM e o que quer que se possa imaginar.

Como mencionado no estágio anterior, processos de negócio podem ter seu início aqui também. Aliás, sendo uma arquitetura centrada em log stream, este é o mais provável. Na ocasião do registro de que um pedido foi finalizado no site, um sistema de pagamento que faz integração com um sistema de terceiro, pode reagir chamando uma API, para processar a transação financeira; um sistema de CRM pode reagir registrando características da venda; e assim por diante.

Seguindo esta linha de raciocínio, no caso de um provedor de cloud computing, por exemplo, vai haver um serviço de provisionamento de infraestrutura, registro de domínios e aplicações SaaS, na expectativa do evento de pagamento realizado ser incluído no log.

Poderia ser também uma campanha de marketing que acabou de ser criada e registrada no log stream, que… okay, você já pegou a ideia.

O ponto crucial aqui é que o sistema que reage a um evento, não está acoplado ao sistema que produz o evento. Idealmente, eles nem se conhecem. Em outras palavras, quem produz o dado não sabe exatamente quem vai consumi-lo e para que finalidade.

A Saída

Suficiente dizer que o céu é o limite.

Vantagens

A principal vantagem que vejo é que uma arquitetura centrada em log stream reduz a complexidade de integração de aplicações a 1-para-1. Em vez de ter que escrever código de integração para falar e ouvir N aplicações e sistemas heterogênios, você escreve código para apenas um canal de comunicação, um ponto único de integração. É claro que cada input é um input e você vai ter que lidar com isso semi-individualmente, porque é algo mais relacionado à semântica da comunicação do que qualquer outra coisa. Mas é possível criar alguns padrões para ajudar nisto, apenas tomando o cuidado de não incorrer no erro da generalização generalizada.

A segunda vantagem é o desacoplamento, que até tem um pouco a ver com a vantagem anterior. Mas o ponto aqui é que um sistema não fica dependendo de fazer as chamadas imperativas a outros sistemas diretamente, em uma exata ordem, tendo que lidar com latências diversas, modos de falha, etc. Você registra um fato, notifica a ocorrência de um evento, e o que vai acontecer depois disso torna-se mais flexível do que se você tivesse que fazer uma chamada imperativa dizendo “faça isso agora”.

Finalmente, você tem um registro temporal durável de tudo que aconteceu desde um determinado ponto no tempo, como a nossa história que não pode ser apagada. E como benefício adicional, se tem a possibilidade de voltar no tempo e reprocessar dados desde então, não alterando a história, que é imutável, mas criando novos registros que são uma correção histórica.

É claro que se você não tem todo espaço em disco do mundo e não tem interesse eterno em tudo que é inserido no log, você pode definir políticas diferenciadas de retenção. O meio do caminho é uma opção. Não precisa ser sempre oito ou oitenta.

Desvantagens

Não é fácil. Não, é sério. Não é fácil. Se te disseram que é fácil, mentiram.

Você precisa de pessoal capacitado, com múltiplos conhecimentos e especialidades. Não dá para pegar meia dúzia de juniores e se colocar a implementar uma arquitetura deste tipo da noite para o dia.

Você tem o custo de infraestrutura, de operação, de mantenção, etc.

O que se precisa analisar é o suposto benefício que se terá com uma arquitetura destas em função de onde a organização está hoje e onde se imagina que ela estará em, digamos, 3-5 anos. Chegariam melhores, com menos dores de cabeça e mais noites bem dormidas se caminhassem para uma arquitetura semelhante a essa?

Decisões de arquitetura não precisam ser todas tomadas no dia 1, podem ser incrementais. Mas um bom roadmap ou um mapa de possíbilidades nunca deixou ninguém com insônia.

Comece seu desenho de arquitetura de sistemas sempre pelo mais simples e vá incrementando. Baby steps. Você vai ver que o mais simples pode ser bem sofisticado, quando permite que a complexidade inevitável seja adicionada aos poucos.

A tecnologia da vez

Provavelmente, você deve ter notado que em momento algum eu cito uma tecnologia específica para implementar esta arquitetura. Nenhuma. Isto porque a minha ideia com este post é tratar a coisa do ponto de vista dos primeiros princípios, dos princípios fundamentais de arquitetura de sistemas e não de tecnologias específicas, que mudam com o tempo.

Como você vai implementar a arquitetura particular da sua organização, tendo domínio conceitual do que pretende fazer e de padrões arquiteturais que emergiram ao longo dos anos, é que é outra coisa. É isto que importa. Esta é a maneira de você não ficar vendido por tecnologias específicas, por vendors, e acabar querendo fazer isso e aquilo por conta de uma tecnologia ou vendedor. Princípios vêm primeiro. Ouch!

Para você ter uma ideia, há 10 anos eu trabalhava em uma empresa onde implementamos um baita sistema usando muitos conceitos que vimos até aqui (propagação de eventos, message brokers, feeds, entre outras coisas), um pipeline bem robusto, sem usar a maioria das tecnologias mais populares de hoje.

O importante é cuidar bem de seus dados na base da pirâmide e estar preparado para escalar com eles para o próximo nível.

Rust | Macros procedurais e testes automatizados

Escrevendo testes integrados para meu projeto Moy Sekret, senti falta de um recurso nativo do Rust para fazer setup & teardown, antes e depois dos casos de teste em si, para checar e limpar alguns efeitos colaterais dele em disco, por se tratar de um programa CLI que lida com criptografia de arquivo.

Sendo Rust uma linguagem compilada para binário nativo, sem um runtime, como Java ou C#, a solução mais óbvia para resolver isto com meta-programação foi criar uma macro.

Macro rules!

Na minha primeira interação para resolver meu problema, então, criei uma macro declarativa simples para injetar um bloco de código before e after imediatamente antes e logo após as funções de testes.

Fonte: https://github.com/leandrosilva/moy-sekret/blob/master/tests/common.rs

O que a macro setup_run_test faz é criar uma outra macro, a run_test, que é a real responsável por rodar o caso de testes, sanduichado pelos blocos before_test e after_test definidos através da setup_run_test.

Depois, para fazer uso destas macros é bem simples.

Fonte: https://github.com/leandrosilva/moy-sekret/blob/a165a570c36af0eae6e020608511a43240e2fda1/tests/encrypt_test.rs

Uma vez que a macro tenha sido exportada em sua definição, quando o módulo em que ela foi definida é importado, ela torna-se também disponível para uso no contexto atual.

Programação por convenção

A despeito destas macros terem dado um bom adianto, para eu não ter que escrever do_something(); e do_something_else(); em todos os meus casos de teste (que por enquanto nem são tantos, para ser franco), isto ainda me pareceu muito trabalho ter que fazer setup dos blocos before e after e depois usar a macro run_test! em cada caso de teste.

Por que não seguir uma convenção em vez de fazer uma configuração? You bet!

Macros procedurais

Rust tem um recurso [já não tão novo assim] chamado Procedural Macros, que permite que você manipule um trecho de código fonte (AST) em tempo de compilação e produza um código diferente para ser compilado.

Há três tipos de macros procedurais:

1) Derive macros (#[derive(AwesomeDerive]) – tipo de macro já bem estável, desde a versão 1.15 de Rust, e de uso razoavelmente comum.

Fonte: https://github.com/leandrosilva/moy-sekret/blob/master/src/lib.rs

2) Function-like macros (pretty_macro!()) – este tipo de macro está estável desde a edição 2018 de Rust. É um tipo muito interessante, parecido com macro_rules!, porém bem mais flexível, já que você tem bastante liberdade em relação aos parâmetros que podem ser aceitos.

Pense em uma função que execute SQL, por exemplo.

Todo este código SQL acaba sendo encapsulado em um TokenStream, que é passado para uma função sql, que finalmente pode parseá-lo e blá, blá, blá.

3) Attribute macros (#[GorgeousAttribute])- este tipo também tornou-se estável em Rust 2018. É muito parecido com as anotações que temos em Java ou C#, porém estritamente em tempo de compilação e permitem fazer transformações no código.

Este foi o tipo de macro que ajudou a resolver minha preguiça de digitar meia dúzia de linhas de código.

Entra o Testaun

O projeto Testaun é o crate que eu criei para conter macros que me ajudem a reduzir código nos meus testes. Hoje, tudo que ele tem é uma macro. Mas por hora, é tudo que eu preciso.

Fonte: https://github.com/leandrosilva/testaun/blob/master/src/lib.rs

O que esta macro procedural faz é extrair o bloco de código de uma função de testes e sanduichar ele com chamadas às funções testaun_before e testaun_after. Na prática, o mesmo que a macro run_test! que mostrei anteriormente.

Lembra? Em lugar de fazer o setup de dois blocos para serem executados antes e depois, usamos convention over configuration e esperamos que estas duas funções tenham sido definidas. Caso contrário, pau! O compilador vai chiar.

Okay. E como é que se usa isto depois?

Tendo adicionado este crate ao projeto, basta criar as funções que rodarão antes e depois do caso de teste, anotar a função de teste e é isto.

Fonte: https://github.com/leandrosilva/testaun/blob/master/tests/tasting_test.rs

Como o código acima exemplifica, se um caso de teste não precisa de before & after, basta não anotar com #[testaun_case].

Uma coisa que percebi foi que o crate serial_test (que também manipula AST) e o testaun não se dão bem juntos. Testaun boicota o serial_test. Vou estudar a coexistência deles depois.

Não se reprima. Digo, não se REPITA!

Se você for como eu, um programador preguiçoso, a DRY kinda guy, você pode usar macros para economizar umas linhas de código repetitivo, reduzir boilerplate, padronizar o código do seu projeto e, no final das contas, torná-lo menos suscetível a falhas.

Menos código repetido, menos bugs.

Aliás, menos código total, menos bugs ainda.

Rust | Minhas impressões até então

Desde março de 2018, eu vinha vendo uma coisa aqui, outra ali, sobre a linguagem Rust, após ter visto a apresentação do Florian Gilcher na GOTO 2017, intitulada “Why is Rust successful?”, mas nada realmente sério. Me lembro de ter ficado especialmente empolgado com duas talks do Bryan Cantrill, uma na QCon 2018, “Is it Time to Rewrite the Operating System in Rust?”, em junho de 2019, e outra em um meet up, “The Summer of Rust”, alguns dias depois, mas ainda assim, nada de pegar um livro para ler, de rabiscar algum código.

Histórico do YouTube #1 – Primeiro contato com Rust

Na época eu até tinha uma desculpa compreensível: tinha acabado de completar um bacharelado de Nutrição. Sim, isso mesmo. Quatro anos em uma sala de aula há 17km de casa, lendo um tanto de livros, artigos científicos; fazendo trabalhos, estudando para provas, apresentando seminários; estágios de 6 horas diárias em dois hospitais, uma clínica de nutrição esportiva e uma escola de educação infantil; e ainda o fadigante TCC sobre a relação entre nutrição e depressão. Tudo isso enquanto ajudava a construir a Pricefy do zero.

Dá para imaginar que o tema do TCC veio bem a calhar.

Histórico do YouTube #2 – Talks que por um momento me empolgaram

Mas então virou o ano, chegou 2020, a fadiga mental diminuiu significantemente e resolvi gastar algum tempo com Rust, estudar com um pouco mais de dedicação, rabiscar uns programas, experimentar por mim mesmo e não ficar somente no que vejo da experiência dos outros.

Esse post é para registrar um pouco das minhas impressões até aqui.

Rust, a linguagem

Não quero, aqui, dar uma introdução à linguagem, porque já existe uma documentação oficial maravilhosa, muito material educativo disponível gratuitamente, pois isso seria um tanto redundante.

O que é importante se ter em mente, a princípio, é que Rust foi criada com o objetivo de ser uma linguagem de sistema, para ser usada em casos de uso onde normalmente se usaria C/C++, como: drivers, sistemas embarcados, microcontroladores, bancos de dados, sistemas operacionais; programas que vivem extremamente próximos ao hardware, que requerem alta performance, com baixo consumo de memória e overhead de execução próximo de zero.

Portanto, algumas decisões de design foram:

– Ser compilada para binários nativos;
– Ter um sistema de tipos estático, forte e extensível;
– Não ter coletor de lixo;
– Ter um sistema seguro de gestão de memória;
– Ser imutável por padrão;
– Dar suporte a concorrência imune a data races e race conditions;
– Ter checagem de uso de memória em tempo de compilação;
– Permitir código “não seguro”, quando explicitamente desejado;
– Oferecer tratamento de erro simples, mas robusto;
– Ter um ótimo ferramental de desenvolvimento.

Dentre outras coisas. Esta não é uma lista exaustiva. Mas é o suficiente para contextualizar o que vou falar sobre minhas impressões.

Em poucas palavras, eu diria que o objetivo principal era que ela fosse uma linguagem de baixo nível, extremamente performática, porém absolutamente segura e produtiva.

Vamos então à minhas impressões.

O Compilador

Eu fiquei realmente pirado no compilador. Não, é sério. Tendo gastado boa parte dos últimos anos programando em C#, JavaScript, Go e Python, acho que não preciso dizer muito mais.

Mas vamos ver um exemplo:

O que nos diria o compilador sobre este programinha?

Hmm? E você, o que me diz?

Ao longo do post vão aparecer mais exemplos legais da atuação do compilador, portanto não vou me prolongar aqui.

Imutável por natureza

Variáveis são sempre imutáveis, a menos que explicitamente dito que não, como no caso que vimos há pouco.

Isso favorece o desenvolvimento de código concorrente seguro, o que há muito tem sido um dos principais atrativos de linguagens funcionais – ou melhor dizendo, do paradigma funcional de programação.

Não há porque temer o compartilhamento de valores que não mudam; aliás, que não podem ser modificados. Nenhuma linha de execução vai crashear esperando que a seja "Hello", quando na verdade, agora, a é "Oi".

Possessiva, porém generosa

Agora, espere. O que aconteceria se seguíssemos a sugestão do compilador e tornássemos a variável a mutável?

O efeito colateral seria observado. A variável a poderia ter seu valor modificado e os prints refletiriam isso.

Primeiro porque ela teria sido explicitamente anotada como mutável. Justo. E depois, porque a macro println! faz parte de uma família de casos específicos de macros, em que o parâmetro é implicitamente tomado por referência (a.k.a. borrowing), por questão de conforto, praticidade, mas não causam efeitos colaterais neles.

Okay. Isso coloca em cheque o que vimos no tópico anterior, não? Nhmm… não tão depressa.

Vamos modificar um pouco o exemplo anterior e ver o que aconteceria em uma função que recebe uma variável não por referência, como é o caso da macro println!, mas por transferência de posse (a.k.a. ownership).

O compilador logo chia, dizendo que se está tentando emprestar o valor supostamente possuído pela variável a, para poder modificá-lo, enquanto este, na verdade, teve sua posse transferida para a função awesomely_crazy. Ou seja, o que quer que awesomely_crazy faça com o que recebeu, a variável a não tem mais nada a ver com isso.

O que acontece é que, em Rust, como você já deve ter percebido, um valor só pode ser possuído por uma única variável por vez; e quando o escopo em que esta está contida termina, seu valor é destruído. No entanto, essa posse pode ser cedida a outro.

Quem garante essa coisa de ownership, borrowing e lifetime em tempo de compilação é o chamado borrow checker, que muitas vezes se recusa a compilar um programa que você tem “certeza” que está tudo certo.

No nosso caso, somente a variável a era dona do valor "Hello" até ter transferido sua posse para a função awesomely_crazy. A partir de então, a função awesomely_crazy (nominalmente o parâmetro s) é quem passou a ser sua única proprietária; e ao final de sua execução, ao término de seu escopo, esse valor será destruído. É por isso que ele não pode ser emprestado novamente para modificação, através de a.push_str, ou mesmo emprestada para println!, que sequer modifica alguma coisa.

Portanto, se quisessemos fazer esse código compilar, teríamos que modificar a implementação da função awesomely_crazy, de modo que ela passasse a tomar o valor da variável a emprestado, por referência (&C/C++ feelings, anybody else?), e não por posse.

Não haveria qualquer problema.

Mas note que awesomely_crazy toma o valor de a emprestado, por referência, mas não pode modificá-lo, como é o caso em outras linguagens. Se quiséssemos permitir que awesomely_crazy modifique o valor possuído pela variável a, teríamos que fazer um desencorajador malabarismo de mut, que provavelmente nos faria pensar um pouco mais no algoritmo que estamos tentando escrever.

Eu sei que tudo isso pode parecer complicado (e na prática é mesmo; tente implementar uma estrutura de dados recursiva, por exemplo), mas essas características da linguagem:

– Imutabilidade por padrão;
– Mutabilidade por decisão explícita;
– Posse exclusiva de valor;
– Empréstimo de valor com restrições.

Com regras rigidamente observadas pelo compilador, são super interessantes na hora de escrever programas que rodam continuamente, por tempo indeterminado, sem crashear depois de devorar toda a memória disponível, por causa leaks; ou então, programas com processos concorrentes, que não crasheiam por conta de data races e race conditions.

Essa é a maneira de Rust possibilitar um runtime de alta performace, seguro para processos concorrentes, que não correm o risco de lidar com dangling pointers, data races, e ainda livres do overhead de um coletor de lixo para garantir isso.

Confesso que volta e meia ainda passo perrengue com isso e tenho que repensar meu código, mas isso tem acontecido cada vez menos e tenho gostado cada vez mais. O que realmente me deixa puto são certos casos de inferências, que penso pqp, como é que ele não consegue saber em tempo de compilação o quanto essa p@%$# vai consumir de memória.

Anyway. De qualquer forma, não existe null pointer em Rust e isso por si só já me deixa feliz.

Sintaxe, bizarra sintaxe

Olha quem está falando: alguém que gastou um tanto de horas de sua vida programando em Erlang. Okay. Não tenho muito o que reclamar.

Mas a real é que esse título é mais clickbait do que verídico.

A verdade é que eu gosto da sintaxe de Rust. Sempre fui fã de linguagens com sintaxe C-like, tipo: Java, JavaScript, C#, Scala, Go, e outras. Mas em todas essas linguagens sempre tem alguma coisa que acho chata, irritante ou bizarra. Às vezes as três.

No caso de Rust, o que acho bizarro é a anotação de escopo para ajudar na validação de tempo de vida de referências à memória. Como vimos anteriormente, não há coletor de lixo, então toda referência tem um tempo de vida baseado no escopo em que esta está contida. Terminado o escopo, essa referência é destruída e sua memória liberada. A maior parte do tempo, o compilador consegue inferir isso sem ajuda, mas às vezes, você precisa dar uma mãozinha, usando o chamado generic lifetime parameter, para garantir que em tempo de execução as dadas referências serão de fato válidas.

De novo, não quero aqui, neste curto espaço, me atrever a explicar algo que já está muito bem explicado gratuitamente e online, mas vamos ver um exemplo disso.

Side note – Olha o compilador aí, dando aquela ajuda Google-like

O código abaixo implementa um if-ternário sem sucesso, porque há uma ambiguidade sobre que referência será retornada.

O compilador, naturalmente, não gosta disso, diz o motivo e sugere uma solução.

Implementada a solução sugerida; ou seja, anotado o escopo de vida do que a função recebe e do que retorna, para que a ambiguidade seja eliminada.

Vòila! O código compila e, a menos que tenha um erro de lógica, roda perfeitamente como esperado.

Tudo bem. Foi só uma anotaçãozinha. Mas isso porque também foi só uma funçãozinha. Imagine algo com escopo de vida um pouco mais completo, que precise de mais de uma anotação de lifetime e ainda outras anotações de tipos genéricos.

É, a coisa pode escalar bem rápido. Generics é um recurso fantástico, sem sombra de dúvidas, mas imagine isso na assinatura de uma função, que também tem outros parâmetros, e retorno, e… Pff!

Mas por outro lado, o lado bom, agora, é que há um recurso robusto de definição de tipos. Em lugar de escrever algo assim:

Você pode definir um tipo que defina essa especificação gigantesca, fazendo bom uso de generics e tudo mais; e inclusive, dar um nome para o que ela representa. Só espero que você seja melhor do que eu com nomes.

Vê? Com isso é possível tornar o código bem mais semântico, comunicar mais significado, porque no final das contas, você passa mais tempo lendo código do que escrevendo ou apagando código.

Side note – Outra vez, o compilador amigão

Pattern Matching e tratamento de erros

Quem me conhece e já trocou ideias de programação comigo, sabe que sou bem fã de pattern matching. Essa é uma das coisas que mais gosto em Erlang e é também uma das que mais gosto em Rust.

Lindo, não? Okay. Eu sei que estou exagerando um pouco.

Mas a questão é que este recurso, além de favorecer que se escreva código mais declarativo e com viés mais funcional, também encoraja o tratamento de erros mais simples, menos complicado.

Em Rust, a forma idiomática de tratamento de erros é que o retorno de uma função seja uma enum Result, que pode conter o resultado do sucesso ou o famigerado erro.

Este é um exemplo que peguei da própria documentação da enum Result. Ele dispensa explicação, tão simples e declarativo que é.

Uma coisa interessante para se mencionar aqui, que tem tudo a ver com pattern matching e tratamento de erros, é que enums em Rust estão muito próximas do que em linguagens funcionais chamamos de tipos algébricos, o que favorece muito a expressividade do código.

Okay. Mas voltando à Result e ao tratamento de erros, expandindo um pouco no exemplo acima, retirado da própria documentação, perceba que há uma série de funções bacanas, incluindo map e or_else.

De novo, essa é a maneira idiomática de se lidar com erros em Rust. Sim, existe uma macro panic!, muito parecida com o que há em Go, mas use com moderação.

Aliás, falando em Go, eu tenho que dizer que gosto bastante de Go e prova disso é que desde 2012 tenho estudado e feito Go aqui e ali, quando faz sentido. Dito isso, eu acho a forma idiomática de tratamento de erros super simples e compreensível, porém tosca e pobre. Nada sofisticada. Mas tudo bem, este não é o ponto da linguagem.

Recursos funcionais

Rust não é uma linguagem funcional. Rust não foi desenvolvida para ser uma linguagem funcional. Mas tendo sido significantemente influenciada por programação funcional, Rust oferece muitas ferramentas para que programadores experientes em programação funcional escrevam código com conformação funcional.

– Imutabilidade por padrão;
– Iterators;
– Clousures;
– Block expressions;
– Pattern matching;
– Function composition;
– Tuplas;
– Enumerações.

São todos recursos disponíveis na linguagem, que possibilitam escrever código expressivo, com estilo funcional; e com um detalhe importante: em geral, uma das reclamações que se faz de linguagens funcionais (e não quero aqui discutir isso) é a questão de se preterir performance de execução em função do rigor conceitual do código, mas este não é o caso de Rust, que tem massiva influência da filosofia C++ de custo zero de abstração.

Tudo ao mesmo tempo agora

Concorrência em Rust, na minha visão, não é tão natural como é em Erlang ou Go, mas isso é ok. Por outro lado, Rust oferece mais de uma ferramenta para se implementar concorrência:

– Message passing;
– Shared-state concurrency;
– Futures.

Este é um exemplo simples de message passing que peguei emprestado de Klabnik & Nichols. Bem semelhante ao que se tem em Go, por exemplo, porém um tanto mais verboso.

Aliás, um parênteses aqui: às vezes acho Rust um pouco verbosa de mais. Fecha parênteses.

Este modelo de troca de mensagens é um que me agrada bastante e que estou bem familiarizado. Em muitas situações tendo a pensar primeiro neste modelo antes de considerar outro, porque ele favorece o desacoplamento.

Abaixo, o modelo de compartilhamento de estado entre threads usando Mutex.

Sinceramente, não gosto muito deste modelo. Mas programação não é sempre sobre o que se gosta, mas sobre o que se precisa fazer para ir da maneira mais segura e eficiente possível do ponto A ao ponto B. Então, quando é necessário usar o bom e velho lock, cá está ele à disposição. Implementação de estruturas de dados thread safe, semáforos de acesso a recursos, são exemplos de uso.

Já este exemplo abaixo é de concorrência com futures, usando a relativamente nova implementação de async/await, que também segue a filosofia de abstração com custo zero. Acho que este modelo dispensa qualquer introdução, por ter se popularizado tanto nos últimos anos, desde que foi implementado em F# e C# e mais recentemente em JavaScript.

Pensando bem, nos últimos anos, escrevendo um monte de C# e Node.js quase diariamente (e ocasionalmente algum Python com Async IO/ASGI nos finais de semana) este é o modelo que mais uso. É simples de ler código com async/await, fácil de entender, de explicar, não tem tempo ruim.

É bom ter mais de uma ferramenta à disposição e ser capaz de implementar mais de um modelo de concorrência, para então escolher o que melhor atende à tarefa em questão; e isso não é exclusivo, um ou outro. Em um sistema, pode haver uma combinação desses modelos que vimos. Aliás, esse é o mais provável.

A propósito, se você ainda se confunde um pouco com concorrência vs paralelismo, recomendo 2 minutos de leitura aqui.

Bem, apesar da minha reclamação sobre a verbosidade do message passing de Rust, no final do dia, a conta ainda fica positiva.

Odeio redefinição de variável

Uma coisa que odeio com todas as minhas forças é shadowing de variável.

Pqp, que p%#*@ é essa?!?!?!

Tá. Eu sei que shadowing não é o puro mal encarnado, tem lá sua razão de ser, blá, blá, blá…

I’m done.

Conclusão

Não existe bala de prata e isso você já deveria saber. Também não existe a linguagem perfeita e própria para todas as situações. O que existe são ferramentas em uma caixa; e o que se espera de você é que você saiba escolher a ferramenta certa, para o trabalho certo, na hora certa.

Eu tenho gostado bastante de Rust até então. Tenho tropeçado em alguns pontos aqui e ali, odiado uma coisa ou outra, mas no geral, estou muito satisfeito.

Até aqui.

Vagas para Ruby on Rails e iOS

Estamos com duas vagas no nosso time de desenvolvimento, aqui, na Urban Summer. Uma delas é para trabalhar com Ruby on Rails, em um startup muito bacana, e outra para trabalhar com iOS, em uma porção de projetos legais.

Como ja publiquei um descritivo completo das vagas no OndeTrabalhar, não vou chover no molhado aqui. É só dar um pulo lá e conferir.

Para essas vagas, não é possível trabalho remoto; que tem ser presencial mesmo, no nosso escritório, em Pinheiros/SP.

.NET 2015: Ótima notícia para usuários de Mac e Linux

Sou muito fã de .NET – curto muito C#, F# e ASP.NET MVC –, mas ter que bootar um Windão no meu Mac para poder mexer com isso nunca foi, absolutamente, algo que me agradou. Já tentei usar Mono algumas vezes, mas hmmmm acabou não rolando muito pra mim, então a solução do Windão no Parallels acabou sendo a menos inconveniente pra mim até hoje.

Mas agora há pouco li uma notícia que promete mudar isso de uma vez por todas:

Announcing .NET 2015 – .NET as Open Source, .NET on Mac and Linux, and Visual Studio Community

Pelo que diz o post, os caras estão construindo uma .NET Core CLR para Windows, Mac e Linux, totalmente open source e suportada pela Microsoft. E além disso, também, um web server chamado Kestrel, baseado em libuv, para rodar ASP.NET 5 no Mac e no Linux.

No que diz respeito a ferramentas de desenvolvimento, a galera que desenvolve o ASP.NET e web tools está trabalhando num projeto chamado OmniSharp, que prove “real intellisense & stuff” para SublimeText, Vim, Emacs, etc. Ou seja, vai melhorar bastante a experiência de desenvolvimento .NET em Mac e Linux.

So sweet.

Campus Code | Meu mais novo projeto

Yeah! Há quanto tempo não blogo nada sobre um novo projeto, hmm?! É só trabalho, trabalho, trabalho… Mas hoje a coisa vai ser diferente! Bem, ao menos em parte, porque a novidade também tem a ver com trabalho.

Sem mais delongas

Campus Code – esse é o nome do meu novo projeto, junto com meus amigos de longa data, Alan Batista e João Almeida.

Nosso objetivo com esse projeto é ensinar leigos a escreverem programas de computador de maneira rápida, prática, divertida e descontraída. Nossa meta é formar um programador júnior, munido do essencial para desenvolver aplicações web, pronto para pegar no batente, em tempo recorde.

Vamos ensinar não somente o b-a-bá da programação web, mas também uma série de “manhas” e “boas práticas de desenvolvimento de software” que nós levamos um bom tempo para aprender, lendo livros, vendo palestras, testando, batendo cabeça aqui e ali, fazendo “certo” em alguns projetos, “errado” em outros, e claro, codando, codando, codando e codando.

Mas, e quem não é leigo em programação, hein?!

Sim, sim, pois é… Não esquecemos dos mais experientes não! Estamos preparando novidades bem empolgantes para logo mais.

A maneira mais fácil e conveniente de ficar ligado nas novidades do Campus Code é curtindo nossa página no Facebook, nos seguindo no Twitter e, obviamente, dando uma chegada no nosso site.

Contratando programadores para mobile, front e back-end

Ultimamente só tenho postado sobre contratações, não? Já tem quase uma vida que não posto nada sobre tecnologia. Shame on me.

Mas vamos lá…

Estou contratando para o meu time na Urban Summer. Estou precisando de gente boa de mobile, front-end e de back-end; e se souber fazer tudo isso ao mesmo, wow!, muito melhor ainda.

Aqui, a gente mexe com Python/Django, Ruby on Rails, ASP.NET MVC, PHP, Java, iOS, PhoneGap, Titanium, e aquela coisa toda de web, sabe como é? HTML, CSS, JS e Cia. Ou seja, temos oportunidades para perfis variados.

Aliás, as posições são tanto para júnior, quanto pleno, sênior e até badass motherf*cker – hopefully. PJ, CLT, full-time e freela.

Dá uma saca no site da empresa, tenha uma idéia do clima que temos aqui, dos trabalhos que fazemos, dos nossos clientes, e me mande um e-mail, para trocarmos uma idéia: leandro@urbansummer.com.br.

Página nova e contratações

Virei a página outra vez….

Ontem assumi como diretor de tecnologia da Urban Summer, uma agência digital bem bacana, que tem feito trabalhos incríveis para uma porção de clientes legais. E como de costume, cá estou eu procurando por gente boa para trabalhar no meu time.

Tenho algumas vagas em aberto, que ainda vou postar aqui com mais detalhes, mas de cara, urgente, urgentíssimo, estou procurando por dois desenvolvedores client-side, como uns dois anos de experiência mais ou menos, com bastante habilidade em:

  • Recortar imagem – você pega um PSD e faz a mágica para transformar em página web
  • HTML
  • CSS
  • JavaScript

Se souber programar “razoavelmente bem” em Python, PHP, C#.NET, Objective-C, Java ou Ruby, que beleza, isso conta ponto a seu favor. Mas se não souber, tudo bem, não tem problema; esse não é um requisito obrigatório.

Se você não tiver interesse, mas souber de um camarada que tem, indique! Eu ficarei grato. Ele, certamente, também ficará – talvez ele até lhe pague um café.

Aguardo contato em leandro@urbansummer.com.br.

Valeu!

Contratando mais um desenvolvedor especialista em front-end

Estou procurando mais um desenvolvedor especialista em front-end para integrar meu time aqui na Pet Love.

No time de front-end, você será responsável por escrever HTML, CSS e JavaScript para nossa loja, que roda em plataforma Vtex, e trabalhar na melhoria contínua da experiência dos nossos usuários.

Perfil do candidato e da vaga

Você pode ser o candidato ideal se tiver experiência com:

  • HTML
  • CSS
  • JavaScript
  • JQuery
  • Twitter Bootstrap
  • SAAS / LESS
  • Google Tag Manager
  • Design responsivo
  • Recorte de imagens (Photoshop / Fireworks)

Se você tiver experiência com programação com Ruby, Java, C#, PHP, ou qualquer coisa assim, também é um baila ponto positivo, porque parte do trabalho é construir funcionalidades na loja usando nossa web API como fonte de dados.

Outro ponto muito positivo é se você tiver experiências com coisas como:

  • Google Web Master Tools
  • Google Analytics
  • SEO
  • Diagnóstico de gargalos de performance e otimização

Nosso objetivo é ter um site rápido, muito rápido, e agradável de se navegar.

Interessado em trabalhar com a gente nesse objetivo?

Remuneração, localização e como se candidatar

Todos aqui são CLT, o salário é competitivo, o horário é relativamente flexível, o ambiente descontraído, divertido, desafiador, e tem muito trabalho a ser feito.

Para se candidatar, basta mandar seu CV em PDF para leandro.silva@petlove.com.br e a gente bate um papo.  Mas, por tudo que há de mais importante nessa vida, só envie seu CV se você realmente acha que se enquadra no perfil da vaga.

Ah! Nosso escritório fica no WTC, próximo à estação de trem Berrine. Já falei isso?

Contratando mais um desenvolvedor experiente

Estou procurando mais um desenvolvedor experiente (não é vaga para estagiário ou júnior; não dessa vez) para integrar meu time na Pet Love, o maior petshop online do Brasil.

Interessado? Not yet, né?! OK. Vou falar um pouco da vaga então.

No meu time, você será responsável por manter e implementar novas funcionalidades em nosso sistema de compra recorrente, nos diversos web services de integração, além de escrever aplicações de monitoração, analise de dados, automatização de tarefas e também algumas extensões do nosso site.

Espero que você tenha experiência prática com:

  • C#
  • ASP.NET MVC
  • ASP.NET Web API (JSON, REST-like)
  • Windows Service / Console Application
  • SQL Server (Stored Procedure, View)
  • HTML / CSS / JavaScript / Twitter Bootstrap
  • Git

Mas que não se limite a isso. Quanto mais abrangente for seu conhecimento de desenvolvimento — o que inclui, diversidade de linguagens, paradigmas e técnicas de programação, bancos de dados, etc —, melhor, mais espaço você terá no time e mais oportunidades teremos de fazer coisas legais.

Aliás, espero que você:

  • Saiba trabalhar em equipe, ajudando o camarada ao lado e também pressionando ele quando for necessário
  • Consiga estimar esforço & tempo para realizar suas próprias tarefas
  • Gerencie seu tempo e não precise ser lembrado de que você tem trabalho para fazer e prazo para entregar
  • Use seu tempo de internet para estudar, aprender coisas diversas, se divertir e proliferar memes
  • Goste de e-commerce, tenha interesse por negócios on-line
  • Tenha muita vontade de fazer trabalhos decentes, 100% testados e terminados, e que lhe deem orgulho!

Não exatamente nessa ordem. 😉

O que tenho a lhe oferecer?

Salário CLT competitivo, horário relativamente flexível, ambiente descontraído, divertido, desafiador, aprendizado e muito trabalho a ser feito. Na verdade, uma pilha de trabalho a ser feito, então é bom que você goste mesmo de trabalhar.

E agora, interessado?

Se sim — e se você acha que é o candidato para esta vaga e não para outra vaga qualquer que possa existir —, mande-me seu CV em PDF para leandro.silva@petlove.com.br e vamos bater um papo. Quem sabe não nos acertamos?

Nosso escritório fica no WTC, próximo à estação de trem Berrine.