Há duas razões principais pelas quais gosto de Erlang:A VM Erlang é um sistema operacional para o seu códigoA língua Erlang incentiva o rigor na resolução de problemasEu vou discutir o segundo ponto aqui.Refatoração usando funçõesAqui está uma expressão que eu uso na vida:E se  alegria (Atividade)> = MinThreshold ->    fazer (Atividade);  verdadeiro ->    pule issofimEste é um exemplo da expressão se de Erlang. A cláusula verdadeira é efetivamente a cláusula else em outros idiomas - ela sempre corresponde e, portanto, é avaliada quando as cláusulas anteriores são falsas.Se você achar isso sintaticamente difícil de processar, você não está sozinho.Além de ilustrar o ponto que você pode querer evitar se expressar em Erlang, esses exemplos servem a um propósito maior: é um homem de palha que podemos refatorar para tornar as coisas óbvias.Aqui está outra versão, que substitui a expressão if por uma cadeia de chamadas de função:maybe_do (  if_standard_met (joy (Activity), MinThreshold),  Atividade)Esta versão é equivalente à primeira, mas formaliza a lógica da “comparação de alegria” usando a função if_standard_met. Essa lógica é definida na primeira versão como semântica de linguagem usando uma expressão if. Nesta versão, aparece como lógica de aplicação.Aqui está o processo que usei para o refator:Eu li a primeira versão e perguntei: "O que está realmente acontecendo aqui?"Depois de pensar, concluí que “algum padrão está sendo aplicado a uma atividade”Usando funções, eu escrevi o que eu pensava que estava acontecendoEu li a versão final algumas vezes e fiquei ainda mais convencido de sua verdadeEssa técnica - decompondo funções complexas em funções constituintes menores - pode ser usada para reformular problemas difíceis de resolver em uma série de problemas embaraçosamente óbvios. Erlang, em particular, presta-se bem a isso, mas o método pode ser aplicado a qualquer idioma.Mitos Sobre ErlangNo vídeo vencedor do Prêmio Peabody, The Ghetto, o protagonista é confrontado com vários obstáculos terríveis que se interpõem entre ele e a tecnologia mítica que ele procura.Alguns obstáculos que podem ser enfrentados em Erlang incluem:Variáveis ​​de atribuição única (imutáveis)Sem loopsNenhuma declaração de retornoBizarro se expressõesEu me pergunto quantos desenvolvedores fugiram em horror, tendo encontrado estes em sua jornada para usar Erlang? É uma pena, porque cada um desses obstáculos é, na verdade, um recurso que os ajudaria a se tornarem melhores desenvolvedores de software!A essência é esta:Os problemas apresentados por cada obstáculo podem ser resolvidos usando funçõesSe você não usa funções para superar esses obstáculos, seu código parecerá horrível, até mesmo hediondoSe você se esforçar para escrever funções que descrevam claramente seus problemas, seu código ficará ótimoEm Erlang, grandes códigos são sempre ótimos, enquanto códigos terríveis são sempre terríveisSe você adotar essa One True Religion, descobrirá algo incrível: geralmente, você pode identificar códigos de qualidade observando uma única métrica - linhas de código por função.Se as suas funções tiverem mais do que algumas linhas de código (um máximo de quatro a cinco linhas por função é um bom benchmark), você precisa olhar mais de perto - provavelmente você terá a oportunidade de incorporá-las em funções menores e mais focadas. .Mas por que?As funções descrevem o problema que você está resolvendo. Se suas funções contiverem muitas linhas, provavelmente você está resolvendo vários problemas sem articulá-las. Se você aproveitar a oportunidade para articulá-las, precisará pensar com rigor.Pensar rigorosamente sobre problemas é o processo de desenvolvimento de software.Você sabe que está pronto quando não há mais nada para pensar - o problema que você está resolvendo se torna tediosamente trivial e o código está obviamente correto.Religião verdadeira.Um Exemplo da Vida Real - Código Terrível de FactoringEra uma vez, eu corri através do código que eu tinha escrito um ano antes. Eu obviamente não tinha religião quando escrevi. É fácil reconhecer um código terrível porque é impossível entender rapidamente. Tente:handle_amqp (#message {name = "db.create"} = Msg, Estado) ->  log: info ({db_create, serviço: to_proplist (Msg)}),  Name = get_required_attr ("nome", Msg),  verify_db_name (nome),  Usuário = get_required_attr ("user", Msg),  Pwd = get_required_attr ("password", Msg),  Opts    case get_attr ("cluster", Msg) de      indefinido -> [];      Cluster -> [{cluster, cluster}]    fim,  case mysql_controller: create_db (nome, usuário, Pwd, opts) de    {ok, HostInfo} ->      Attrs = [{"slaves", ""} | host_info_attrs (HostInfo)],      {reply, message_response (Msg, Attrs), estado};    {error, Err} ->      log: erro ({db_create, Err, erlang: get_stacktrace ()})      {error, Err, State}  fim.Esse código pode funcionar, mas não faço ideia - precisaria submetê-lo a uma série de testes ou executá-lo em produção por um tempo. Pior, eu não posso saber se esse código está correto - eu nem sei o que significa fazer!Embora eu ache que esse código é "limpo" sintaticamente (não faz meus olhos sangrarem como o código Erlang pode), o problema não está na sintaxe - é com o significado. Para entender o código, preciso fazer algum trabalho.Que grande oportunidade em uma tarde de domingo para pensar com rigor!Passo 1 - O que está acontecendo aqui?Eu vou fatorar isso usando uma decomposição de cima para baixo. A mecânica de codificação é muito simples: descreva o que está acontecendo da forma mais clara possível. Como veremos de novo e de novo, o trabalho aqui consiste em responder a pergunta: o que realmente está acontecendo aqui?Então, o que realmente está acontecendo com essa função terrível?Simples: estou lidando com um tipo de mensagem específico - "db.create".Então vamos fazer isso:handle_amqp (#message {name = "db.create"} = Msg, Estado) ->  handle_db_create_msg (Msg, estado).Feito!A sério. Eu acabei por aqui! Eu resolvi meu problema completamente e inequivocamente. Não há debate. Não há cabeça arranhando. Eu nem preciso testar isso, estou completamente pronto!Etapa 2 - Gerenciando “DB Create”Ok, não terminei. Eu preciso lidar com a mensagem "DB Create", o que isso significa.Movendo o código original para uma função separada, tenho isto:handle_db_create_msg (Msg, estado) ->  log: info ({db_create, serviço: to_proplist (Msg)}),  Name = get_required_attr ("nome", Msg),  verify_db_name (nome),  Usuário = get_required_attr ("user", Msg),  Pwd = get_required_attr ("password", Msg),  Opts    case get_attr ("cluster", Msg) de      indefinido -> [];      Cluster -> [{cluster, cluster}]    fim,  case mysql_controller: create_db (nome, usuário, Pwd, opts) de    {ok, HostInfo} ->      Attrs = [{"slaves", ""} | host_info_attrs (HostInfo)],      {reply, message_response (Msg, Attrs), estado};    {error, Err} ->      log: erro ({db_create, Err, erlang: get_stacktrace ()}),      {error, Err, State}  fim.Oque esta acontecendo aqui?Deixe-me pensar um momento em voz alta:Primeiro eu registro a operação pendenteEntão eu coleciono e valido entrada da mensagemEntão eu crio o banco de dadosEntão eu cuido do resultadoEntão, afinal de contas, isso é bem simples - mas você não saberia sem ler o código com cuidado! Vamos usar funções para torná-lo completamente óbvio.Depois de alguns ajustes e algumas falhas de ignição eu descobri isso:handle_db_create_msg (Msg, estado) ->  log_operation (db_create, Msg),  Args = db_create_args (Msg),  Resultado = db_create (Args),  handle_db_create_result (Resultado, Msg, Estado).Tire um momento e compare-o com o meu exercício de “pensar alto” acima. É exatamente o que está acontecendo - representado por quatro funções distintas, fáceis de ler e significativas.Engraçado, esse pequeno código funcionou para acertar! É uma daquelas "letras curtas" que nós realmente demoramos.Mas eu terminei!Etapa 3 - Registro em logNão realizado. Eu preciso de uma função log_operation.log_operation (Type, Msg) ->  log: info ({tipo, serviço: to_proplist (Msg)}).Eu poderia facilmente passar sem essa função, mas são duas linhas de código que fazem com que "registre uma operação" seja óbvio onde quer que você a veja. Caso contrário, você estaria lendo "registro de chamadas: informações com alguns argumentos, espera, quais são esses argumentos novamente, lembro-me disso de antes, porcaria" a cada vez.Etapa 4 - convertendo a mensagem "DB Create" em ArgsEm seguida, implementaremos db_create_args.Veja o que eu tenho, principalmente copiado da função original:db_create_args (Msg) ->  Name = get_required_attr ("nome", Msg),  verify_db_name (nome),  Usuário = get_required_attr ("user", Msg),  Pwd = get_required_attr ("password", Msg),  Opts    case get_attr ("cluster", Msg) de      indefinido -> [];      Cluster -> [{cluster, cluster}]    fim,  [Nome, Usuário, Pwd, Opts].Olhando para a última linha, essa função retorna uma lista de argumentos que podem ser usados ​​downstream para criar o banco de dados.Mas é muito código para uma função! É demais ler e processar de uma só vez. Vamos usar funções para fazer isso obviamente correto.Oque esta acontecendo aqui? Acontece que não muito:db_create_args (Msg) ->  [db_name (Msg),   db_user (Msg),   db_password (Msg),   db_create_opts (Msg)].Demora cerca de dois segundos para escanear a função e encolher de ombros. Nenhum estrabismo ou cabeça arranhando. Muito óbvio. Se movendo.Aqui estão as funções arg-for-message:db_name (Msg) ->  verify_db_name (get_required_attr ("nome", Msg)).db_user (Msg) ->  get_required_attr ("usuário", Msg).db_password (Msg) ->    get_required_attr ("password", Msg).db_create_opts (Msg) ->  case get_attr ("cluster", Msg) de    indefinido -> [];    Cluster -> [{cluster, cluster}]  fim.Por que toda a cerimônia? O que há de errado com a versão anterior - aquela em que toda a lógica está flutuando em um bloco monolítico de código?É apenas errado no sentido moral - isso é uma religião! Lembre-se, nós estamos atrás de vergonhosamente óbvio. Cada função arg-for-message dá uma olhada rápida e é tão anticlimática que dificilmente vale a pena ler. Esse não é o caso do monolito, que requer análise de linguagem e inferência antes mesmo de começar a raciocinar sobre conceitos elevados como correção.Mas espere um minuto, essa expressão de caso em db_create_options está me incomodando. Eu aposto que há alguma lógica escondida lá que eu não tenho pensado.O que está acontecendo lá? Eu li isso: se um "cluster" é especificado na mensagem, eu quero fornecer esse cluster como uma opção. São realmente opções de cluster.Então vamos fazer assim:db_create_opts (Msg) ->  db_create_cluster_opts (get_attr ("cluster", Msg)).db_create_cluster_opts (undefined) -> [];db_create_cluster_opts (Cluster) -> [{cluster, cluster}].Este é indiscutivelmente um foco extremo em minúcias. Honestamente, na prática, eu poderia ter pulado este. Mas aqui está a diferença:A primeira versão - a que usa uma expressão de caso - pede que você primeiro processe a semântica da linguagem Erlang (expressão de caso), então inferir o significado (o que a expressão de caso está fazendo), então considere a lógica da aplicação.A segunda versão - a que usa uma função cuidadosamente nomeada - pede apenas que você considere a lógica da aplicação. Sim, você precisa ler a função e pausar por um momento para considerar o que significa “db criar opções de cluster” - e esse é precisamente o ponto! Uma vez que a lógica clica, é tão chato e óbvio que você é obrigado a passar para tópicos mais interessantes.Etapa 5 - criar o banco de dadosEm seguida é db_create:db_create ([Name, User, Pwd, Opts]) ->  mysql_controller: create_db (nome, usuário, Pwd, opts).Isso é simplesmente uma passagem para a chamada de função externa.Embora possamos viver sem essa indireção, ela fornece um papel importante para tornar as coisas óbvias. As entradas para essa operação são criadas por db_create_args e o resultado é manipulado por handle_db_create_result. A função db_create é parte lógica desse vocabulário. Sim, ele oculta os detalhes da implementação do uso do mysql_controller. Mas o principal benefício aparece na clareza que vemos em handle_db_create_msg. Então, nós gastamos uma linha extra de código.Em nossa Religião Única e Verdadeira, não é pecado trocar toques-chave, que são baratos, por clareza, o que é precioso.Etapa 6 - Manipular o resultado da criação do banco de dadosA maioria dos desenvolvedores Erlang usa expressões de casos para lidar com resultados de funções. Não há nada de errado com isso:case read_something () de  {ok, Result} -> Resultado;  {erro, erro} -> erro (erro)fimMas agora, curioso, você pergunta: "O que realmente está acontecendo aqui?"Bem, estou traduzindo o resultado de read_something em outra coisa.Então direi que:translate_read_something (read_something ())E então faça:translate_read_something ({ok, Result}) -> Resultado;translate_read_something ({error, Error}) -> erro (erro).Se você usa expressões de casos para fazer algo, está perdendo a oportunidade de nomear algo assim. Use uma função. Você ficará chocado ao descobrir uma lógica importante e oculta durante o processo de nomenclatura.É claro que translate_read_something é um nome terrível - você quer esclarecer o que é algo - e esse é precisamente o ponto! Quando você usa as funções dessa maneira, você é forçado a lidar com nomes. Quando algo não está claro em um nome, não está claro em seu cérebro.Neste exemplo, estamos lidando com o resultado de uma função. Nesses casos, eu costumo usar uma convenção de nomenclatura handle_xxx - então algo como handle_read_result.Com esse pano de fundo, eis o que eu tenho para o manipulador de resultados db_create:handle_db_create_result ({ok, HostInfo}, Msg, estado) ->  Attrs = [{"slaves", ""} | host_info_attrs (HostInfo)],  {reply, message_response (Msg, Attrs), estado};handle_db_create_result ({error, Err}, _Msg, State) ->  log: erro ({db_create, Err, erlang: get_stacktrace ()}),  {error, Err, State}.Essa é uma refatoração mecânica da expressão de caso na função handle_amqp original. Não é ruim - apenas algumas linhas de código. Mas tire um momento para ler o código. É óbvio? Ou se parece mais com uma imagem estereoscópica onde, se você relaxar o seu olhar, um Jesus tridimensional surge da tela?Oque esta acontecendo aqui?Existem dois cenários: um caso ok em que a operação de criação é bem-sucedida e um caso de erro em que a operação de criação falha.Então, digamos que:handle_db_create_result ({ok, HostInfo}, Msg, estado) ->  handle_db_created (HostInfo, Msg, Estado);handle_db_create_result ({error, Err}, _Msg, State) ->  handle_db_create_error (Err, State).Primeiro, nosso manipulador de casos de sucesso:handle_db_created (HostInfo, Msg, Estado) ->  Attrs = [{"slaves", ""} | host_info_attrs (HostInfo)],  {reply, message_response (Msg, Attrs), State}.Não tem muito código, mas o código é estranho. O que está acontecendo com o atributo "escravos"? Não tenho certeza, na verdade. Lembre-se, este é um código real que estou refatorando - escrevi isso um ano antes e não me lembro por que está lá.Ah, eu lembro - está lá para manter um contrato de nível de serviço! A API original incluía uma lista de “escravos” (um infeliz anacronismo ainda usado em muitas aplicações de banco de dados). A nova API não. Em vez de apenas descartar as informações, estou fornecendo um valor vazio.Mas acho que é uma falha: não é óbvio que eu esteja implementando uma camada de compatibilidade com versões anteriores. Claro, eu poderia adicionar um comentário, mas por que não apenas tornar o código óbvio?Primeiro, vamos reescrever nosso manipulador:handle_db_created (HostInfo, Msg, Estado) ->  Attrs = db_created_response_attrs (HostInfo),  {reply, message_response (Msg, Attrs), State}.Sem um olhar mais atento, você pode ter perdido a mudança: nós convertemos a lógica implícita do aplicativo “construa a resposta criada pelo banco de dados attrs” em uma função que nomeie essa lógica explicitamente.Aqui está essa função:db_created_response_attrs (HostInfo) ->  db_created_legacy_attrs (host_info_attrs (HostInfo)).Olha lá! O comportamento legado é agora uma parte oficial da implementação, em vez de derivar a esmo em uma expressão sem nome. Se algum dia esquecer essa parte esquisita da lógica, preciso apenas dar uma olhada no código. E enquanto eu poderia fazer uma pausa para pensar sobre a sensibilidade dessa lógica - esse é precisamente o ponto! Nosso objetivo é desviar o fardo mental da análise da linguagem e inferir o raciocínio direto sobre a lógica da aplicação.Aqui está a implementação totalmente desinteressante desse suporte legado:db_created_legacy_attrs (Attrs) -> [{"slaves", ""} | Attrs].E finalmente, nosso manipulador de caso de erro:handle_db_create_error (Err, State) ->  log: erro ({db_create, Err, erlang: get_stacktrace ()}),  {error, Err, State}.Embora esse código pareça inocente, existe um bug! Eu não percebi isso antes porque quando vejo uma parede de código, digo para mim mesmo: “Uau, olhe para todo esse código. Eu me pergunto o que isso faz? Funciona? Eu aposto que sim. Se houvesse um bug, teríamos pegado nos testes de unidade. Fico feliz por termos uma ótima cobertura de teste. Olhe para todo esse código. Arrumado!"Mas quando você está olhando para uma ou duas linhas de código em uma função cuidadosamente nomeada, os bugs são mais fáceis de detectar.Então, oque há de errado?Olhe aterlang: get_stracktrace () - em um relance casual, parece que ele retorna o rastreamento da pilha atual. Isso significa que vou registrar o rastreamento de pilha da operação atual? Por que eu me importo com isso?Lendo os documentos forerlang: get_stacktrace / 0 Eu vejo:Obtenha o rastreio da pilha de chamadas (stacktrace) da última exceção no processo de chamada… Se não houver exceções em um processo, o stacktrace será [].Isso certamente não é o que eu quero! Eu não estou lidando com uma exceção nesta função, então o resultado é [] ou alguma exceção aleatória de uma operação anterior!Eu suspeito que esse código pode ter vivido dentro de um bloco catch de exceção, o que teria feito sentido. Mas em algum refator anterior, ele foi perdido em meio à expansão do código ao redor. Um sistema de tipos teria percebido isso? Não é provável. Um linter? Não é provável. Este é realmente um problema endêmico para exceções como um recurso de linguagem! Mas com o código despojado de seu ruído, meu cérebro teve uma chance melhor de capturá-lo.É isso que eu quero:log: erro ({tipo, erro})E nós terminamos! Na verdade, verdadeiramente feito!Resumo do RefatorAqui está a função original e terrível:handle_amqp (#message {name = "db.create"} = Msg, Estado) ->  log: info ({db_create, serviço: to_proplist (Msg)}),  Name = get_required_attr ("nome", Msg),  verify_db_name (nome),  Usuário = get_required_attr ("user", Msg),  Pwd = get_required_attr ("password", Msg),  Opts    case get_attr ("cluster", Msg) de      indefinido -> [];      Cluster -> [{cluster, cluster}]    fim,  case mysql_controller: create_db (nome, usuário, Pwd, opts) de    {ok, HostInfo} ->      Attrs = [{"slaves", ""} | host_info_attrs (HostInfo)],      {reply, message_response (Msg, Attrs), estado};    {error, Err} ->      elog: erro ({db_create, Err, erlang: get_stacktrace ()}),      {error, Err, State}fim.Este é o código refatorado:handle_amqp (#message {name = "db.create"} = Msg, Estado) ->  handle_db_create_msg (Msg, estado).handle_db_create_msg (Msg, estado) ->  log_operation (db_create, Msg),  Args = db_create_args (Msg),  Resultado = db_create (Args),  handle_db_create_result (Resultado, Msg, Estado).log_operation (Type, Msg) ->  log: info ({tipo, serviço: to_proplist (Msg)}).db_create_args (Msg) ->  [db_name (Msg),   db_user (Msg),   db_password (Msg),   db_create_opts (Msg)].db_name (Msg) ->  verify_db_name (get_required_attr ("nome", Msg)).db_user (Msg) ->  get_required_attr ("usuário", Msg).db_password (Msg) ->  get_required_attr ("password", Msg).db_create_opts (Msg) ->  db_create_cluster_opts (get_attr ("cluster", Msg)).db_create_cluster_opts (undefined) -> [];db_create_cluster_opts (Cluster) -> [{cluster, cluster}].db_create (nome, usuário, Pwd, opts) ->  mysql_controller: create_db (nome, usuário, Pwd, opts).handle_db_create_result ({ok, HostInfo}, Msg, estado) ->  handle_db_created (HostInfo, Msg, Estado);handle_db_create_result ({error, Err}, _Msg, State) ->  handle_db_create_error (Err, State).handle_db_created (HostInfo, Msg, Estado) ->  Attrs = db_created_response_attrs (HostInfo),  {reply, message_response (Msg, Attrs), State}.db_created_response_attrs (HostInfo) ->  db_created_legacy_attrs (host_info_attrs (HostInfo)).db_created_legacy_attrs (Attrs) -> [{"slaves", ""} | Attrs].handle_db_create_error (Err, State) ->  log: error ({Type, Err}),  {error, Err, State}.Whooooa! Isto é melhor?? De jeito nenhum no Hades isso é melhor!Código "terrível" original:Funções: 1Linhas de Código: 19Média de linhas por função: 19Código “óbvio” refatorado:Funções: 15Linhas de código: 39Média de linhas por função: 2,6Código bloat! 19 linhas para 39 linhas! "Demitido, você twit!"Whoa, cozinhe lá em baixo Sparky.Na verdade, reduzimos as linhas de 19 para apenas 5 - uma redução de 4x!Esta é a nossa nova função - o que estamos realmente fazendo:handle_db_create_msg (Msg, estado) ->  log_operation (db_create, Msg),  Args = db_create_args (Msg),  Resultado = db_create (Args),  handle_db_create_result (Resultado, Msg, Estado).Nossa contagem total de linhas aumentou em 2x. Isso é ruim se você argumentar que a contagem de linhas se correlaciona positivamente com a contagem de erros, o que, é claro, deve ser se todo o resto for constante. Mas neste caso, há mais na história - pensamos rigorosamente sobre o que estamos fazendo e garantimos que nosso código reflita esse trabalho.Nossas linhas adicionais de código são pagas em espadas, tornando o referido código embaraçosamente óbvio.Custo e BenefícioHá um custo claro para essa abordagem: tempo gasto tornando as coisas óbvias.É verdade - muito pensamento e esforço foram dedicados a esse processo - e esse é precisamente o ponto!Aqui estão alguns dos benefícios:A intenção do programador é clara, ajudando você a identificar o raciocínio incorreto mais cedoProgramadores introduzem menos bugs inicialmenteO teste é mais focado e mais fácil de raciocinar sobreO código é mais fácil de depurar porque erros ocorrem em um contexto coerente e bem definidoA melhor maneira de apreciar esses benefícios é tentar esse método e executá-lo durante um período de meses. Mas comece uma vez e veja como se sente.Há outro benefício que você desfrutará e que é menos óbvio: em primeiro lugar, você vai melhorar e acelerar a criação de código óbvio.Muitas vezes o processo de programação é começar com uma noção difusa do que você está fazendo, tentar algo, falhar, tentar outra coisa, ficar frustrado, reprovar mais, ficar realmente frustrado, experimentar mais um pouco, começar a entender melhor o problema, Revise o problema, tente um pouco mais, obtenha algum sucesso, falhe novamente, tome um Valium, repita este processo N vezes e depois, bam! - está funcionando. E então você aplica isso para tornar o método de refatoração mais óbvio para codificar seu glorioso código de trabalho, de modo que você e os outros se sintam totalmente apáticos, porque isso é tão aborrecido e claramente claro.Mas se você fizer o mais tardar - o trabalho embaraçosamente óbvio - você se tornará um programador muito melhor. Você ficará mais rápido ao identificar códigos incoerentes. Com o tempo, você ficará mais rápido ao traduzir códigos incoerentes em algo simples e claro. Em mais tempo, você começará a escrever imediatamente esse código simples e claro - porque seu cérebro muda. É como aprender uma língua. Em algum momento, com prática suficiente, você pára de pensar em falar essa língua e apenas começa a usá-la.E então toda a discussão de custo-benefício é deixada de lado - tudo é benefício.ResumoUma das melhores qualidades de Erlang é que é fácil identificar códigos terríveis - basta procurar as funções longasVocê pode escrever um ótimo código, misturando código terrível em pedaços cada vez menores, cada um obviamente correto, simplesmente perguntando "o que está acontecendo aqui" - "o que realmente está acontecendo?" Até saber exatamente o que está acontecendo, em todos os lugaresObviamente, o código correto terá menos bugsObviamente, o código correto é mais fácil de consertar quando ele tem bugs (relaxando por um momento nossa definição de correto - isto é uma religião para que possamos fazer isso)O teste é similarmente um processo de busca por clarezaSe você praticar esse método, será melhor e mais rápido escrever códigos embaraçosamente óbvios diretamenteUm pensamento final.As pessoas que se queixam da hedionda sintaxe de Erlang estão escrevendo longas funções com muitas atribuições de variáveis, declarações de caso, declarações if - tentando forçar um estilo imperativo de programação em uma linguagem funcional.Você pode ser tentado a culpar Erlang. Mas você sabe melhor agora.Vamos deixar nossos julgamentos de lado, para quem entre nós está sem código terrível? Nosso amor pelo óbvio e pelo correto deve emanar como um farol de luz. Nossa missão deve permanecer sempre clara: implacavelmente procurar o óbvio, e depois fazer o óbvio, até que apenas o óbvio permaneça.

Resolvendo problemas embaraçosamente óbvios em Erlang

Há duas razões principais pelas quais gosto de Erlang:

A VM Erlang é um sistema operacional para o seu código
A língua Erlang incentiva o rigor na resolução de problemas
Eu vou discutir o segundo ponto aqui.

Refatoração usando funções
Aqui está uma expressão que eu uso na vida:

E se
alegria (Atividade)> = MinThreshold ->
fazer (Atividade);
verdadeiro ->
pule isso
fim
Este é um exemplo da expressão se de Erlang. A cláusula verdadeira é efetivamente a cláusula else em outros idiomas – ela sempre corresponde e, portanto, é avaliada quando as cláusulas anteriores são falsas.

Se você achar isso sintaticamente difícil de processar, você não está sozinho.

Além de ilustrar o ponto que você pode querer evitar se expressar em Erlang, esses exemplos servem a um propósito maior: é um homem de palha que podemos refatorar para tornar as coisas óbvias.

Aqui está outra versão, que substitui a expressão if por uma cadeia de chamadas de função:

maybe_do (
if_standard_met (joy (Activity), MinThreshold),
Atividade)
Esta versão é equivalente à primeira, mas formaliza a lógica da “comparação de alegria” usando a função if_standard_met. Essa lógica é definida na primeira versão como semântica de linguagem usando uma expressão if. Nesta versão, aparece como lógica de aplicação.

Aqui está o processo que usei para o refator:

Eu li a primeira versão e perguntei: “O que está realmente acontecendo aqui?”
Depois de pensar, concluí que “algum padrão está sendo aplicado a uma atividade”
Usando funções, eu escrevi o que eu pensava que estava acontecendo
Eu li a versão final algumas vezes e fiquei ainda mais convencido de sua verdade
Essa técnica – decompondo funções complexas em funções constituintes menores – pode ser usada para reformular problemas difíceis de resolver em uma série de problemas embaraçosamente óbvios. Erlang, em particular, presta-se bem a isso, mas o método pode ser aplicado a qualquer idioma.

Mitos Sobre Erlang
No vídeo vencedor do Prêmio Peabody, The Ghetto, o protagonista é confrontado com vários obstáculos terríveis que se interpõem entre ele e a tecnologia mítica que ele procura.

Alguns obstáculos que podem ser enfrentados em Erlang incluem:

Variáveis ​​de atribuição única (imutáveis)
Sem loops
Nenhuma declaração de retorno
Bizarro se expressões
Eu me pergunto quantos desenvolvedores fugiram em horror, tendo encontrado estes em sua jornada para usar Erlang? É uma pena, porque cada um desses obstáculos é, na verdade, um recurso que os ajudaria a se tornarem melhores desenvolvedores de software!

A essência é esta:

Os problemas apresentados por cada obstáculo podem ser resolvidos usando funções
Se você não usa funções para superar esses obstáculos, seu código parecerá horrível, até mesmo hediondo
Se você se esforçar para escrever funções que descrevam claramente seus problemas, seu código ficará ótimo
Em Erlang, grandes códigos são sempre ótimos, enquanto códigos terríveis são sempre terríveis
Se você adotar essa One True Religion, descobrirá algo incrível: geralmente, você pode identificar códigos de qualidade observando uma única métrica – linhas de código por função.

Se as suas funções tiverem mais do que algumas linhas de código (um máximo de quatro a cinco linhas por função é um bom benchmark), você precisa olhar mais de perto – provavelmente você terá a oportunidade de incorporá-las em funções menores e mais focadas. .

Mas por que?

As funções descrevem o problema que você está resolvendo. Se suas funções contiverem muitas linhas, provavelmente você está resolvendo vários problemas sem articulá-las. Se você aproveitar a oportunidade para articulá-las, precisará pensar com rigor.

Pensar rigorosamente sobre problemas é o processo de desenvolvimento de software.

Você sabe que está pronto quando não há mais nada para pensar – o problema que você está resolvendo se torna tediosamente trivial e o código está obviamente correto.

Religião verdadeira.

Um Exemplo da Vida Real – Código Terrível de Factoring
Era uma vez, eu corri através do código que eu tinha escrito um ano antes. Eu obviamente não tinha religião quando escrevi. É fácil reconhecer um código terrível porque é impossível entender rapidamente. Tente:

handle_amqp (#message {name = “db.create”} = Msg, Estado) ->
log: info ({db_create, serviço: to_proplist (Msg)}),
Name = get_required_attr (“nome”, Msg),
verify_db_name (nome),
Usuário = get_required_attr (“user”, Msg),
Pwd = get_required_attr (“password”, Msg),
Opts
case get_attr (“cluster”, Msg) de
indefinido -> [];
Cluster -> [{cluster, cluster}]
fim,
case mysql_controller: create_db (nome, usuário, Pwd, opts) de
{ok, HostInfo} ->
Attrs = [{“slaves”, “”} | host_info_attrs (HostInfo)],
{reply, message_response (Msg, Attrs), estado};
{error, Err} ->
log: erro ({db_create, Err, erlang: get_stacktrace ()})
{error, Err, State}
fim.
Esse código pode funcionar, mas não faço ideia – precisaria submetê-lo a uma série de testes ou executá-lo em produção por um tempo. Pior, eu não posso saber se esse código está correto – eu nem sei o que significa fazer!

Embora eu ache que esse código é “limpo” sintaticamente (não faz meus olhos sangrarem como o código Erlang pode), o problema não está na sintaxe – é com o significado. Para entender o código, preciso fazer algum trabalho.

Que grande oportunidade em uma tarde de domingo para pensar com rigor!

Passo 1 – O que está acontecendo aqui?
Eu vou fatorar isso usando uma decomposição de cima para baixo. A mecânica de codificação é muito simples: descreva o que está acontecendo da forma mais clara possível. Como veremos de novo e de novo, o trabalho aqui consiste em responder a pergunta: o que realmente está acontecendo aqui?

Então, o que realmente está acontecendo com essa função terrível?

Simples: estou lidando com um tipo de mensagem específico – “db.create”.

Então vamos fazer isso:

handle_amqp (#message {name = “db.create”} = Msg, Estado) ->
handle_db_create_msg (Msg, estado).
Feito!

A sério. Eu acabei por aqui! Eu resolvi meu problema completamente e inequivocamente. Não há debate. Não há cabeça arranhando. Eu nem preciso testar isso, estou completamente pronto!

Etapa 2 – Gerenciando “DB Create”
Ok, não terminei. Eu preciso lidar com a mensagem “DB Create”, o que isso significa.

Movendo o código original para uma função separada, tenho isto:

handle_db_create_msg (Msg, estado) ->
log: info ({db_create, serviço: to_proplist (Msg)}),
Name = get_required_attr (“nome”, Msg),
verify_db_name (nome),
Usuário = get_required_attr (“user”, Msg),
Pwd = get_required_attr (“password”, Msg),
Opts
case get_attr (“cluster”, Msg) de
indefinido -> [];
Cluster -> [{cluster, cluster}]
fim,
case mysql_controller: create_db (nome, usuário, Pwd, opts) de
{ok, HostInfo} ->
Attrs = [{“slaves”, “”} | host_info_attrs (HostInfo)],
{reply, message_response (Msg, Attrs), estado};
{error, Err} ->
log: erro ({db_create, Err, erlang: get_stacktrace ()}),
{error, Err, State}
fim.
Oque esta acontecendo aqui?

Deixe-me pensar um momento em voz alta:

Primeiro eu registro a operação pendente
Então eu coleciono e valido entrada da mensagem
Então eu crio o banco de dados
Então eu cuido do resultado
Então, afinal de contas, isso é bem simples – mas você não saberia sem ler o código com cuidado! Vamos usar funções para torná-lo completamente óbvio.

Depois de alguns ajustes e algumas falhas de ignição eu descobri isso:

handle_db_create_msg (Msg, estado) ->
log_operation (db_create, Msg),
Args = db_create_args (Msg),
Resultado = db_create (Args),
handle_db_create_result (Resultado, Msg, Estado).
Tire um momento e compare-o com o meu exercício de “pensar alto” acima. É exatamente o que está acontecendo – representado por quatro funções distintas, fáceis de ler e significativas.

Engraçado, esse pequeno código funcionou para acertar! É uma daquelas “letras curtas” que nós realmente demoramos.

Mas eu terminei!

Etapa 3 – Registro em log
Não realizado. Eu preciso de uma função log_operation.

log_operation (Type, Msg) ->
log: info ({tipo, serviço: to_proplist (Msg)}).
Eu poderia facilmente passar sem essa função, mas são duas linhas de código que fazem com que “registre uma operação” seja óbvio onde quer que você a veja. Caso contrário, você estaria lendo “registro de chamadas: informações com alguns argumentos, espera, quais são esses argumentos novamente, lembro-me disso de antes, porcaria” a cada vez.

Etapa 4 – convertendo a mensagem “DB Create” em Args
Em seguida, implementaremos db_create_args.

Veja o que eu tenho, principalmente copiado da função original:

db_create_args (Msg) ->
Name = get_required_attr (“nome”, Msg),
verify_db_name (nome),
Usuário = get_required_attr (“user”, Msg),
Pwd = get_required_attr (“password”, Msg),
Opts
case get_attr (“cluster”, Msg) de
indefinido -> [];
Cluster -> [{cluster, cluster}]
fim,
[Nome, Usuário, Pwd, Opts].
Olhando para a última linha, essa função retorna uma lista de argumentos que podem ser usados ​​downstream para criar o banco de dados.

Mas é muito código para uma função! É demais ler e processar de uma só vez. Vamos usar funções para fazer isso obviamente correto.

Oque esta acontecendo aqui? Acontece que não muito:

db_create_args (Msg) ->
[db_name (Msg),
db_user (Msg),
db_password (Msg),
db_create_opts (Msg)].
Demora cerca de dois segundos para escanear a função e encolher de ombros. Nenhum estrabismo ou cabeça arranhando. Muito óbvio. Se movendo.

Aqui estão as funções arg-for-message:

db_name (Msg) ->
verify_db_name (get_required_attr (“nome”, Msg)).
db_user (Msg) ->
get_required_attr (“usuário”, Msg).
db_password (Msg) ->
get_required_attr (“password”, Msg).
db_create_opts (Msg) ->
case get_attr (“cluster”, Msg) de
indefinido -> [];
Cluster -> [{cluster, cluster}]
fim.
Por que toda a cerimônia? O que há de errado com a versão anterior – aquela em que toda a lógica está flutuando em um bloco monolítico de código?

É apenas errado no sentido moral – isso é uma religião! Lembre-se, nós estamos atrás de vergonhosamente óbvio. Cada função arg-for-message dá uma olhada rápida e é tão anticlimática que dificilmente vale a pena ler. Esse não é o caso do monolito, que requer análise de linguagem e inferência antes mesmo de começar a raciocinar sobre conceitos elevados como correção.

Mas espere um minuto, essa expressão de caso em db_create_options está me incomodando. Eu aposto que há alguma lógica escondida lá que eu não tenho pensado.

O que está acontecendo lá? Eu li isso: se um “cluster” é especificado na mensagem, eu quero fornecer esse cluster como uma opção. São realmente opções de cluster.

Então vamos fazer assim:

db_create_opts (Msg) ->
db_create_cluster_opts (get_attr (“cluster”, Msg)).
db_create_cluster_opts (undefined) -> [];
db_create_cluster_opts (Cluster) -> [{cluster, cluster}].
Este é indiscutivelmente um foco extremo em minúcias. Honestamente, na prática, eu poderia ter pulado este. Mas aqui está a diferença:

A primeira versão – a que usa uma expressão de caso – pede que você primeiro processe a semântica da linguagem Erlang (expressão de caso), então inferir o significado (o que a expressão de caso está fazendo), então considere a lógica da aplicação.
A segunda versão – a que usa uma função cuidadosamente nomeada – pede apenas que você considere a lógica da aplicação. Sim, você precisa ler a função e pausar por um momento para considerar o que significa “db criar opções de cluster” – e esse é precisamente o ponto! Uma vez que a lógica clica, é tão chato e óbvio que você é obrigado a passar para tópicos mais interessantes.
Etapa 5 – criar o banco de dados
Em seguida é db_create:

db_create ([Name, User, Pwd, Opts]) ->
mysql_controller: create_db (nome, usuário, Pwd, opts).
Isso é simplesmente uma passagem para a chamada de função externa.

Embora possamos viver sem essa indireção, ela fornece um papel importante para tornar as coisas óbvias. As entradas para essa operação são criadas por db_create_args e o resultado é manipulado por handle_db_create_result. A função db_create é parte lógica desse vocabulário. Sim, ele oculta os detalhes da implementação do uso do mysql_controller. Mas o principal benefício aparece na clareza que vemos em handle_db_create_msg. Então, nós gastamos uma linha extra de código.

Em nossa Religião Única e Verdadeira, não é pecado trocar toques-chave, que são baratos, por clareza, o que é precioso.

Etapa 6 – Manipular o resultado da criação do banco de dados
A maioria dos desenvolvedores Erlang usa expressões de casos para lidar com resultados de funções. Não há nada de errado com isso:

case read_something () de
{ok, Result} -> Resultado;
{erro, erro} -> erro (erro)
fim
Mas agora, curioso, você pergunta: “O que realmente está acontecendo aqui?”

Bem, estou traduzindo o resultado de read_something em outra coisa.

Então direi que:

translate_read_something (read_something ())
E então faça:

translate_read_something ({ok, Result}) -> Resultado;
translate_read_something ({error, Error}) -> erro (erro).
Se você usa expressões de casos para fazer algo, está perdendo a oportunidade de nomear algo assim. Use uma função. Você ficará chocado ao descobrir uma lógica importante e oculta durante o processo de nomenclatura.

É claro que translate_read_something é um nome terrível – você quer esclarecer o que é algo – e esse é precisamente o ponto! Quando você usa as funções dessa maneira, você é forçado a lidar com nomes. Quando algo não está claro em um nome, não está claro em seu cérebro.

Neste exemplo, estamos lidando com o resultado de uma função. Nesses casos, eu costumo usar uma convenção de nomenclatura handle_xxx – então algo como handle_read_result.

Com esse pano de fundo, eis o que eu tenho para o manipulador de resultados db_create:

handle_db_create_result ({ok, HostInfo}, Msg, estado) ->
Attrs = [{“slaves”, “”} | host_info_attrs (HostInfo)],
{reply, message_response (Msg, Attrs), estado};
handle_db_create_result ({error, Err}, _Msg, State) ->
log: erro ({db_create, Err, erlang: get_stacktrace ()}),
{error, Err, State}.
Essa é uma refatoração mecânica da expressão de caso na função handle_amqp original. Não é ruim – apenas algumas linhas de código. Mas tire um momento para ler o código. É óbvio? Ou se parece mais com uma imagem estereoscópica onde, se você relaxar o seu olhar, um Jesus tridimensional surge da tela?

Oque esta acontecendo aqui?

Existem dois cenários: um caso ok em que a operação de criação é bem-sucedida e um caso de erro em que a operação de criação falha.

Então, digamos que:

handle_db_create_result ({ok, HostInfo}, Msg, estado) ->
handle_db_created (HostInfo, Msg, Estado);
handle_db_create_result ({error, Err}, _Msg, State) ->
handle_db_create_error (Err, State).
Primeiro, nosso manipulador de casos de sucesso:

handle_db_created (HostInfo, Msg, Estado) ->
Attrs = [{“slaves”, “”} | host_info_attrs (HostInfo)],
{reply, message_response (Msg, Attrs), State}.
Não tem muito código, mas o código é estranho. O que está acontecendo com o atributo “escravos”? Não tenho certeza, na verdade. Lembre-se, este é um código real que estou refatorando – escrevi isso um ano antes e não me lembro por que está lá.

Ah, eu lembro – está lá para manter um contrato de nível de serviço! A API original incluía uma lista de “escravos” (um infeliz anacronismo ainda usado em muitas aplicações de banco de dados). A nova API não. Em vez de apenas descartar as informações, estou fornecendo um valor vazio.

Mas acho que é uma falha: não é óbvio que eu esteja implementando uma camada de compatibilidade com versões anteriores. Claro, eu poderia adicionar um comentário, mas por que não apenas tornar o código óbvio?

Primeiro, vamos reescrever nosso manipulador:

handle_db_created (HostInfo, Msg, Estado) ->
Attrs = db_created_response_attrs (HostInfo),
{reply, message_response (Msg, Attrs), State}.
Sem um olhar mais atento, você pode ter perdido a mudança: nós convertemos a lógica implícita do aplicativo “construa a resposta criada pelo banco de dados attrs” em uma função que nomeie essa lógica explicitamente.

Aqui está essa função:

db_created_response_attrs (HostInfo) ->
db_created_legacy_attrs (host_info_attrs (HostInfo)).
Olha lá! O comportamento legado é agora uma parte oficial da implementação, em vez de derivar a esmo em uma expressão sem nome. Se algum dia esquecer essa parte esquisita da lógica, preciso apenas dar uma olhada no código. E enquanto eu poderia fazer uma pausa para pensar sobre a sensibilidade dessa lógica – esse é precisamente o ponto! Nosso objetivo é desviar o fardo mental da análise da linguagem e inferir o raciocínio direto sobre a lógica da aplicação.

Aqui está a implementação totalmente desinteressante desse suporte legado:

db_created_legacy_attrs (Attrs) -> [{“slaves”, “”} | Attrs].
E finalmente, nosso manipulador de caso de erro:

handle_db_create_error (Err, State) ->
log: erro ({db_create, Err, erlang: get_stacktrace ()}),
{error, Err, State}.
Embora esse código pareça inocente, existe um bug! Eu não percebi isso antes porque quando vejo uma parede de código, digo para mim mesmo: “Uau, olhe para todo esse código. Eu me pergunto o que isso faz? Funciona? Eu aposto que sim. Se houvesse um bug, teríamos pegado nos testes de unidade. Fico feliz por termos uma ótima cobertura de teste. Olhe para todo esse código. Arrumado!”

Mas quando você está olhando para uma ou duas linhas de código em uma função cuidadosamente nomeada, os bugs são mais fáceis de detectar.

Então, oque há de errado?

Olhe aterlang: get_stracktrace () – em um relance casual, parece que ele retorna o rastreamento da pilha atual. Isso significa que vou registrar o rastreamento de pilha da operação atual? Por que eu me importo com isso?

Lendo os documentos forerlang: get_stacktrace / 0 Eu vejo:

Obtenha o rastreio da pilha de chamadas (stacktrace) da última exceção no processo de chamada… Se não houver exceções em um processo, o stacktrace será [].
Isso certamente não é o que eu quero! Eu não estou lidando com uma exceção nesta função, então o resultado é [] ou alguma exceção aleatória de uma operação anterior!

Eu suspeito que esse código pode ter vivido dentro de um bloco catch de exceção, o que teria feito sentido. Mas em algum refator anterior, ele foi perdido em meio à expansão do código ao redor. Um sistema de tipos teria percebido isso? Não é provável. Um linter? Não é provável. Este é realmente um problema endêmico para exceções como um recurso de linguagem! Mas com o código despojado de seu ruído, meu cérebro teve uma chance melhor de capturá-lo.

É isso que eu quero:

log: erro ({tipo, erro})
E nós terminamos! Na verdade, verdadeiramente feito!

Resumo do Refator
Aqui está a função original e terrível:

handle_amqp (#message {name = “db.create”} = Msg, Estado) ->
log: info ({db_create, serviço: to_proplist (Msg)}),
Name = get_required_attr (“nome”, Msg),
verify_db_name (nome),
Usuário = get_required_attr (“user”, Msg),
Pwd = get_required_attr (“password”, Msg),
Opts
case get_attr (“cluster”, Msg) de
indefinido -> [];
Cluster -> [{cluster, cluster}]
fim,
case mysql_controller: create_db (nome, usuário, Pwd, opts) de
{ok, HostInfo} ->
Attrs = [{“slaves”, “”} | host_info_attrs (HostInfo)],
{reply, message_response (Msg, Attrs), estado};
{error, Err} ->
elog: erro ({db_create, Err, erlang: get_stacktrace ()}),
{error, Err, State}
fim.
Este é o código refatorado:

handle_amqp (#message {name = “db.create”} = Msg, Estado) ->
handle_db_create_msg (Msg, estado).
handle_db_create_msg (Msg, estado) ->
log_operation (db_create, Msg),
Args = db_create_args (Msg),
Resultado = db_create (Args),
handle_db_create_result (Resultado, Msg, Estado).
log_operation (Type, Msg) ->
log: info ({tipo, serviço: to_proplist (Msg)}).
db_create_args (Msg) ->
[db_name (Msg),
db_user (Msg),
db_password (Msg),
db_create_opts (Msg)].
db_name (Msg) ->
verify_db_name (get_required_attr (“nome”, Msg)).
db_user (Msg) ->
get_required_attr (“usuário”, Msg).
db_password (Msg) ->
get_required_attr (“password”, Msg).
db_create_opts (Msg) ->
db_create_cluster_opts (get_attr (“cluster”, Msg)).
db_create_cluster_opts (undefined) -> [];
db_create_cluster_opts (Cluster) -> [{cluster, cluster}].
db_create (nome, usuário, Pwd, opts) ->
mysql_controller: create_db (nome, usuário, Pwd, opts).
handle_db_create_result ({ok, HostInfo}, Msg, estado) ->
handle_db_created (HostInfo, Msg, Estado);
handle_db_create_result ({error, Err}, _Msg, State) ->
handle_db_create_error (Err, State).
handle_db_created (HostInfo, Msg, Estado) ->
Attrs = db_created_response_attrs (HostInfo),
{reply, message_response (Msg, Attrs), State}.
db_created_response_attrs (HostInfo) ->
db_created_legacy_attrs (host_info_attrs (HostInfo)).
db_created_legacy_attrs (Attrs) -> [{“slaves”, “”} | Attrs].
handle_db_create_error (Err, State) ->
log: error ({Type, Err}),
{error, Err, State}.
Whooooa! Isto é melhor?? De jeito nenhum no Hades isso é melhor!

Código “terrível” original:

Funções: 1
Linhas de Código: 19
Média de linhas por função: 19
Código “óbvio” refatorado:

Funções: 15
Linhas de código: 39
Média de linhas por função: 2,6
Código bloat! 19 linhas para 39 linhas! “Demitido, você twit!”

Whoa, cozinhe lá em baixo Sparky.

Na verdade, reduzimos as linhas de 19 para apenas 5 – uma redução de 4x!

Esta é a nossa nova função – o que estamos realmente fazendo:

handle_db_create_msg (Msg, estado) ->
log_operation (db_create, Msg),
Args = db_create_args (Msg),
Resultado = db_create (Args),
handle_db_create_result (Resultado, Msg, Estado).
Nossa contagem total de linhas aumentou em 2x. Isso é ruim se você argumentar que a contagem de linhas se correlaciona positivamente com a contagem de erros, o que, é claro, deve ser se todo o resto for constante. Mas neste caso, há mais na história – pensamos rigorosamente sobre o que estamos fazendo e garantimos que nosso código reflita esse trabalho.

Nossas linhas adicionais de código são pagas em espadas, tornando o referido código embaraçosamente óbvio.

Custo e Benefício
Há um custo claro para essa abordagem: tempo gasto tornando as coisas óbvias.

É verdade – muito pensamento e esforço foram dedicados a esse processo – e esse é precisamente o ponto!

Aqui estão alguns dos benefícios:

A intenção do programador é clara, ajudando você a identificar o raciocínio incorreto mais cedo
Programadores introduzem menos bugs inicialmente
O teste é mais focado e mais fácil de raciocinar sobre
O código é mais fácil de depurar porque erros ocorrem em um contexto coerente e bem definido
A melhor maneira de apreciar esses benefícios é tentar esse método e executá-lo durante um período de meses. Mas comece uma vez e veja como se sente.

Há outro benefício que você desfrutará e que é menos óbvio: em primeiro lugar, você vai melhorar e acelerar a criação de código óbvio.

Muitas vezes o processo de programação é começar com uma noção difusa do que você está fazendo, tentar algo, falhar, tentar outra coisa, ficar frustrado, reprovar mais, ficar realmente frustrado, experimentar mais um pouco, começar a entender melhor o problema, Revise o problema, tente um pouco mais, obtenha algum sucesso, falhe novamente, tome um Valium, repita este processo N vezes e depois, bam! – está funcionando. E então você aplica isso para tornar o método de refatoração mais óbvio para codificar seu glorioso código de trabalho, de modo que você e os outros se sintam totalmente apáticos, porque isso é tão aborrecido e claramente claro.

Mas se você fizer o mais tardar – o trabalho embaraçosamente óbvio – você se tornará um programador muito melhor. Você ficará mais rápido ao identificar códigos incoerentes. Com o tempo, você ficará mais rápido ao traduzir códigos incoerentes em algo simples e claro. Em mais tempo, você começará a escrever imediatamente esse código simples e claro – porque seu cérebro muda. É como aprender uma língua. Em algum momento, com prática suficiente, você pára de pensar em falar essa língua e apenas começa a usá-la.

E então toda a discussão de custo-benefício é deixada de lado – tudo é benefício.

Resumo
Uma das melhores qualidades de Erlang é que é fácil identificar códigos terríveis – basta procurar as funções longas
Você pode escrever um ótimo código, misturando código terrível em pedaços cada vez menores, cada um obviamente correto, simplesmente perguntando “o que está acontecendo aqui” – “o que realmente está acontecendo?” Até saber exatamente o que está acontecendo, em todos os lugares
Obviamente, o código correto terá menos bugs
Obviamente, o código correto é mais fácil de consertar quando ele tem bugs (relaxando por um momento nossa definição de correto – isto é uma religião para que possamos fazer isso)
O teste é similarmente um processo de busca por clareza
Se você praticar esse método, será melhor e mais rápido escrever códigos embaraçosamente óbvios diretamente
Um pensamento final.

As pessoas que se queixam da hedionda sintaxe de Erlang estão escrevendo longas funções com muitas atribuições de variáveis, declarações de caso, declarações if – tentando forçar um estilo imperativo de programação em uma linguagem funcional.

Você pode ser tentado a culpar Erlang. Mas você sabe melhor agora.

Vamos deixar nossos julgamentos de lado, para quem entre nós está sem código terrível? Nosso amor pelo óbvio e pelo correto deve emanar como um farol de luz. Nossa missão deve permanecer sempre clara: implacavelmente procurar o óbvio, e depois fazer o óbvio, até que apenas o óbvio permaneça.

 


Advertisement