Não simplifique este código
(github.com/kubernetes)- O
pv_controller.godo Kubernetes é o controlador que sincroniza o binding de PV/PVC e, já no topo do arquivo, deixa explícito: “não simplifique e mantenha o estilo space shuttle” - Esse estilo mantém um
elsecorrespondente para todoife deixa comentários até em condições que parecem óbvias, para expor no código os ramos revisados e a intenção - O centro do design são os ponteiros bidirecionais conectados por
pvc.Spec.VolumeNameepv.Spec.ClaimRef, permitindo lidar com recuperação de concorrência, deleção, edições do usuário e bindings simultâneos em um ambiente sem transações - O controlador combina observação de mudanças em PV/PVC, cache interno, fila com um único worker, registro de eventos, provisionamento dinâmico e interface de migração CSI para gerenciar as transições de estado do binding
- Os ramos verbosos e os comentários existem para preservar o conhecimento de negócio do comportamento e o contexto de recuperação de falhas, então mudanças futuras também devem seguir esse estilo
Papel e princípios de escrita de pv_controller.go
pv_controller.goé o arquivo de implementação do PersistentVolumeController no pacotepersistentvolumedo Kubernetes- Esse controlador mantém alinhado o estado de
PersistentVolumeClaimePersistentVolume- Controlador de cache que observa mudanças em
PersistentVolume - Controlador de cache que observa mudanças em
PersistentVolumeClaim - Sincronização do estado de PV/PVC com base nos eventos de mudança dos dois objetos
- Controlador de cache que observa mudanças em
- O comentário no topo do arquivo alerta repetidamente para não simplificar esse código
- O nome do estilo é
space shuttle style - A abordagem é ter um
elsecorrespondente para toda instruçãoif - O objetivo é deixar todos os ramos explícitos, exceto checagens de erro simples
- Mesmo comportamentos que parecem óbvios são comentados para que quem faz manutenção consiga acompanhar a complexidade do binding
- O nome do estilo é
Por que manter o space shuttle style
- Esse controlador é o resultado da unificação de trabalhos que originalmente estavam divididos em três controladores
- Ao simplificar o subsistema de PV, surgiu a necessidade de tratar todas as condições de forma explícita no código
- Como resultado, o código pode parecer verboso e cheio de comentários e ramificações
- Essa verbosidade é um mecanismo para deixar no código o conhecimento de negócio e o contexto do comportamento de binding
- Ao alterar esse arquivo, é preciso preservar o
space shuttle stylee, quando necessário, adicionar novos ramos e comentários da mesma forma
Design central: ponteiros bidirecionais entre PV e PVC
- No centro do design há ponteiros bidirecionais entre PV e PVC
- Ponteiro do lado do PVC:
pvc.Spec.VolumeName - Ponteiro do lado do PV:
pv.Spec.ClaimRef
- Ponteiro do lado do PVC:
- Essa bidirecionalidade é difícil de tratar em um sistema sem transações, mas é necessária para garantir funcionamento correto mesmo em situações de falha
- Se uma instância HA rogue do controlador criar uma condição de corrida, podem surgir múltiplos bindings indistinguíveis, com possibilidade de perda de dados
- O controlador foi projetado principalmente para operar em modo de alta disponibilidade active-passive
- As transições de objeto também foram projetadas para funcionar em HA active-active
- Mas, se dois controladores ativos entrarem em conflito com frequência, o desempenho pode cair
Forma de binding e condições de recuperação
- O controlador suporta objetos pre-bound bidirecionais
- Um PVC que deseja um PV específico
- Um PV reservado para um PVC específico
- O binding ocorre em duas etapas
- Primeiro, altera
PV.Spec.ClaimRef - Depois, altera
PVC.Spec.VolumeName
- Primeiro, altera
- Em qualquer ponto desse processo, o PV ou o PVC podem ser modificados ou apagados pelo usuário ou por outro controlador
- Também é possível que dois ou mais controladores tentem fazer binding simultâneo entre volumes e claims diferentes
- O controlador precisa ser capaz de recuperar desses cenários de conflito
Principais componentes da struct do controlador
PersistentVolumeControllerpossui listers, funções de sync de informer, cliente Kubernetes, registrador de eventos, gerenciador de plugins de volume e outros componentes necessários para sincronizar PV/PVC- As últimas versões conhecidas de PV/PVC ficam armazenadas em cache interno
volumes persistentVolumeOrderedIndexclaims cache.Store
- Esse cache reflete tanto a versão mais recente salva no servidor de API quanto a versão recebida por eventos do etcd
- Um binding pode gerar cerca de quatro eventos
- Atualização de
volume.Spec - Atualização de
volume.Status - Atualização de
claim.Spec - Atualização de
claim.Status
- Atualização de
- Sem o cache interno, o informer pode manter um estado antigo e tentar corrigir novamente um binding que já foi concluído
- Se isso tentar regravar no servidor de API, pode ocorrer conflito de versão com o objeto já persistido
Fila de trabalho e restrições de concorrência
- O controlador tem workqueues separadas para processar claims e volumes
claimQueuevolumeQueue
- Cada fila deve ter exatamente uma worker thread
- Em especial,
syncClaim()não é reentrante - Se duas execuções de
syncClaim()rodarem ao mesmo tempo, podem surgir os seguintes problemas- Fazer binding de dois claims diferentes ao mesmo volume
- Fazer binding de um único claim a dois volumes
- O controlador consegue se recuperar dessas situações com erros de versão do servidor de API e verificações próprias, mas um modelo com múltiplos workers pode reduzir a velocidade geral
syncClaim: ponto de entrada da sincronização de PVC
syncClaimé o método principal chamado quando um claim é criado, atualizado ou sincronizado periodicamente- Esse método não diferencia o tipo de evento
- Primeiro, define a migration annotation correta no PVC e, se necessário, atualiza o servidor de API
- Em seguida, faz o desvio com base na presença da annotation
AnnBindCompleted- Se a annotation não existir, chama
syncUnboundClaim - Se a annotation existir, chama
syncBoundClaim
- Se a annotation não existir, chama
- O processamento real é dividido entre métodos para claims unbound e bound por motivos de legibilidade
checkVolumeSatisfyClaim: verificação dos requisitos do PV
checkVolumeSatisfyClaimverifica se o PV solicitado atende aos requisitos do PVC- As condições verificadas são listadas explicitamente no código
- Erro se o PV tiver
DeletionTimestamp - Erro se a capacidade do PV for menor que a capacidade solicitada pelo PVC
- Erro se
storageClassNamefor diferente - Se o feature gate
VolumeAttributesClassestiver ativado, verifica seVolumeAttributesClassNamecorresponde - Se o feature gate estiver desativado, mas houver
VolumeAttributesClassNameno claim ou no volume, gera erro - Erro se
volumeModenão for compatível - Erro se o modo de acesso não for compatível
- Erro se o PV tiver
- Se todas as condições forem satisfeitas, retorna
nil
Tratamento de eventos para PVC com delayed binding
emitEventForUnboundDelayBindingClaimgera um evento informativo para claim não bound em modo de delayed binding- O reason padrão é
WaitForFirstConsumer - A mensagem padrão diz que o binding ficará aguardando até que o primeiro consumer seja criado
- Se houver um Pod ainda não agendado que referencie esse PVC, o reason muda para
WaitForPodScheduled- Se houver vários Pods, a mensagem inclui o nome de todos eles
- No volume scheduling, só um Pod é considerado, mas como não se sabe qual será usado, todos os Pods são incluídos
syncUnboundClaim: tratamento de PVC ainda não bound
- Se
claim.Spec.VolumeNameestiver vazio, o usuário não pediu um PV específico - Nesse caso, o controlador verifica o modo de delayed binding do claim e busca o PV mais adequado com
findBestMatchForClaim - Se não houver PV adequado, o processamento segue esta ordem
- Se for possível atribuir uma StorageClass padrão, atualiza o PVC e encerra a sincronização
- Se for delayed binding e ainda não estiver em estado de provisioning, gera um evento de espera
- Se o claim tiver StorageClass, tenta provisionamento dinâmico com
provisionClaim - Caso contrário, registra um evento
FailedBindingdizendo que não há PV disponível nem StorageClass
- Se houver um PV adequado, chama
bindpara fazer o binding entre PV e PVC- Em caso de sucesso, registra métricas da operação de provision + binding e limpa o cache de timestamp
- Se ocorrer erro ao salvar, um
syncClaimposterior concluirá o binding
Tratamento de PVC que pede um PV específico
- Se
claim.Spec.VolumeNamenão estiver vazio, o usuário pediu um PV específico - Se o PV solicitado não estiver no cache, o status do PVC é atualizado para
Pendinge será tentado novamente depois - Se o PV solicitado existir e
volume.Spec.ClaimRefestiver vazio, o PV ainda não foi claimado- Verifica os requisitos com
checkVolumeSatisfyClaim - Se os requisitos não forem atendidos, registra evento
VolumeMismatche mantém o PVC emPending - Se os requisitos forem atendidos, chama
bind
- Verifica os requisitos com
- Se o PV solicitado já estiver claimado por esse mesmo PVC, chama
bindpara concluir o binding - Se o PV solicitado estiver ligado a outro claim, o tratamento é o seguinte
- Se o claim não tiver annotation de binding feita pelo controlador, registra evento
FailedBindinge o mantém emPending - Se parecer ter sido bound pelo controlador, mas estiver ligado a outro claim, retorna erro em um estado “should never happen”
- Se o claim não tiver annotation de binding feita pelo controlador, registra evento
syncBoundClaim: tratamento de PVC já bound
syncBoundClaimtrata PVCs com a annotationAnnBindCompleted- Se o claim já estiver bound, mas
claim.Spec.VolumeNameestiver vazio, muda o estado do claim paraClaimLost- A mensagem do evento diz que o claim bound perdeu a referência ao PV e os dados do volume foram perdidos
- Se o PV apontado pelo claim não existir, também muda para
ClaimLost- A mensagem do evento diz que o claim bound perdeu o PersistentVolume e que os dados foram perdidos
- Se o PV existir, mas
volume.Spec.ClaimRefestiver vazio, considera que o volume voltou ao estado unbound e chamabindnovamente - Se
ClaimRef.UIDdo PV for igual ao UID do claim, considera binding normal e chamabind- Na maioria dos casos, essa chamada não faz nada
- Se o PV apontar para outro claimant, define a fase do claim como
Lost, um estado terminal
syncVolume: ponto de entrada da sincronização de PV
syncVolumeé o método principal chamado quando um volume é criado, atualizado ou sincronizado periodicamente- O tipo de evento não é diferenciado
- Primeiro, define no PV a migration annotation e o finalizer corretos e, se necessário, atualiza o servidor de API
- Se
volume.Spec.ClaimRefestiver vazio, o volume é considerado não utilizado e sua fase viraAvailable - Se
ClaimRefexistir, mas o UID estiver vazio, o PV é tratado como reservado para um PVC específico e sua fase viraAvailable- Nessa situação, o PVC ainda não está bound a esse PV, e o sync do PVC fará o processamento
Tratamento de PV quando o claim não é encontrado
- Se o PV estiver bound a um claim, o controlador procura o PVC pelo namespace/name em
ClaimRef - Quando o PVC não é encontrado no cache, ele faz verificações adicionais em certas condições
- Verifica novamente no cache do informer
- Verifica novamente no servidor de API
- Em PVs criados por provisioner externo ou binder externo, sob carga elevada, o PVC ainda pode não ter sido sincronizado para o cache local
- Para evitar fazer reclaim incorreto do PVC, é feito um duplo check
- Se for concluído que o claim não existe, a fase do volume muda para
ReleasedereclaimVolumeé executado- Se a fase anterior era
Failed, ela não é sobrescrita - Se a política de reclaim for
Retain, é registrado um log dizendo que o PV referencia um claim inexistente
- Se a fase anterior era
Quando a conexão entre PV e PVC está desalinhada
- Se o claim existir, mas
claim.Spec.VolumeNameestiver vazio, o PVC ainda não tem o nome do PV - Se
volumeModenão bater, registra eventosVolumeMismatchtanto no PV quanto no PVC e ignorasyncClaim - Se não houver mismatch, adiciona o claim à
claimQueuepara quesyncClaimseja chamado em breve- Essa abordagem acelera o binding de volumes provisionados
- Se
claim.Spec.VolumeNamefor igual ao nome do volume atual, considera binding normal e atualiza a fase do volume paraBound - Se o claim estiver bound a outro volume, o tratamento depende do caso
- Se o volume tiver sido provisionado dinamicamente e a política de reclaim for
Delete, marca comoReleasede executareclaimVolume - Se for um volume bound pelo controlador, limpa com
unbindVolume - Se o ponteiro tiver sido criado pelo usuário, mantém o ponteiro, mas chama
unbindVolumepara atualizar a fase e limparClaimRef.UID
- Se o volume tiver sido provisionado dinamicamente e a política de reclaim for
Atualização de status e emissão de eventos
updateClaimStatussalva o status do PVC no servidor de API- Mudança de fase
- Quando não há volume, inicializa
AccessModes,CapacityeCurrentVolumeAttributesClassName - Quando há volume, atualiza modo de acesso, capacidade e nome atual da classe de atributos do volume
- Há uma condição para atualizar a capacidade apenas no momento em que o claim vira
Bound- A diferença entre o tamanho de filesystem do PVC e o tamanho do block device do PV pode ser intencional, então a capacidade de um claim já bound não deve ser sobrescrita
- Se o feature gate
VolumeAttributesClassestiver ativado,CurrentVolumeAttributesClassNameé definido na transição de pending para bound- Depois disso, um resizer ou override administrativo deve lidar com o valor; se o controlador continuar definindo esse campo, pode haver race condition
updateClaimStatusWithEventeupdateVolumePhaseWithEventsó emitem eventos quando o status/fase realmente muda
Atribuição da StorageClass padrão
assignDefaultStorageClassprocura e atribui a StorageClass padrão quando o claim não tem storage class- Claims que já têm storage class são ignorados
- Se não houver classe padrão, nada é atualizado e a função retorna
false - Se houver classe padrão, define o nome da classe em
claim.Spec.StorageClassNamee atualiza o servidor de API
Escopo do arquivo e limites explícitos
- Segundo os metadados do arquivo exibidos no GitHub,
pv_controller.gotem 2038 linhas, 1864 LOC e 91 KB - O corpo fornecido inclui apenas da parte inicial do arquivo até o começo da função
bindVolumeToClaim; o restante continua por um link para a visualização raw - Portanto, este resumo se limita à estrutura do controlador, aos comentários de design, aos principais ramos de sincronização e à lógica de atualização de estado visíveis no trecho de código fornecido
1 comentários
Opiniões no Hacker News
Não sei se é estranho que o código neste arquivo realmente pareça código Go comum. Por ser Go, é verboso e, por não depender de abstrações profundas, parece mais longo, mas o código em si me parece típico.
Abstração é uma faca de dois gumes, então esse jeito também é válido; se não houvesse o prefácio, acho que eu nem teria pensado duas vezes sobre o estilo de escrita. Talvez a diferença venha de eu ter mais experiência com software corporativo do que com software de sistemas. Para alguém que contribui constantemente com Kubernetes, esses comentários podem parecer desnecessários, mas, se fosse código que um leitor de um ambiente corporativo fosse ler em um futuro distante sem contexto, nessa complexidade eu provavelmente teria colocado ainda mais comentários
Antigamente esse tipo de código parecia normal, mas, nos últimos 10 anos mais ou menos, parece que muita gente passou a valorizar concisão acima de explicitude.
Especialmente em código importante como esse, prefiro muito mais explicitude. Várias vezes na minha carreira, por causa de código que combinava várias condições e omitia comentários explicando o contexto de negócio e o significado, não dava para saber se o comportamento atual era intencional ou acidental. Esse estilo tende a virar código que impede mudanças, não código resistente a mudanças, e no mínimo torna difícil para alguém que não seja o autor alterá-lo. Criar uma cerca de Chesterton desnecessária vai contra a manutenibilidade
Esse comentário provavelmente foi adicionado depois de uma tentativa fracassada de simplificar o código, como um aviso para futuros mantenedores pensarem de novo antes de tentar a mesma coisa.
O commit que adicionou o aviso foi "Add note about space-shuttle code style"[1], e o commit imediatamente anterior foi "Revert controller/volume: simplify sync logic in syncUnboundClaim"[2]
[1] https://github.com/kubernetes/kubernetes/commit/de4d193d45f6...
[2] https://github.com/kubernetes/kubernetes/commit/8a1baa4d64ca...
Eu estava pensando de forma parecida, mas mudei de ideia quando vi os
ifs muito aninhados. Ali eu certamente teria criado ramificações com retorno antecipado.Dá a sensação de que terminaram só a primeira etapa de "faça funcionar, faça ficar rápido, faça ficar bonito" e não fizeram a parte de "deixar bonito". Já escrevi código feio e cheio de comentários assim ao destrinchar interações complicadas de estado, mas normalmente arrumo um pouco antes da revisão. Talvez fosse melhor simplesmente colocar um grande banner no topo do arquivo dizendo "não tente simplificar este código". Ainda assim, com certeza não é terrível
Pode até ser estranho, mas você não está sozinho. Para mim, esse código parece totalmente normal. Já escrevi código e comentários desse tipo em componentes que eu considerava importantes para a confiabilidade do sistema.
Nunca concordei com a moda de "código sem comentários", e, quando voltei meses ou anos depois, os comentários que eu mesmo tinha escrito foram valiosos para o meu eu do futuro com muita frequência. É difícil imaginar reconstruir a lógica embutida em um componente com esse grau de complexidade sem comentários sólidos
Em especial, a afirmação de que todo
iftem um comentário deelsecorrespondente não parece ser sempre verdadeira. Muitos dosifs sem correspondente são verificações simples comoif (err != nil) {ou outros casos de retorno antecipado, mas, mesmo excluindo esses, parece haverifs sem correspondente.Dito isso, pela minha experiência com software corporativo, comentários adicionais também não eram necessariamente tão numerosos. A base de código tinha comentários
// end ifcomo uma praga, mas comentários explicativos de verdade eram rarosTexto sobre a qualidade do software do Space Shuttle: https://archive.is/HX7n4
Em resumo, o que torna esse software incrível não é o quanto ele faz, mas o quão bem ele funciona. Dizem que ele nunca trava, não precisa ser reiniciado, não tem bugs e é o mais próximo da perfeição que humanos já alcançaram. As últimas três versões tinham 420 mil linhas cada, com apenas um erro em cada uma, e houve 17 erros no total nas últimas 11 versões. Dizem que um programa comercial de complexidade semelhante teria algo como 5.000 erros
Por ser cara e lenta demais, provar a correção do software com assistentes de prova modernos provavelmente seria muito mais barato, rápido e, na prática, mais seguro. Projetos como seL4 e CompCert mostram como fazer isso
Entendo a intenção de
// KEEP THE SPACE SHUTTLE FLYING., mas é meio engraçado fazer referência, em um comentário, a um sistema que não está mais em operação por causa de um histórico de segurança ruim.Será que daqui a uns 10 anos as pessoas ainda vão se lembrar bem do Space Shuttle?
Os problemas de segurança do Space Shuttle foram, em grande parte, problemas de hardware, não de software.
No "Appendix F - Personal Observations on Reliability of Shuttle" [0], apêndice de Richard Feynman no relatório do acidente do Challenger de 1986, ele diz o seguinte:
Ele destacou especificamente a qualidade do software aviônico como exemplo de que até grandes projetos governamentais complexos como o Shuttle podem ser bem engenheirados, e que eles não estão, por si só, destinados a baixa qualidade e risco
0: https://www.nasa.gov/history/rogersrep/v2appf.htm
Levou pessoas e equipamentos ao espaço e os trouxe de volta para casa em bem mais de 100 missões bem-sucedidas. Eu ainda vejo isso de forma positiva, e é bem provável que continue vendo. Em termos de progresso humano e efeito líquido, foi um sucesso.
O que encerrou o Shuttle não foi um histórico de segurança ruim, mas custo e a expectativa de uma piora futura na segurança.
Embora os dois acidentes do Shuttle tenham matado mais astronautas do que outros desastres da NASA, considerando a dificuldade do que de fato aconteceu, o histórico de segurança foi realmente impressionante. O código parece muito bom.
A situação do Space Shuttle é mais complexa do que simplesmente dizer que sua segurança era ruim. Em termos de missões, seu histórico é melhor que o de outros veículos lançadores. O Shuttle teve 2 missões fatais em 135; a Soyuz da era soviética teve 2 em 66; e o SpaceShipTwo tem um histórico assustadoramente ruim, com 1 missão fatal em apenas 12 voos.
Porém, o Space Shuttle tinha uma capacidade de tripulação muito maior do que a necessária para a maioria das missões. Ao contrário da Apollo ou da Soyuz, com 3 pessoas, ele podia levar até 8; e, considerando que a maioria das missões soviéticas/Roscosmos, ESA e CNSA eram missões autônomas totalmente não tripuladas, não havia tripulação alguma a ser colocada em risco. Talvez essa analogia se encaixe melhor no Kubernetes: um sistema altamente engenheirado, poderoso e multiuso, mas que exige muita atenção e provavelmente é usado um pouco mais do que o necessário.
Pelo critério mais comum, por passageiro-milha, o Space Shuttle está entre os veículos mais seguros já construídos e voados.
Falando francamente como alguém cuja infância foi exatamente nos anos 1980: não sei como seria possível não lembrá-lo com carinho. Será que você é jovem demais, vê esse programa e todas as suas missões e conquistas apenas em retrospecto, e tem uma perspectiva contaminada pelo clima atual de uma era centrada em empreiteiras espaciais privadas?
A história de Richard Hipp sobre adequar o código do SQLite a padrões de aviação também é bastante interessante: https://corecursive.com/066-sqlite-with-richard-hipp/#testin...
Esta parte me lembra a verificação de exaustividade em código TypeScript. Tento usá-la sempre.
https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
O
satisfies never, mais recente, é muito bom para esse uso. Também é conveniente quando, por preferência, você usa cadeias deif else.Talvez você goste de
ts-pattern.https://github.com/gvergnaud/ts-pattern
Considerando apenas os casos em que se coloca um
elseexplícito em cadaifque não seja totalmente trivial, fico curioso sobre quanto esse código teria ficado mais simples se os autores do Kubernetes tivessem projetado tudo em torno de pattern matching estrutural, em vez de blocosif/else.Várias linguagens mainstream que oferecem pattern matching estrutural têm ferramentas para verificar em tempo de compilação se o matching é exaustivo, e só isso já poderia ser uma solução idiomática, aumentando a densidade de informação do código.
Discussão de 2018: https://news.ycombinator.com/item?id=18772873
Só dei uma olhada por alto no código, mas, sinceramente, ele não me parece tão ruim. Há coisas que eu teria feito de outro jeito, mas já vi muito código bem pior.
Pelo menos este código segue uma regra, tudo parece ter sido escrito depois de pensar, e dá a impressão de que há um método nesse caos. Eu escolheria esse tipo de código a qualquer momento em vez da típica mistura confusa de estilos, programação preguiçosa e estrutura ilógica que já vi tantas vezes.
Fico me perguntando por que, ao criar novas práticas de "segurança", ignoram boas práticas de engenharia de software documentadas.
Módulos de 2.000 linhas, métodos de 200 linhas e 3 ou 4 níveis de aninhamento de
ifsão considerados prejudiciais. Comentários que dizem apenas o que o código faz, e não por quê, também não são úteis e tendem a divergir do código real. Também se vê uso desnecessário denil. Mesmo sem entrar em questões mais profundas, como acoplamento ou princípio da responsabilidade única, esses pontos aparecem na superfície.Se você acha que essas coisas são prejudiciais, recomendo ler "John Carmack on Inlined Code".
http://number-none.com/blow/john_carmack_on_inlined_code.htm...
"O código de controle de voo dos foguetes da Armadillo tinha só alguns milhares de linhas, então peguei a função tic principal e comecei a inlinar todas as sub-rotinas. Não posso dizer que encontrei um bug oculto que pudesse causar uma queda real, mas encontrei algumas variáveis sendo definidas várias vezes e alguns fluxos de controle que pareciam um pouco suspeitos, e o código final ficou menor e mais limpo."
Se Carmack encontrou valor nessa abordagem, acho que não devemos descartá-la apressadamente. Os comentários posteriores também valem a leitura.
"Nos anos desde que escrevi isso, passei a ver com muito mais bons olhos, mesmo em C/C++, a programação funcional pura dentro de limites razoáveis... Quando ficar difícil de lidar, encontre uma forma de separar blocos em funções puras."
Às vezes é caso de "não há outro jeito(TM)".
Limites arbitrários de número de linhas tendem a gerar fragmentação desnecessária. Some includes, licenças, código de cola e comentários, e vira um espaguete difícil de abordar. Tente manter métodos com 200 linhas em código de alto desempenho e a performance pode despencar como o voo de Ícaro.
Ao ler os comentários do código, dá para ver que uma enorme quantidade de know-how foi colocada ali para simplificar esse código em um único módulo e torná-lo acessível e, mais importante, sustentável. Para quem não conhece a linguagem ou a lógica, comentários que dão uma noção do que o código faz são muito úteis. Daqui a 6 meses, até o próprio código vai parecer estranho, então eles também são úteis para você mesmo.
Escrevi por bastante tempo dessa forma "segura", mas acabei criando muito mais bugs do que com tratamento de erros em estilo ferroviário usando retorno antecipado, e também levei muito mais tempo para corrigi-los
Colocar um
elseexplícito em todo blocoiffaz explodir a complexidade de ter que manter o contexto atual na cabeça. Acho razoável mudar essa regra para: "todo bloco condicionalifretorna antecipadamente ou tem um blocoelsecorrespondente". O padrãoif (cond) { tratamento especial }certamente torna o código muito mais perigoso e difícil de raciocinar do que o retorno antecipado.Não existe um único conjunto canônico de boas práticas
O tamanho de uma função ou o número de linhas de código em um arquivo não é, por si só, prejudicial nem benéfico. Cada linguagem tem sua própria visão sobre como organizar código, mas nada disso pode ser apresentado como "a melhor prática". Go não é uma linguagem que favorece dividir o código em muitos arquivos pequenos.
Um método de 200 linhas não é inerentemente errado. Se o código interno for linear e mantiver o mesmo nível de abstração, pode ser a melhor escolha
A alternativa de criar 40 métodos de 5 linhas pode ser pior. Para entender o todo, é preciso ficar pulando de um lado para o outro, e ainda dá para estragar a ordem das chamadas. Há 40! permutações possíveis.
Esse tipo de código parece um candidato ideal para migrar para um sistema declarativo, baseado em regras e orientado por tabelas
Essa abordagem é mais fácil de entender e verificar do que um monte de cláusulas
ifimperativas e improvisadas. Esse tipo de código bagunçado normalmente é sinal de que há uma abstração faltando.