Esta é uma coleção clínica cínica do que aprendi ao longo de 30 anos em desenvolvimento de software. Repito, algumas coisas são muito cínicas, e o resto é resultado de longas observações em diferentes locais de trabalho.
Desenvolvimento de software
Especificações primeiro, depois codifique
Se você não sabe exatamente o que está tentando resolver, não sabe qual código escrever.
Primeiro, descreva como seu aplicativo funciona antes de iniciar a programação.
“Sem requisitos ou projeto, a programação é a arte de adicionar bugs a um arquivo de texto vazio.” - Louis SraigleyÀs vezes, até uma "breve apresentação" é suficiente - não mais que dois parágrafos descrevendo o que seu aplicativo faz.
Houve momentos em que, devido a etapas não escritas, passei mais tempo olhando o código e me perguntando o que fazer a seguir. Este é um bom sinal de que é hora de parar e discutir a situação com os colegas. Ou talvez repensar a decisão.
Descreva as etapas como comentários
Se você não sabe como começar, descreva o fluxo de dados de nível superior no seu aplicativo, simplesmente no seu idioma nativo. E, em seguida, preencha o código nulo entre os comentários.
Ou melhor ainda: leia cada comentário como uma função e depois escreva uma função que faça exatamente isso.
Maxixe ajuda você a realizar expectativas
Gherkin é um formato de descrição de teste cujo princípio diz: "Dado que o sistema está em um determinado estado, se algo acontecer, isso é esperado". Se você não usar ferramentas de teste que entendam o Gherkin, você terá uma boa idéia do que esperar do aplicativo.
Os testes de unidade são bons, os testes de integração são ainda melhores
No meu trabalho atual, estamos testando apenas módulos e classes. Por exemplo, escrevemos testes apenas para o nível de apresentação, depois escrevemos testes apenas para o nível do controlador e assim por diante. Isso nos ajuda a entender se tudo está em ordem, mas não nos permite ver a imagem completa do que está acontecendo - para isso, os testes de integração que verificam o comportamento de todo o sistema são muito mais úteis.
Testes melhoram APIs
Programamos na estrutura de níveis: existe um nível de armazenamento que deve tornar nossos dados eternos; existe um nível de processamento que deve, de alguma forma, transformar os dados armazenados; existe uma camada de apresentação que contém informações sobre a apresentação de dados etc.
Como eu disse, os testes de integração são melhores, mas testar os próprios níveis permite que você entenda melhor a aparência de suas APIs. Então você entenderá melhor a situação com as chamadas de algo: a API é muito complicada? Preciso manter tantos dados por perto para fazer uma ligação?
Faça testes que você pode executar na linha de comando
Quero dizer, não as linhas de comando em si são importantes para qualquer objeto, mas seu conhecimento dos comandos para executar os testes, sua capacidade de automatizar a execução deles, que você pode aplicar na ferramenta de integração contínua.
Esteja pronto para enviar seu código para a cesta
Muitos dos que começam a desenvolver com base em testes (TDD) ficam aborrecidos quando você diz a eles que pode precisar reescrever muito do código deles, incluindo o que você escreveu.
O TDD foi
inventado para jogar código: quanto mais você aprende sobre um problema, mais entende que o que escreveu não o resolverá a longo prazo.
Não se preocupe com isso. Seu código não é um muro: se você sempre tiver que jogá-lo fora, isso não será um desperdício. Obviamente, você perdeu tempo escrevendo código, mas agora entende melhor o problema.
Uma boa linguagem tem testes integrados
Garanto-lhe: se a biblioteca de idiomas padrão tiver uma estrutura de teste - embora mínima -, no ecossistema relacionado os testes serão melhores do que em uma linguagem que não a possua, independentemente dos méritos das estruturas de teste externas para essa linguagem.
Pensar no futuro significa desperdiçar sua energia
Quando os desenvolvedores tentam resolver um problema, algumas vezes tentam encontrar uma maneira de resolver todos os problemas, incluindo aqueles que possam surgir no futuro.
Vou lhe dizer uma coisa: esses problemas futuros nunca surgirão e você terá que acompanhar uma enorme pilha de código que não será usada por inteiro, ou terá que reescrever tudo devido à pilha de códigos não utilizados.
Resolva o problema agora. Em seguida, decida o seguinte. Depois o próximo. Depois de perceber um padrão que surge com base nessas decisões, e somente
então você encontrará sua "solução universal".
A documentação é uma mensagem de amor futura para você
Todos sabemos que tipo de hemorragia é escrever documentação maldita sobre funções, classes e módulos. Mas entender o curso de seus pensamentos quando você escreveu essa ou aquela função pode salvar sua bunda no futuro.
A documentação da função é seu contrato
Começando a escrever a documentação, você está realmente criando um contrato (talvez com você mesmo): "Eu afirmo que essa função faz
isso , e é isso que faz".
Se, posteriormente, você descobrir que seu código não está em conformidade com a documentação, isso será um problema de código, não uma documentação.
Se a descrição da função tiver "e", isso é ruim
Uma função deve fazer apenas uma coisa. Quando você escreve a documentação e vê que adicionou um "e", significa que a função faz outra coisa. Divida-o em duas funções e livre-se do "e".
Não use valores booleanos como parâmetros
Ao desenvolver uma função, você pode ser tentado a adicionar um sinalizador. Não faça isso.
Deixe-me explicar com um exemplo: digamos que você tenha um sistema de mensagens, e há uma função
getUserMessages
que retorna todas as mensagens para o usuário. Mas há uma situação em que você precisa retornar um breve resumo de cada mensagem (por exemplo, o primeiro parágrafo) ou a mensagem inteira. Portanto, você adiciona um parâmetro na forma de um sinalizador ou valor booleano que você chama de
retrieveFullMessage
.
Novamente, não faça isso.
Como quem lê seu código verá
getUserMessage(userId, true)
e se perguntará o que é isso.
Ou você pode renomear a função
getUserMessageSummaries
e inserir
getUserMessagesFull
, ou algo semelhante, mas cada função simplesmente chamará o
getUserMessage
original com
true
ou
false
- mas a interface fora da sua classe / módulo será clara.
Mas não adicione sinalizadores ou parâmetros booleanos às funções.
Cuidado com as alterações na interface
No parágrafo anterior, mencionei a renomeação de uma função. Se você controlar a fonte na qual a função é usada, isso não é um problema, é apenas uma questão de pesquisa e substituição. Mas se a função for fornecida pela biblioteca, você não precisará alterar o nome por sua própria vontade. Isso quebrará muitos outros aplicativos que você não controla e incomoda muitas pessoas.
Você pode criar novas funções e marcar a função atual como indesejável em um documento ou por meio de código. E depois de alguns lançamentos, você pode finalmente matá-la.
Solução feia: crie novas funções, marque a atual como indesejável e
adicione sleep
ao início da função para forçar a atualização dos usuários da função antiga.
Boas linguagens possuem documentação embutida
Se a linguagem usar seu próprio modo de documentar funções, classes, módulos e tudo mais, e mesmo se houver um gerador simples de documentação, tudo o que for mencionado será bem documentado (não ótimo, mas pelo menos bom).
E os idiomas que não possuem documentação interna geralmente são mal documentados.
Linguagem é mais do que apenas linguagem
Você escreve em uma linguagem de programação e faz as coisas "funcionarem". Mas existem apenas palavras especiais: a linguagem possui um sistema de montagem, um sistema de gerenciamento de dependências, ferramentas para interação de ferramentas, bibliotecas e frameworks, existe uma comunidade, existe uma maneira de interagir com as pessoas.
Não selecione idiomas para facilitar o uso. Lembre-se de que você pode encontrar a sintaxe simples, mas ao escolher esse idioma, você também escolhe a maneira como os criadores do idioma se comunicam com sua comunidade.
Às vezes, é melhor deixar o aplicativo travar do que não fazer nada.
Embora isso pareça estranho, é melhor não adicionar manipulação de erros do que capturá-los silenciosamente e não fazer nada.
Java tem um padrão infelizmente comum:
try { something_that_can_raise_exception() } catch (Exception ex) { System.out.println(ex); }
Nada é feito com uma exceção aqui, apenas uma mensagem é exibida.
Se você não souber como lidar com o erro, deixe-o acontecer, para que pelo menos você possa descobrir
quando aconteceu.
Se você souber processar, faça
Ao contrário do parágrafo anterior: se você sabe quando uma exceção, erro ou resultado aparece e sabe como lidar com isso, faça-o. Mostre a mensagem de erro, tente salvar os dados em algum lugar, descarte os dados inseridos pelo usuário para uso posterior no log - apenas
processe-os .
Os tipos falam sobre quais dados você tem
A memória é apenas uma sequência de bytes. Bytes são simplesmente números de 0 a 255. O que esses números significam é descrito no sistema de tipos de idiomas.
Por exemplo, em C, o tipo de caractere (tipo de caractere) com um valor de 65 provavelmente será a letra "A" e um int com um valor de 65 será o número 65.
Lembre-se disso ao trabalhar com seus dados.
Ao adicionar booleanos, muitos esquecem de verificar o número de valores
True
. Recentemente me deparei com este exemplo JavaScript:
console.log(true+true === 2); > true console.log(true === 1); > false
Se seus dados tiverem um esquema, armazene-os como uma estrutura
Se os dados forem simples, por exemplo, apenas dois campos, você poderá armazená-los em uma lista (ou uma tupla, se o seu idioma permitir). Mas se os dados tiverem um esquema - um formato fixo -, sempre use alguma estrutura ou classe para armazená-los.
Reconheça e fique longe do culto à carga
A idéia do "culto à carga" é que, se alguém o fez, então nós podemos. Na maioria das vezes, o culto à carga é simplesmente uma "fuga fácil" do problema: por que deveríamos pensar em como armazenar corretamente os dados do usuário se o X já fez isso?
"Se a Big Company armazena dados dessa maneira, podemos fazê-lo."
"Se a Big Company usa, então isso é bom."
"A ferramenta certa para a tarefa" é uma maneira de impor sua opinião
A frase “a ferramenta certa para a tarefa” deve significar que existe uma ferramenta certa e errada para alguma coisa. Por exemplo, usando uma linguagem ou estrutura específica em vez da linguagem ou estrutura atual.
Mas toda vez que ouço essa expressão de alguém, as pessoas pressionam sua linguagem / estrutura favorita dessa maneira, em vez de, digamos, a linguagem / estrutura correta.
A "ferramenta certa" é mais óbvia do que você pensa
Talvez agora você esteja participando de um projeto no qual deseja processar algum texto. Você pode dizer: "Vamos usar o Perl, porque todo mundo sabe que o Perl é muito bom no processamento de texto".
O que você esquece: sua equipe é especializada em C. Todo mundo conhece C, não Perl.
Claro, se este é um projeto pequeno "no joelho", então é possível no Perl. E se o projeto é importante para a empresa, é melhor escrevê-lo em C.
PS: Seu projeto heróico (mais sobre isso abaixo) pode falhar por causa disso.
Não se encaixe no que está fora do seu projeto
Às vezes, em vez de usar ferramentas de extensão apropriadas, as pessoas começam a alterar bibliotecas e estruturas externas. Por exemplo, faça alterações diretamente no WordPress ou Django.
Assim, você pode facilmente e rapidamente tornar o projeto inadequado para manutenção. Assim que a nova versão for lançada, você precisará sincronizar as alterações com o projeto principal e logo descobrirá que não poderá mais aplicar as alterações e deixar a versão antiga da ferramenta externa cheia de falhas de segurança.
Padrões de batida de fluxos de dados
Esta é a minha opinião pessoal. Se você entender como os dados devem passar pelo seu código, será melhor para ele do que se você usar vários padrões de design.
Os padrões de design são usados para descrever soluções, não para encontrá-las.
Mais uma vez a minha opinião pessoal. De acordo com minhas observações, na maioria das vezes os padrões de design são usados para encontrar uma solução. E, como resultado, a solução - e às vezes o próprio problema - é distorcida para se ajustar ao padrão.
Primeiro, resolva seu problema. Encontre uma boa solução e pesquise entre os padrões para saber como sua solução é chamada.
Eu já vi isso muitas vezes: temos um problema, o padrão está próximo da solução correta, vamos usar o padrão, agora precisamos adicionar um monte de tudo à solução correta, para que ele corresponda ao padrão.
Aprenda o básico da programação funcional
Você não precisa se aprofundar nas perguntas “o que são mônadas” ou “é um functor”. Mas lembre-se: você não deve alterar dados constantemente; crie novos elementos com novos valores (considere dados imutáveis); na medida do possível, execute funções e classes que não armazenam estados internos (funções e classes puras).
O esforço cognitivo é o inimigo da legibilidade.
"
Dissonância cognitiva " é uma expressão velada "para entender isso, preciso lembrar simultaneamente duas (ou mais) coisas diferentes". E quanto mais indiretas essas informações tiverem, mais esforço será necessário para mantê-las em sua mente.
Por exemplo, adicionar booleanos para contar valores
True
é uma versão moderada da dissonância cognitiva. Se você ler o código e ver a função
sum()
, que, como você sabe, adiciona todos os números da lista, espera ver uma lista de números; e conheci pessoas usando
sum()
para contar valores
True
em uma lista de booleanos, o que é completamente confuso.
Número mágico sete mais ou menos dois
O "
número mágico " é um artigo em psicologia que descreve o número de elementos que uma pessoa é capaz de manter simultaneamente na memória de curto prazo.
Se você tem uma função que chama uma função que chama uma função que chama uma função que chama uma função que chama uma função, isso é um inferno para o leitor do seu código.
Apenas pense: vou obter o resultado dessa função, passá-lo para a segunda função, obter seu resultado, passar a terceira, etc.
Além disso, hoje os psicólogos estão mais frequentemente falando sobre o número mágico QUATRO, em vez de sete.
Pense na categoria de “composição de funções” (por exemplo, “eu chamarei essa função, depois aquela e depois lá ...”), e não na categoria de “chamada de funções” (por exemplo, “essa função chamará isso, chamará isso. .. ").
Os cortes são bons, mas apenas no curto prazo
Muitos idiomas, bibliotecas e estruturas oferecem métodos de atalho para reduzir o número de caracteres digitados.
Mais tarde, porém, ele voltará para você e você será forçado a remover os cortes e escrever tudo na íntegra.
Então, primeiro descubra o que uma abreviação específica está fazendo antes de usá-la.
Você não precisa escrever a coisa toda a princípio e depois alterá-la para abreviação: faça o que as abreviações fazem por você e pelo menos entenderá o que pode dar errado ou como substituir algo por uma versão completa.
Resista à tentação de "tranqüilidade"
Obviamente, o IDE ajudará você na conclusão automática de um monte de tudo e facilitará a criação de um projeto, mas você entende o que está acontecendo lá?
Você sabe como seu sistema de compilação funciona? Se você precisar executá-lo sem um IDE, poderá fazê-lo?
Você se lembra dos nomes das funções sem conclusão automática? É possível quebrar algo ou renomeá-lo para facilitar a compreensão?
Interesse-se pelo que está acontecendo sob o capô.
SEMPRE use fusos horários em datas
Ao trabalhar com datas,
sempre adicione fusos horários. Você
sempre terá problemas com incompatibilidade de fuso horário nos computadores e nos servidores e perde muito tempo para depuração, tentando entender por que a interface exibe a hora errada.
SEMPRE use UTF-8
Você terá os mesmos problemas com codificações e datas. Portanto, sempre converta valores de sequência em UTF-8, armazene-os em bancos de dados em UTF-8 e retorne de suas APIs para UTF-8.
Você pode converter para qualquer outra codificação, mas o UTF-8 venceu a guerra de codificação, portanto, é a maneira mais fácil de manter isso.
Comece estúpido
Uma das maneiras de se afastar do IDE é "começar de uma maneira estúpida": basta pegar o compilador, QUALQUER editor, com destaque de código e - programar, construir, executar.
Sim, não é fácil. Mas quando mais tarde você usar algum tipo de IDE, você só pensará nos botões "Sim, ele lança isso". É exatamente isso que os IDEs fazem.
Os logs são para eventos, não para a interface do usuário.
Durante muito tempo, usei logs para mostrar aos usuários o que está acontecendo com o aplicativo. Bem, você sabe, porque é muito mais fácil usar uma coisa do que duas.
Para informar os usuários sobre os eventos, use o formulário de saída padrão. Para relatórios de erros, mensagens de erro padrão. E use os logs apenas para armazenar dados que você pode processar facilmente posteriormente.
Os logs não são uma interface do usuário, mas uma entidade que você precisa analisar para recuperar informações no momento certo. Os logs não devem ser legíveis por humanos.
Depuradores superestimados
Já ouvi muitas reclamações de que os editores de código sem depuradores são terríveis, precisamente porque não têm depuradores.
Mas quando seu código está em operação, você
não pode executar seu depurador favorito. Inferno, você não pode nem executar seu IDE favorito. Mas o diário ... funciona em qualquer lugar. Você pode não ter as informações desejadas no momento da queda (por exemplo, devido a diferentes níveis de log), mas
pode ativar o log para descobrir o motivo posteriormente.
Fico em silêncio sobre o fato de que os próprios depuradores são ruins, eles simplesmente não fornecem a ajuda que muitos esperam deles.
Sempre use um sistema de controle de versão
“Esta é apenas a minha aplicação estúpida com a qual quero aprender algo” - isso não justifica a falta de um sistema de controle de versão.
Se você usar esse sistema desde o início, será mais fácil reverter quando você cometer um erro.
Uma alteração por confirmação
Eu conheci pessoas que escrevem as seguintes mensagens em confirmações: "Corrige o problema 1, 2 e 3". A menos que todos esses problemas se dupliquem - dos quais dois já devem estar fechados -, devem haver três confirmações em vez de uma.
Adira ao princípio de "uma alteração por confirmação". E por mudança quero dizer uma mudança em um arquivo. Se você precisar alterar três arquivos, confirme esses arquivos juntos. Pergunte a si mesmo: "se eu reverter essa alteração, o que deve desaparecer?"
"Git add -p" irá ajudá-lo com muitas mudanças
Isso se aplica apenas ao Git. Ele permite mesclar parcialmente os arquivos usando o parâmetro "-p", para que você possa selecionar apenas as alterações relacionadas entre si, deixando as outras para a nova confirmação.
Estruturar projetos por dados ou tipo, não funcionalidade
A maioria dos projetos usa a seguinte estrutura:
. +-- IncomingModels | +-- DataTypeInterface | +-- DataType1 | +-- DataType2 | +-- DataType3 +-- Filters | +-- FilterInterface | +-- FilterValidDataType2 +-- Processors | +-- ProcessorInterface | +-- ConvertDataType1ToDto1 | +-- ConvertDataType2ToDto2 +-- OutgoingModels +-- DtoInterface +-- Dto1 +-- Dto2
Ou seja, os dados são estruturados por funcionalidade (todos os modelos de entrada estão em um diretório ou pacote, todos os filtros estão em outro diretório ou pacote, etc.).
Isso funciona muito bem. Mas quando você estrutura de acordo com os dados, é muito mais fácil dividir o projeto em projetos menores, porque em algum momento você pode precisar fazer quase tudo da mesma forma que agora, com apenas pequenas diferenças.
. +-- Base | +-- IncomingModels | | +-- DataTypeInterface | +-- Filters | | +-- FilterInterface | +-- Processors | | +-- ProcessorInterface | +-- OutgoingModels | +-- DtoInterface +-- Data1 | +-- IncomingModels | | +-- DataType1 | +-- Processors | | +-- ConvertDataType1ToDto1 | +-- OutgoingModels | +-- Dto1 ...
Agora você pode criar um módulo que funciona
apenas com o Data1, outro módulo que funciona apenas com o Data2, etc. E então você pode separá-los em módulos isolados.
E quando você precisar criar outro projeto, também contendo Data1 e trabalhando com Data3, poderá reutilizar a maior parte do código no módulo Data1.
Faça bibliotecas
Eu sempre vi como os desenvolvedores criam mega-repositórios com projetos diferentes ou mantêm ramificações diferentes, não para que sejam um ambiente temporário para ingressar mais tarde na parte principal, mas simplesmente para dividir o projeto em partes menores (falando sobre dividir em módulos, imagine que, em vez de criar um novo projeto que reutilize o tipo Data1, eu usei um ramo com uma função principal completamente diferente e o tipo Data3).
Por que não alocar peças usadas com frequência a bibliotecas que podem ser conectadas em diferentes projetos?
Na maioria das vezes, o motivo é que as pessoas não sabem como criar bibliotecas ou estão preocupadas em como "publicar" essas bibliotecas em fontes de dependência sem entregá-las (portanto, não é melhor entender como sua ferramenta de gerenciamento de projetos obtém dependências para que você possa criar seu próprio repositório de dependência?).
Aprenda a monitorar
Em uma vida anterior, adicionei muitas métricas para entender como o sistema se comporta: quão rápido chegou, quão rápido foi, quanto havia entre a entrada e a saída, quantas tarefas foram processadas ...
Isso realmente dá uma boa idéia do comportamento do sistema. A velocidade está diminuindo? Para entender, posso verificar quais dados entram no sistema. A redução de velocidade é normal em algum momento?
O fato é que, sem monitoramento adicional, é bastante estranho tentar descobrir o quão "saudável" é o sistema. Uma verificação de saúde no estilo "Ele responde a solicitações" não é mais adequada.
Adicionar o monitoramento antecipadamente ajudará você a entender como o sistema se comporta.
Use arquivos de configuração
Imagine: você escreveu uma função na qual precisa passar um valor para iniciar o processamento (por exemplo, o ID da conta no Twitter). Mas então você precisa fazer isso com dois valores e basta chamar a função novamente com um valor diferente.
É melhor usar arquivos de configuração e apenas executar o aplicativo duas vezes, com duas configurações diferentes.
As opções de linha de comando parecem estranhas, mas são úteis
Se você transferir algo para os arquivos de configuração, poderá facilitar a vida dos usuários e adicionar a capacidade de selecionar e abrir o arquivo.
Hoje, para todos os idiomas, existem bibliotecas que trabalham com opções para a linha de comando. Eles ajudarão você a criar um bom utilitário, fornecendo uma interface de usuário padrão para tudo.
Não apenas composições funcionais, mas composições de aplicativos
O Unix usa esse conceito: "aplicativos que fazem uma coisa e fazem bem".
Eu disse que você pode usar um aplicativo com dois arquivos de configuração. E se você precisar de resultados de ambos os aplicativos? Em seguida, você pode escrever um aplicativo que leia os resultados do segundo e combine tudo em um resultado comum.
Mesmo ao usar a composição do aplicativo, comece estúpido
A composição dos aplicativos pode se transformar em microsserviços (o que é bom), mas exige um entendimento de como os aplicativos "se comunicam" entre si pela rede (protocolos e similares).
Não há necessidade de começar com isso. Os aplicativos podem escrever e ler arquivos, muito mais fácil. , .
, . «, », , « , ».
, . , .
,
, , . , . . . , .
, , . Lisp, . , Python
yield
, , , . , , , , .
, , . , « », « » ..
, .
,
, .
, , «»: , , , . , , . , , , .
, , . , .
, . (« ?»), .
… Google
, . , Google , . , , Google , .
C/C++ — K&R
. :)
Python — PEP8
PEP8. , .
, ?
sleep()
.
? ?
, .
sleepForSecs
sleepForMs
, ,
sleep
.
, .
«Zen of Python», .
,
, . , - . - , , , .
« , » — .: , — . , .
, Java , Rust. , Spring , ++.
, — , «» .
, .
—
, , - .
— , — .
« , »
« » « ». , , , , .
, .
,
, , , , .
.
( «»), , , . , AWS SQS ( ), , , RabbitMQ.
- , , , .
,
, . , . , .
( , ). , , , .
,
- , , . , ,
, , .
, . , , , « » « , ».
, .
«». , . , . , , . , .
: «, , , , ». , .
.
. - . «» «».
, , , . , .
, ,
. , , - , .
Não faça isso.
- ,
.
- , . , .
, . .
- ,
: - , . , .
«, , » — , .
, , . . , - . . .
, , . , , , - . , , .
,
« ».
- , , , . : « , , ».
.
, , , . .
, .
«»
«» — . , - « », - .
, , , . , , , .
.
, , «»
. - : «, , ?»
, . , , (, , ).
, —
, -, , , , ( ).
… , - , , «
!».
:
«» , , , , . , , .
, /, .
, .
- .
« » « »
: - , , , .
« » — , .
.
,
, , - , .
- .
, .
, , .
, , , .
… .
IT
.
, , 15 , 3-4 .
.
.
, , , - , , , , , , .
. , , URL, .
Trello — ,
« , », .
,
, « , », « , ».
. . , - .
, .
.
, , .
…
, . , « ». - « », , .
. .
Github «, » . , - .
.
: Python, , Java Python, .
«, »
, , , «, ».
- , , - . , .